diff --git a/.gitignore b/.gitignore index 0014ee8f..21a12484 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ mvnw /k8s/template-yaml/deploy/ /k8s/dockerfiles/html/ /k8s/dockerfiles/jar + +# web +**/node_modules diff --git a/k8s/build-java.sh b/k8s/build-java.sh index 7bb2180b..bb0884a3 100755 --- a/k8s/build-java.sh +++ b/k8s/build-java.sh @@ -6,8 +6,14 @@ baseDir="/home/somuns/ci4s" #判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块 if [ "$1" == "all" ]; then buildDir=$baseDir -else +elif [ "$1" == "manage" ]; then buildDir="$baseDir/ruoyi-modules/management-platform" +elif [ "$1" == "auth" ]; then + buildDir="$baseDir/ruoyi-auth" +elif [ "$1" == "gateway" ]; then + buildDir="$baseDir/ruoyi-gateway" +elif [ "$1" == "system" ]; then + buildDir="$baseDir/ruoyi-modules/ruoyi-system" fi echo "Building $buildDir" diff --git a/k8s/build.sh b/k8s/build.sh index e8f19b3c..13298d60 100755 --- a/k8s/build.sh +++ b/k8s/build.sh @@ -30,7 +30,7 @@ done echo "branch: $branch" echo "service: $service" -valid_services=("manage-front" "manage" "front" "all") +valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then echo "Invalid service name: $service" >&2 echo "Valid services are: ${valid_services[*]}" @@ -107,13 +107,19 @@ compile_java() { fi } -if [ "$service" == "manage-front" ] || [ "$service" == "front" ]; then +if [ "$service" == "front" ]; then # 编译前端 compile_front fi +if [ "$service" == "manage-front" ]; then + # 编译前端 + compile_front + # 编译java + compile_java "manage" +fi -if [ "$service" == "manage-front" ] || [ "$service" == "manage" ]; then +if [ "$service" != "manage-front" ] && [ "$service" != "all" ] && [ "$service" != "front" ]; then # 编译java compile_java $service fi diff --git a/k8s/build_and_deploy.sh b/k8s/build_and_deploy.sh index ef582cf3..44ea23fa 100755 --- a/k8s/build_and_deploy.sh +++ b/k8s/build_and_deploy.sh @@ -35,20 +35,6 @@ while getopts "b:s:e:h" opt; do esac done -valid_services=("manage-front" "manage" "front" "all") -if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then - echo "Invalid service name: $service" >&2 - echo "Valid services are: ${valid_services[*]}" - exit 1 -fi - -valid_envs=("dev" "test") -if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then - echo "Invalid environment: $env" >&2 - echo "Valid environments are: ${valid_envs[*]}" - exit 1 -fi - # 拉取指定分支的最新代码 echo "Checking out and pulling branch $branch..." @@ -68,6 +54,20 @@ fi chmod +777 ${baseDir}/k8s/*.sh +valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") +if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then + echo "Invalid service name: $service" >&2 + echo "Valid services are: ${valid_services[*]}" + exit 1 +fi + +valid_envs=("dev" "test") +if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then + echo "Invalid environment: $env" >&2 + echo "Valid environments are: ${valid_envs[*]}" + exit 1 +fi + echo "start build" sh ${baseDir}/k8s/build.sh -b ${branch} -s ${service} if [ $? -ne 0 ]; then diff --git a/k8s/deploy.sh b/k8s/deploy.sh index 5b3f7887..d06c05d9 100755 --- a/k8s/deploy.sh +++ b/k8s/deploy.sh @@ -27,7 +27,7 @@ done echo "Deploy service: $service, environment: $env" -valid_services=("manage-front" "manage" "front" "all") +valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then echo "Invalid service name: $service" >&2 echo "Valid services are: ${valid_services[*]}" @@ -129,15 +129,34 @@ build_and_deploy() { deploy_service ${yaml_file} } +if [ "$service" == "front" ]; then + build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml" +fi + # 构建和部署 manage 服务 -if [ "$service" == "manage-front" ] || [ "$service" == "manage" ]; then +if [ "$service" == "manage" ]; then build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml" fi +if [ "$service" == "auth" ]; then + #部署认证中心 + build_and_deploy "auth-dockerfile" "172.20.32.187/ci4s/ci4s-auth:${tag}" "k8s-5auth.yaml" +fi + +if [ "$service" == "gateway" ]; then + #部署网关 + build_and_deploy "gateway-dockerfile" "172.20.32.187/ci4s/ci4s-gateway:${tag}" "k8s-4gateway.yaml" +fi + +if [ "$service" == "system" ]; then + #部署系统服务 + build_and_deploy "system-dockerfile" "172.20.32.187/ci4s/ci4s-system:${tag}" "k8s-6system.yaml" +fi # 构建和部署 front 服务 -if [ "$service" == "manage-front" ] || [ "$service" == "front" ]; then +if [ "$service" == "manage-front" ]; then build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml" + build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml" fi 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/BasicTableInfo/index.less b/react-ui/src/components/BasicTableInfo/index.less index 314b05ca..850af79c 100644 --- a/react-ui/src/components/BasicTableInfo/index.less +++ b/react-ui/src/components/BasicTableInfo/index.less @@ -27,7 +27,7 @@ display: flex; flex: 1; flex-direction: column; - align-items: flex-start; + align-items: stretch; min-width: 0; } diff --git a/react-ui/src/components/IFramePage/index.tsx b/react-ui/src/components/IFramePage/index.tsx index 861a0c05..fab32ab2 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/Dataset/components/ResourceInfo/index.less b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less index 8cbba3d6..91eec995 100644 --- a/react-ui/src/pages/Dataset/components/ResourceInfo/index.less +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less @@ -50,7 +50,7 @@ height: 100%; .ant-tabs-nav-wrap { padding-top: 8px; - padding-left: 30px; + padding-left: @content-padding; background-color: white; border-radius: 10px 10px 0 0; } diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.less b/react-ui/src/pages/Dataset/components/ResourceIntro/index.less index 6ac9223b..28f0d976 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.less +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.less @@ -2,7 +2,7 @@ width: 100%; &__top { - padding: 20px 30px; + padding: 20px @content-padding; background: white; border-radius: 0 0 10px 10px; box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); @@ -22,4 +22,15 @@ font-size: @font-size; } } + + &--dataset { + height: 100%; + background-color: white; + border-radius: 0 0 10px 10px; + } + + &--dataset &__top { + border-radius: 0; + box-shadow: none; + } } diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx index 0aa3b7e3..e30dfa5d 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx @@ -12,6 +12,7 @@ import { } from '@/pages/Dataset/config'; import ModelMetrics from '@/pages/Model/components/ModelMetrics'; import { getGitUrl } from '@/utils'; +import classNames from 'classnames'; import styles from './index.less'; type ResourceIntroProps = { @@ -206,7 +207,11 @@ function ResourceIntro({ : getModelDatas(info as ModelData); return ( -
+
; +} + +export default GitLink; diff --git a/react-ui/src/pages/Model/components/ModelEvolution/index.less b/react-ui/src/pages/Model/components/ModelEvolution/index.less index cc91d358..a089198b 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/index.less +++ b/react-ui/src/pages/Model/components/ModelEvolution/index.less @@ -1,7 +1,7 @@ .model-evolution { width: 100%; height: 100%; - padding: 0 30px 20px; + padding: 0 @content-padding 20px; overflow-x: hidden; background: white; border-radius: 0 0 10px 10px; diff --git a/react-ui/src/pages/Model/components/ModelMetrics/index.less b/react-ui/src/pages/Model/components/ModelMetrics/index.less index 7856b5a1..77b763d8 100644 --- a/react-ui/src/pages/Model/components/ModelMetrics/index.less +++ b/react-ui/src/pages/Model/components/ModelMetrics/index.less @@ -1,7 +1,7 @@ .model-metrics { &__table { margin-top: 10px; - padding: 20px 30px 0; + padding: 20px @content-padding 0; background: white; border-radius: 10px; diff --git a/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less index f0d5449e..70de3494 100644 --- a/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less +++ b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less @@ -9,6 +9,7 @@ line-height: 42px; text-align: center; background: @background; + border-radius: 4px 4px 0 0; .singleLine(); } @@ -16,6 +17,7 @@ margin-bottom: 20px !important; color: @text-color-secondary; font-size: 13px; + line-height: 22px; word-break: break-all; .singleLine(); } @@ -54,9 +56,10 @@ flex: none; width: 117px; padding: 0 15px; - background: white; + background: rgba(255, 255, 255, 0.1); border: 1px solid .addAlpha(@primary-color, 0.2) []; border-radius: 4px; + box-shadow: 0px 3px 6px .addAlpha(@primary-color, 0.1) [] inset; &__title { margin-bottom: 20px; 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/pages/User/Login/login.tsx b/react-ui/src/pages/User/Login/login.tsx new file mode 100644 index 00000000..9ef18148 --- /dev/null +++ b/react-ui/src/pages/User/Login/login.tsx @@ -0,0 +1,221 @@ +import { clearSessionToken, setSessionToken } from '@/access'; +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 { history, useModel } from '@umijs/max'; +import { Button, Checkbox, Flex, Form, Image, Input, message, type InputRef } from 'antd'; +import CryptoJS from 'crypto-js'; +import { useEffect, useRef, useState } from 'react'; +import { flushSync } from 'react-dom'; +import styles from './login.less'; + +const VERSION = 1; +const AESKEY = 'OPENSOURCETECHNOLOGYCENTER'; + +const LoginInputPrefix = ({ icon }: { icon: string }) => { + return ( +
+ +
+
+ ); +}; + +const Login = () => { + const { initialState, setInitialState } = useModel('@@initialState'); + const [captchaCode, setCaptchaCode] = useState(''); + const [uuid, setUuid] = useState(''); + const [form] = Form.useForm(); + 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 }); + } + }, []); + const getCaptchaCode = async () => { + const [res] = await to(getCaptchaImg()); + if (res) { + const imgdata = `data:image/png;base64,${res.img}`; + setCaptchaCode(imgdata); + setUuid(res.uuid); + } + }; + + const fetchUserInfo = async () => { + const userInfo = await initialState?.fetchUserInfo?.(); + if (userInfo) { + flushSync(() => { + setInitialState((s) => ({ + ...s, + currentUser: userInfo, + })); + }); + } + }; + + // 登录 + const handleSubmit = async (values: API.LoginParams) => { + const [res, error] = await to(login({ ...values, uuid })); + if (res && res.data) { + const current = new Date(); + const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60); + const { access_token } = res.data; + setSessionToken(access_token, access_token, expireTime); + message.success('登录成功!'); + + LocalStorage.setItem(LocalStorage.rememberPasswordKey, values.autoLogin ? 'true' : 'false'); + if (values.autoLogin) { + const user = { + username: values.username, + password: values.password, + version: VERSION, + }; + const encrypted = CryptoJS.AES.encrypt(JSON.stringify(user), AESKEY).toString(); + LocalStorage.setItem(LocalStorage.loginUserKey, encrypted); + } else { + LocalStorage.removeItem(LocalStorage.loginUserKey); + } + + await fetchUserInfo(); + const urlParams = new URL(window.location.href).searchParams; + history.push(urlParams.get('redirect') || '/'); + } else { + if (error?.data?.code === 500 && error?.data?.msg === '验证码错误') { + captchaInputRef.current?.focus({ + cursor: 'all', + }); + } + + clearSessionToken(); + getCaptchaCode(); + } + }; + + 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/styles/theme.less b/react-ui/src/styles/theme.less index 0008455d..758eec31 100644 --- a/react-ui/src/styles/theme.less +++ b/react-ui/src/styles/theme.less @@ -38,11 +38,14 @@ ); // 字体大小 +@font-size-input: 14px; @font-size: 15px; -@font-size-title: 18px; @font-size-content: 16px; -@font-size-input: 14px; -@font-size-input-lg: 16px; +@font-size-input-lg: @font-size-content; +@font-size-title: 18px; + +// padding +@content-padding: 25px; // 函数 .addAlpha(@color, @alpha) { 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..c3d2c44a 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,9 +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(); - // console.log('pathname', pathname); - // console.log('search', search); + const newSearch = toHome || pathname === '/' ? '' : urlParams.toString(); if (pathname !== PageEnum.LOGIN) { closeAllModals(); removeAllPageCacheState(); @@ -88,6 +88,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); + } +}; + /** * 验证文件上传 * diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/config/Oauth2ClientProperties.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/config/Oauth2ClientProperties.java new file mode 100644 index 00000000..848f2fee --- /dev/null +++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/config/Oauth2ClientProperties.java @@ -0,0 +1,25 @@ +package com.ruoyi.auth.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "oauth2") +@Getter +@Setter +public class Oauth2ClientProperties { + + private String clientId; + private String clientSecret; + private String scope; + private String userAuthorizationUri; + private String accessTokenUri; + private String redirectUri; + private String logoutUri; + private String checkTokenUri; + private String loginPage; +} diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java index c53e91a3..4681a385 100644 --- a/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java +++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java @@ -2,12 +2,19 @@ package com.ruoyi.auth.controller; import javax.servlet.http.HttpServletRequest; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.auth.config.Oauth2ClientProperties; +import com.ruoyi.auth.form.AccessTokenVo; import com.ruoyi.auth.form.LoginKeyBody; +import com.ruoyi.common.core.exception.ServiceException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.*; import com.ruoyi.auth.form.LoginBody; import com.ruoyi.auth.form.RegisterBody; import com.ruoyi.auth.service.SysLoginService; @@ -18,6 +25,9 @@ import com.ruoyi.common.security.auth.AuthUtil; import com.ruoyi.common.security.service.TokenService; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.system.api.model.LoginUser; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; /** * token 控制 @@ -33,6 +43,9 @@ public class TokenController @Autowired private SysLoginService sysLoginService; + @Autowired + private Oauth2ClientProperties oauth2ClientProperties; + @PostMapping("login") public R login(@RequestBody LoginBody form) { @@ -51,6 +64,64 @@ public class TokenController return R.ok(tokenService.createToken(userInfo)); } + @PostMapping("loginByOauth2") + public R loginByOauth2(@RequestBody Map params) + /** + * { + * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZGVtby1hcHAiXSwiZXhwIjoxNzI5MTU0MTExLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6IjQzNGVkNmI0LWIzN2MtNDliMS04NTczLWZhNmU4YTg5YTUxYSIsImNsaWVudF9pZCI6IkFCQyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdfQ.U591q4fUaUBtBt5Ex-S2daM7DIl9-Ov0MsveymNfHxI", + * "token_type": "bearer", + * "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZGVtby1hcHAiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXRpIjoiNDM0ZWQ2YjQtYjM3Yy00OWIxLTg1NzMtZmE2ZThhODlhNTFhIiwiZXhwIjoxNzMxNjQ5OTY4LCJqdGkiOiJiODFhNmJkMC02ZDQzLTQ4M2QtOThmMy1hZWIxNDcyZDUwNzciLCJjbGllbnRfaWQiOiJBQkMifQ.0uMYVqW1G7j0chwxIdWGwpjDr12ogZPcD1iQfPsAs5k", + * "expires_in": 7198, + * "scope": "read write", + * "account_info": { + * "id": 1, + * "clientId": "ABC", + * "username": "admin", + * "mobile": "1232378743", + * "email": "abc@123.com" + * } + * } + */ + { + if (params.containsKey("code") && StringUtils.isNotBlank(params.get("code"))){ + RestTemplate restTemplate = new RestTemplate(); + String url = oauth2ClientProperties.getAccessTokenUri(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap map= new LinkedMultiValueMap<>(); + map.add("grant_type", "authorization_code"); + map.add("code", params.get("code")); + map.add("redirect_uri", oauth2ClientProperties.getRedirectUri()); + map.add("client_id", oauth2ClientProperties.getClientId()); + map.add("client_secret", oauth2ClientProperties.getClientSecret()); + + HttpEntity> request = new HttpEntity<>(map, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + String body = response.getBody(); + + AccessTokenVo accessTokenVo = JSON.parseObject(body, AccessTokenVo.class); + String accessToken = accessTokenVo.getAccess_token(); + AccessTokenVo.Account_info accountInfo = accessTokenVo.getAccount_info(); + + // 用户登录 + LoginUser userInfo = sysLoginService.login(accountInfo); + // 获取登录token + Map token = tokenService.createToken(userInfo); + token.put("checkTokenUri",oauth2ClientProperties.getCheckTokenUri()); + token.put("logoutUri",oauth2ClientProperties.getLogoutUri()); + token.put("oauth2AccessToken",accessToken); + return R.ok(token); + } + + throw new ServiceException("用户信息获取失败"); + } + + + @GetMapping("oauth2ClientInfo") + public R getClientInfo() { + return R.ok(JSON.toJSON(oauth2ClientProperties)); + } + @DeleteMapping("logout") public R logout(HttpServletRequest request) { diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/form/AccessTokenVo.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/form/AccessTokenVo.java new file mode 100644 index 00000000..2d47e1fc --- /dev/null +++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/form/AccessTokenVo.java @@ -0,0 +1,119 @@ +package com.ruoyi.auth.form; + +import java.io.Serializable; +import java.lang.Integer; +import java.lang.String; + +public class AccessTokenVo implements Serializable { + private String access_token; + + private String refresh_token; + + private String scope; + + private Account_info account_info; + + private String token_type; + + private Integer expires_in; + + public String getAccess_token() { + return this.access_token; + } + + public void setAccess_token(String access_token) { + this.access_token = access_token; + } + + public String getRefresh_token() { + return this.refresh_token; + } + + public void setRefresh_token(String refresh_token) { + this.refresh_token = refresh_token; + } + + public String getScope() { + return this.scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public Account_info getAccount_info() { + return this.account_info; + } + + public void setAccount_info(Account_info account_info) { + this.account_info = account_info; + } + + public String getToken_type() { + return this.token_type; + } + + public void setToken_type(String token_type) { + this.token_type = token_type; + } + + public Integer getExpires_in() { + return this.expires_in; + } + + public void setExpires_in(Integer expires_in) { + this.expires_in = expires_in; + } + + public static class Account_info implements Serializable { + private String clientId; + + private String mobile; + + private Integer id; + + private String email; + + private String username; + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getMobile() { + return this.mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public Integer getId() { + return this.id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + } +} diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java index 1bf324a3..2e1a57e8 100644 --- a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java +++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java @@ -1,5 +1,6 @@ package com.ruoyi.auth.service; +import com.ruoyi.auth.form.AccessTokenVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.ruoyi.common.core.constant.CacheConstants; @@ -193,4 +194,62 @@ public class SysLoginService } return userInfo; } + + public LoginUser login(AccessTokenVo.Account_info accountInfo) { + + String username = accountInfo.getUsername(); + + // 用户名或密码为空 错误 + if (StringUtils.isAnyBlank(username)) + { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写"); + throw new ServiceException("用户/密码必须填写"); + } + + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围"); + throw new ServiceException("用户名不在指定范围"); + } + // IP黑名单校验 + String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST)); + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) + { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单"); + throw new ServiceException("很遗憾,访问IP已被列入系统黑名单"); + } + // 查询用户信息 + R userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); + + if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) + { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在"); + throw new ServiceException("登录用户:" + username + " 不存在"); + // register(username, "123456"); +// userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); + } + + if (R.FAIL == userResult.getCode()) + { + throw new ServiceException(userResult.getMsg()); + } + + LoginUser userInfo = userResult.getData(); + SysUser user = userResult.getData().getSysUser(); + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除"); + throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + } + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员"); + throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + } + + recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功"); + return userInfo; + } } diff --git a/ruoyi-auth/src/main/resources/bootstrap.yml b/ruoyi-auth/src/main/resources/bootstrap.yml index 95a9dfdd..4dfb267d 100644 --- a/ruoyi-auth/src/main/resources/bootstrap.yml +++ b/ruoyi-auth/src/main/resources/bootstrap.yml @@ -33,9 +33,4 @@ spring: refresh: true - data-id: ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} group: DEFAULT_GROUP - refresh: true - - redis: - host: 172.20.32.150 - port: 6379 - password: \ No newline at end of file + refresh: true \ No newline at end of file diff --git a/ruoyi-gateway/src/main/resources/bootstrap.yml b/ruoyi-gateway/src/main/resources/bootstrap.yml index d389a43a..bcfd24d8 100644 --- a/ruoyi-gateway/src/main/resources/bootstrap.yml +++ b/ruoyi-gateway/src/main/resources/bootstrap.yml @@ -3,7 +3,7 @@ server: port: 8082 # Spring -spring: +spring: application: # 应用名称 name: ruoyi-gateway diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java index 7667f1ce..0a3b608d 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java @@ -83,7 +83,7 @@ public class DevEnvironmentController extends BaseController { * @return 删除是否成功 */ @DeleteMapping("{id}") - public GenericsAjaxResult deleteById(@PathVariable("id") Integer id) { + public GenericsAjaxResult deleteById(@PathVariable("id") Integer id) throws Exception { return genericsSuccess(this.devEnvironmentService.removeById(id)); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java index 055b60dc..4562c7d4 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java @@ -54,5 +54,5 @@ public interface DevEnvironmentService { */ boolean deleteById(Integer id); - String removeById(Integer id); + String removeById(Integer id) throws Exception; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java index 7992f089..606d35aa 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java @@ -138,7 +138,7 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { } @Override - public String removeById(Integer id) { + public String removeById(Integer id) throws Exception { DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); if (devEnvironment == null){ return "开发环境信息不存在"; @@ -152,6 +152,7 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { return "无权限删除该开发环境"; } + jupyterService.stopJupyterService(id); devEnvironment.setState(0); return this.devEnvironmentDao.update(devEnvironment)>0?"删除成功":"删除失败"; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java index faacf8dd..fc02f1f6 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java @@ -62,9 +62,10 @@ public class ImageServiceImpl implements ImageService { @Resource private MinioService minioService; // @Value("${minio.dataReleaseBucketName}") -// private String bucketName; - @Value("${harbor.bucketName}") + @Value("${minio.dataReleaseBucketName}") private String bucketName; + // @Value("${harbor.bucketName}") +// private String bucketName; @Value("${harbor.repository}") private String repository; @Value("${harbor.harborUrl}") @@ -184,12 +185,14 @@ public class ImageServiceImpl implements ImageService { // } List imageVersions = imageVersionService.queryByImageId(id); - for (ImageVersion imageVersion :imageVersions) { - dockerClientUtil.removeImage(imageVersion.getUrl(), imageVersion.getHostIp()); + for (ImageVersion imageVersion : imageVersions) { + if (StringUtils.isNotEmpty(imageVersion.getHostIp())) { + dockerClientUtil.removeImage(imageVersion.getUrl(), imageVersion.getHostIp()); + } } image.setState(0); - return this.imageDao.update(image) > 0 ? "删除成功" : "删除失败"; + return this.imageDao.update(image) > 0 ? "删除成功" : "删除失败"; } @@ -376,10 +379,10 @@ public class ImageServiceImpl implements ImageService { if (oldImage != null) { List oldImageVersions = imageVersionDao.queryByImageId(oldImage.getId()); for (ImageVersion oldImageVersion : oldImageVersions) { - if(oldImageVersion.getTagName().equals(imageVo.getTagName())){ + if (oldImageVersion.getTagName().equals(imageVo.getTagName())) { throw new IllegalStateException("镜像tag不能重复"); } - } + } } LoginUser loginUser = SecurityUtils.getLoginUser(); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java index e3e1e8b7..45c2a69e 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java @@ -10,8 +10,7 @@ import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.*; -import io.kubernetes.client.util.ClientBuilder; -import io.kubernetes.client.util.credentials.AccessTokenAuthentication; +import io.kubernetes.client.util.Config; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.json.JSONObject; @@ -60,9 +59,10 @@ public class K8sClientUtil { this.http = http; this.token = token; try { - this.apiClient = new ClientBuilder(). - setBasePath(http).setVerifyingSsl(false). - setAuthentication(new AccessTokenAuthentication(token)).build(); +// this.apiClient = new ClientBuilder(). +// setBasePath(http).setVerifyingSsl(false). +// setAuthentication(new AccessTokenAuthentication(token)).build(); + this.apiClient = Config.fromCluster(); } catch (Exception e) { log.error("构建K8s-Client异常", e); throw new RuntimeException("构建K8s-Client异常"); @@ -636,7 +636,17 @@ public class K8sClientUtil { public V1Pod createPodWithEnv(String podName, String namespace, String proxyUrl, String mountPath, String pvcName, String image) { CoreV1Api api = new CoreV1Api(apiClient); - V1PodList v1PodList = null; + + V1SecurityContext v1SecurityContext = new V1SecurityContext(); + v1SecurityContext.setPrivileged(true); + + // 配置卷和卷挂载 + List volumeMounts = new ArrayList<>(); + volumeMounts.add(new V1VolumeMount().name("workspace").mountPath(mountPath)); + + List volumes = new ArrayList<>(); + volumes.add(new V1Volume().name("workspace").hostPath(new V1HostPathVolumeSource().path(hostPath + "/" + podName + "/" + mountPath).type("DirectoryOrCreate"))); + V1Pod pod = new V1PodBuilder() .withNewMetadata() .withName(podName) @@ -645,7 +655,9 @@ public class K8sClientUtil { .addNewContainer() .withName(podName) .withImage(image) // 替换为您实际要使用的镜像名称 - .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath)) + .withSecurityContext(v1SecurityContext) +// .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath)) + .withVolumeMounts(volumeMounts) .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() .addNewEnv() .withName("HTTP_PROXY") @@ -660,9 +672,10 @@ public class K8sClientUtil { .withValue("localhost,kubernetes.default.svc") .endEnv() .endContainer() - .addNewVolume() - .withName("workspace").withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName)) - .endVolume() +// .addNewVolume() +// .withName("workspace").withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName)) +// .endVolume() + .withVolumes(volumes) .endSpec() .build(); try {