diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 66de518b..0639985b 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -27,6 +27,16 @@ export default [ }, ], }, + { + path: '/authorize', + layout: false, + component: './Authorize/index', + }, + { + path: '/gitlink', + layout: true, + component: './GitLink/index', + }, { path: '/user', layout: false, diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 4f911013..c018253e 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -7,7 +7,6 @@ import defaultSettings from '../config/defaultSettings'; import '../public/fonts/font.css'; import { getAccessToken } from './access'; import './dayjsConfig'; -import { PageEnum } from './enums/pagesEnums'; import './global.less'; import { removeAllPageCacheState } from './hooks/pageCacheState'; import { @@ -23,6 +22,7 @@ export { requestConfig as request } from './requestConfig'; import { type GlobalInitialState } from '@/types'; import { menuItemRender } from '@/utils/menuRender'; import ErrorBoundary from './components/ErrorBoundary'; +import { needAuth } from './utils'; import { gotoLoginPage } from './utils/ui'; /** @@ -40,14 +40,17 @@ export async function getInitialState(): Promise { roleNames: response.user.roles, } as API.CurrentUser; } catch (error) { - console.error(error); + console.error('1111', error); gotoLoginPage(); } return undefined; }; + // 如果不是登录页面,执行 const { location } = history; - if (location.pathname !== PageEnum.LOGIN) { + + console.log('getInitialState', needAuth(location.pathname)); + if (needAuth(location.pathname)) { const currentUser = await fetchUserInfo(); return { fetchUserInfo, @@ -94,7 +97,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { onPageChange: () => { const { location } = history; // 如果没有登录,重定向到 login - if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) { + if (!initialState?.currentUser && needAuth(location.pathname)) { gotoLoginPage(); } }, @@ -159,8 +162,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { const { location } = e; const menus = getRemoteMenu(); - // console.log('onRouteChange', e); - if (menus === null && location.pathname !== PageEnum.LOGIN) { + console.log('onRouteChange', menus); + if (menus === null && needAuth(location.pathname)) { history.go(0); } }; @@ -170,12 +173,12 @@ export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { }; export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { - //console.log('patchClientRoutes', e); + console.log('patchClientRoutes', e); patchRouteWithRemoteMenus(e.routes); }; export function render(oldRender: () => void) { - // console.log('render'); + console.log('render'); const token = getAccessToken(); if (!token || token?.length === 0) { oldRender(); diff --git a/react-ui/src/components/IFramePage/index.tsx b/react-ui/src/components/IFramePage/index.tsx index c4ab9d3f..ce3e1c48 100644 --- a/react-ui/src/components/IFramePage/index.tsx +++ b/react-ui/src/components/IFramePage/index.tsx @@ -12,6 +12,7 @@ export enum IframePageType { DatasetAnnotation = 'DatasetAnnotation', // 数据标注 AppDevelopment = 'AppDevelopment', // 应用开发 DevEnv = 'DevEnv', // 开发环境 + GitLink = 'GitLink', } const getRequestAPI = (type: IframePageType): (() => Promise) => { @@ -26,6 +27,8 @@ const getRequestAPI = (type: IframePageType): (() => Promise) => { code: 200, data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '', }); + case IframePageType.GitLink: + return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); } }; diff --git a/react-ui/src/components/RightContent/AvatarDropdown.tsx b/react-ui/src/components/RightContent/AvatarDropdown.tsx index 5f8d639a..d93d4f74 100644 --- a/react-ui/src/components/RightContent/AvatarDropdown.tsx +++ b/react-ui/src/components/RightContent/AvatarDropdown.tsx @@ -1,6 +1,8 @@ import { clearSessionToken } from '@/access'; import { setRemoteMenu } from '@/services/session'; import { logout } from '@/services/system/auth'; +import { ClientInfo } from '@/types'; +import SessionStorage from '@/utils/sessionStorage'; import { gotoLoginPage } from '@/utils/ui'; import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; import { setAlpha } from '@ant-design/pro-components'; @@ -64,6 +66,11 @@ const AvatarDropdown: React.FC = ({ menu }) => { clearSessionToken(); setRemoteMenu(null); gotoLoginPage(); + const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true); + if (clientInfo) { + const { logoutUri } = clientInfo; + location.replace(logoutUri); + } }; const actionClassName = useEmotionCss(({ token }) => { return { diff --git a/react-ui/src/pages/Authorize/index.less b/react-ui/src/pages/Authorize/index.less new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/pages/Authorize/index.tsx b/react-ui/src/pages/Authorize/index.tsx new file mode 100644 index 00000000..f3624f32 --- /dev/null +++ b/react-ui/src/pages/Authorize/index.tsx @@ -0,0 +1,50 @@ +import { setSessionToken } from '@/access'; +import { loginByOauth2Req } from '@/services/auth'; +import { to } from '@/utils/promise'; +import { history, useModel, useSearchParams } from '@umijs/max'; +import { message } from 'antd'; +import { useEffect } from 'react'; +import { flushSync } from 'react-dom'; +import styles from './index.less'; + +function Authorize() { + const { initialState, setInitialState } = useModel('@@initialState'); + const [searchParams] = useSearchParams(); + const code = searchParams.get('code'); + const redirect = searchParams.get('redirect'); + useEffect(() => { + loginByOauth2(); + }, []); + + // 登录 + const loginByOauth2 = async () => { + const params = { + code, + }; + const [res] = await to(loginByOauth2Req(params)); + debugger; + if (res && res.data) { + const { access_token, expires_in } = res.data; + setSessionToken(access_token, access_token, expires_in); + message.success('登录成功!'); + await fetchUserInfo(); + history.push(redirect || '/'); + } + }; + + const fetchUserInfo = async () => { + const userInfo = await initialState?.fetchUserInfo?.(); + if (userInfo) { + flushSync(() => { + setInitialState((s) => ({ + ...s, + currentUser: userInfo, + })); + }); + } + }; + + return
; +} + +export default Authorize; diff --git a/react-ui/src/pages/GitLink/index.tsx b/react-ui/src/pages/GitLink/index.tsx new file mode 100644 index 00000000..8646926d --- /dev/null +++ b/react-ui/src/pages/GitLink/index.tsx @@ -0,0 +1,7 @@ +import IframePage, { IframePageType } from '@/components/IFramePage'; + +function GitLink() { + return ; +} + +export default GitLink; diff --git a/react-ui/src/pages/User/Login/index.tsx b/react-ui/src/pages/User/Login/index.tsx index 9ef18148..86d56ff6 100644 --- a/react-ui/src/pages/User/Login/index.tsx +++ b/react-ui/src/pages/User/Login/index.tsx @@ -1,11 +1,12 @@ import { clearSessionToken, setSessionToken } from '@/access'; +import { getClientInfoReq } from '@/services/auth'; import { getCaptchaImg, login } from '@/services/system/auth'; -import { parseJsonText } from '@/utils'; -import { safeInvoke } from '@/utils/functional'; import LocalStorage from '@/utils/localStorage'; import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import { gotoOAuth2 } from '@/utils/ui'; import { history, useModel } from '@umijs/max'; -import { Button, Checkbox, Flex, Form, Image, Input, message, type InputRef } from 'antd'; +import { Form, message, type InputRef } from 'antd'; import CryptoJS from 'crypto-js'; import { useEffect, useRef, useState } from 'react'; import { flushSync } from 'react-dom'; @@ -31,25 +32,35 @@ const Login = () => { const captchaInputRef = useRef(null); useEffect(() => { - getCaptchaCode(); - const autoLogin = LocalStorage.getItem(LocalStorage.rememberPasswordKey) ?? 'false'; - if (autoLogin === 'true') { - const userStorage = LocalStorage.getItem(LocalStorage.loginUserKey); - const userJson = safeInvoke((text: string) => - CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8), - )(userStorage); - const user = safeInvoke(parseJsonText)(userJson); - if (user && typeof user === 'object' && user.version === VERSION) { - const { username, password } = user; - form.setFieldsValue({ username: username, password: password, autoLogin: true }); - } else { - form.setFieldsValue({ username: '', password: '', autoLogin: true }); - LocalStorage.removeItem(LocalStorage.loginUserKey); - } - } else { - form.setFieldsValue({ username: '', password: '', autoLogin: false }); - } + // getCaptchaCode(); + // const autoLogin = LocalStorage.getItem(LocalStorage.rememberPasswordKey) ?? 'false'; + // if (autoLogin === 'true') { + // const userStorage = LocalStorage.getItem(LocalStorage.loginUserKey); + // const userJson = safeInvoke((text: string) => + // CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8), + // )(userStorage); + // const user = safeInvoke(parseJsonText)(userJson); + // if (user && typeof user === 'object' && user.version === VERSION) { + // const { username, password } = user; + // form.setFieldsValue({ username: username, password: password, autoLogin: true }); + // } else { + // form.setFieldsValue({ username: '', password: '', autoLogin: true }); + // LocalStorage.removeItem(LocalStorage.loginUserKey); + // } + // } else { + // form.setFieldsValue({ username: '', password: '', autoLogin: false }); + // } + getClientInfo(); }, []); + const getClientInfo = async () => { + const [res] = await to(getClientInfoReq()); + if (res && res.data) { + const clientInfo = res.data; + SessionStorage.setItem(SessionStorage.clientInfoKey, clientInfo, true); + gotoOAuth2(); + } + }; + const getCaptchaCode = async () => { const [res] = await to(getCaptchaImg()); if (res) { @@ -71,6 +82,12 @@ const Login = () => { } }; + const handleSubmit2 = async (values: API.LoginParams) => { + const url = + 'http://172.20.32.106:8080/oauth/authorize?client_id=ci4s&response_type=code&grant_type=authorization_code'; + window.location.href = url; + }; + // 登录 const handleSubmit = async (values: API.LoginParams) => { const [res, error] = await to(login({ ...values, uuid })); @@ -109,113 +126,115 @@ const Login = () => { } }; - return ( -
-
-
- - 智能材料科研平台 -
-
- 智能材料科研平台 - -
-
- 大语言模型运维 统一管理平台 -
- -
-
-
-
- 欢迎登录 - 智能材料科研平台 -
-
-
账号登录
-
-
- - } - allowClear - /> - - - - } - allowClear - /> - - - -
- - - } - ref={captchaInputRef} - allowClear - /> - -
- 验证码 getCaptchaCode()} - /> -
- - - 记住密码 - - - - - -
-
-
-
-
-
- ); + return
; + + // return ( + //
+ //
+ //
+ // + // 智能材料科研平台 + //
+ //
+ // 智能材料科研平台 + // + //
+ //
+ // 大语言模型运维 统一管理平台 + //
+ // + //
+ //
+ //
+ //
+ // 欢迎登录 + // 智能材料科研平台 + //
+ //
+ //
账号登录
+ //
+ //
+ // + // } + // allowClear + // /> + // + + // + // } + // allowClear + // /> + // + + // + //
+ // + // + // } + // ref={captchaInputRef} + // allowClear + // /> + // + //
+ // 验证码 getCaptchaCode()} + // /> + //
+ + // + // 记住密码 + // + + // + // + // + //
+ //
+ //
+ //
+ //
+ //
+ // ); }; export default Login; diff --git a/react-ui/src/services/auth/index.js b/react-ui/src/services/auth/index.js new file mode 100644 index 00000000..853ad6ab --- /dev/null +++ b/react-ui/src/services/auth/index.js @@ -0,0 +1,16 @@ +import { request } from '@umijs/max'; + +// 单点登录 +export function loginByOauth2Req(data) { + return request(`/api/auth/loginByOauth2`, { + method: 'POST', + data, + }); +} + +// 登录获取客户端信息 +export function getClientInfoReq() { + return request(`/api/auth/oauth2ClientInfo`, { + method: 'GET', + }); +} \ No newline at end of file diff --git a/react-ui/src/types.ts b/react-ui/src/types.ts index 40348fab..a7df0561 100644 --- a/react-ui/src/types.ts +++ b/react-ui/src/types.ts @@ -7,6 +7,17 @@ import { ExperimentStatus, TensorBoardStatus } from '@/enums'; import type { Settings as LayoutSettings } from '@ant-design/pro-components'; +export type ClientInfo = { + accessTokenUri: string; + checkTokenUri: string; + clientId: string; + clientSecret: string; + loginPage: string; + logoutUri: string; + redirectUri: string; + userAuthorizationUri: string; +}; + // 全局初始状态类型 export type GlobalInitialState = { settings?: Partial; @@ -14,6 +25,7 @@ export type GlobalInitialState = { fetchUserInfo?: () => Promise; loading?: boolean; collapsed?: boolean; + clientInfo?: ClientInfo; }; // 流水线全局参数 diff --git a/react-ui/src/utils/sessionStorage.ts b/react-ui/src/utils/sessionStorage.ts index 41117e1e..8ffa8836 100644 --- a/react-ui/src/utils/sessionStorage.ts +++ b/react-ui/src/utils/sessionStorage.ts @@ -9,6 +9,8 @@ export default class SessionStorage { static readonly serviceVersionInfoKey = 'service-version-info'; // 编辑器 url static readonly editorUrlKey = 'editor-url'; + // 客户端信息 + static readonly clientInfoKey = 'client-info'; static getItem(key: string, isObject: boolean = false) { const jsonStr = sessionStorage.getItem(key); diff --git a/react-ui/src/utils/ui.tsx b/react-ui/src/utils/ui.tsx index 9034a67b..12f013f2 100644 --- a/react-ui/src/utils/ui.tsx +++ b/react-ui/src/utils/ui.tsx @@ -6,9 +6,11 @@ import { PageEnum } from '@/enums/pagesEnums'; import { removeAllPageCacheState } from '@/hooks/pageCacheState'; import themes from '@/styles/theme.less'; +import { type ClientInfo } from '@/types'; import { history } from '@umijs/max'; import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; import { closeAllModals } from './modal'; +import SessionStorage from './sessionStorage'; type ModalConfirmProps = ModalFuncProps & { isDelete?: boolean; @@ -75,7 +77,7 @@ export const gotoLoginPage = (toHome: boolean = true) => { const { pathname, search } = location; const urlParams = new URLSearchParams(); urlParams.append('redirect', pathname + search); - const newSearch = toHome && pathname !== '/' ? '' : urlParams.toString(); + const newSearch = toHome || pathname === '/' ? '' : urlParams.toString(); // console.log('pathname', pathname); // console.log('search', search); if (pathname !== PageEnum.LOGIN) { @@ -88,6 +90,15 @@ export const gotoLoginPage = (toHome: boolean = true) => { } }; +export const gotoOAuth2 = () => { + const clientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true) as ClientInfo; + if (clientInfo) { + const { clientId, userAuthorizationUri } = clientInfo; + const url = `${userAuthorizationUri}?client_id=${clientId}&response_type=code&grant_type=authorization_code`; + location.replace(url); + } +}; + /** * 验证文件上传 *