Browse Source

Merge remote-tracking branch 'origin/dev' into dev-automl

dev-automl
chenzhihang 1 year ago
parent
commit
9c9fee871e
38 changed files with 904 additions and 198 deletions
  1. +3
    -0
      .gitignore
  2. +7
    -1
      k8s/build-java.sh
  3. +9
    -3
      k8s/build.sh
  4. +17
    -17
      k8s/build_and_deploy.sh
  5. +22
    -3
      k8s/deploy.sh
  6. +10
    -0
      react-ui/config/routes.ts
  7. +11
    -8
      react-ui/src/app.tsx
  8. +1
    -1
      react-ui/src/components/BasicTableInfo/index.less
  9. +3
    -0
      react-ui/src/components/IFramePage/index.tsx
  10. +7
    -0
      react-ui/src/components/RightContent/AvatarDropdown.tsx
  11. +0
    -0
      react-ui/src/pages/Authorize/index.less
  12. +50
    -0
      react-ui/src/pages/Authorize/index.tsx
  13. +1
    -1
      react-ui/src/pages/Dataset/components/ResourceInfo/index.less
  14. +12
    -1
      react-ui/src/pages/Dataset/components/ResourceIntro/index.less
  15. +6
    -1
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  16. +1
    -1
      react-ui/src/pages/Dataset/components/ResourceVersion/index.less
  17. +7
    -0
      react-ui/src/pages/GitLink/index.tsx
  18. +1
    -1
      react-ui/src/pages/Model/components/ModelEvolution/index.less
  19. +1
    -1
      react-ui/src/pages/Model/components/ModelMetrics/index.less
  20. +4
    -1
      react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less
  21. +147
    -128
      react-ui/src/pages/User/Login/index.tsx
  22. +221
    -0
      react-ui/src/pages/User/Login/login.tsx
  23. +16
    -0
      react-ui/src/services/auth/index.js
  24. +6
    -3
      react-ui/src/styles/theme.less
  25. +12
    -0
      react-ui/src/types.ts
  26. +2
    -0
      react-ui/src/utils/sessionStorage.ts
  27. +12
    -3
      react-ui/src/utils/ui.tsx
  28. +25
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/config/Oauth2ClientProperties.java
  29. +75
    -4
      ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java
  30. +119
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/form/AccessTokenVo.java
  31. +59
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
  32. +1
    -1
      ruoyi-gateway/src/main/resources/bootstrap.yml
  33. +1
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java
  34. +1
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java
  35. +2
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java
  36. +8
    -6
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java
  37. +1
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ServiceServiceImpl.java
  38. +23
    -10
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java

+ 3
- 0
.gitignore View File

@@ -55,3 +55,6 @@ mvnw
/k8s/template-yaml/deploy/
/k8s/dockerfiles/html/
/k8s/dockerfiles/jar

# web
**/node_modules

+ 7
- 1
k8s/build-java.sh View File

@@ -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"


+ 9
- 3
k8s/build.sh View File

@@ -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


+ 17
- 17
k8s/build_and_deploy.sh View File

@@ -3,6 +3,10 @@
#记录开始时间
startTime=$(date +%s)

# 登录到目标环境
baseDir="/home/somuns/ci4s"
cd ${baseDir}

#build
# 默认参数
branch="master"
@@ -31,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..."

@@ -64,9 +54,19 @@ fi

chmod +777 ${baseDir}/k8s/*.sh

# 登录到目标环境
baseDir="/home/somuns/ci4s"
cd ${baseDir}
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}


+ 22
- 3
k8s/deploy.sh View File

@@ -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




+ 10
- 0
react-ui/config/routes.ts View File

@@ -27,6 +27,16 @@ export default [
},
],
},
{
path: '/authorize',
layout: false,
component: './Authorize/index',
},
{
path: '/gitlink',
layout: true,
component: './GitLink/index',
},
{
path: '/user',
layout: false,


+ 11
- 8
react-ui/src/app.tsx View File

@@ -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<GlobalInitialState> {
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();


+ 1
- 1
react-ui/src/components/BasicTableInfo/index.less View File

@@ -27,7 +27,7 @@
display: flex;
flex: 1;
flex-direction: column;
align-items: flex-start;
align-items: stretch;
min-width: 0;
}



+ 3
- 0
react-ui/src/components/IFramePage/index.tsx View File

@@ -12,6 +12,7 @@ export enum IframePageType {
DatasetAnnotation = 'DatasetAnnotation', // 数据标注
AppDevelopment = 'AppDevelopment', // 应用开发
DevEnv = 'DevEnv', // 开发环境
GitLink = 'GitLink',
}

const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
@@ -26,6 +27,8 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
code: 200,
data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '',
});
case IframePageType.GitLink:
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' });
}
};



+ 7
- 0
react-ui/src/components/RightContent/AvatarDropdown.tsx View File

@@ -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<GlobalHeaderRightProps> = ({ 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 {


+ 0
- 0
react-ui/src/pages/Authorize/index.less View File


+ 50
- 0
react-ui/src/pages/Authorize/index.tsx View File

@@ -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 <div className={styles.container}></div>;
}

export default Authorize;

+ 1
- 1
react-ui/src/pages/Dataset/components/ResourceInfo/index.less View File

@@ -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;
}


+ 12
- 1
react-ui/src/pages/Dataset/components/ResourceIntro/index.less View File

@@ -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;
}
}

+ 6
- 1
react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx View File

@@ -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 (
<div className={styles['resource-intro']}>
<div
className={classNames(styles['resource-intro'], {
[styles['resource-intro--dataset']]: resourceType === ResourceType.Dataset,
})}
>
<div className={styles['resource-intro__top']}>
<SubAreaTitle
title="基本信息"


+ 1
- 1
react-ui/src/pages/Dataset/components/ResourceVersion/index.less View File

@@ -1,6 +1,6 @@
.resource-version {
min-height: 100%;
padding: 20px 30px;
padding: 20px @content-padding;
color: @text-color;
font-size: @font-size-content;
background: white;


+ 7
- 0
react-ui/src/pages/GitLink/index.tsx View File

@@ -0,0 +1,7 @@
import IframePage, { IframePageType } from '@/components/IFramePage';

function GitLink() {
return <IframePage type={IframePageType.GitLink}></IframePage>;
}

export default GitLink;

+ 1
- 1
react-ui/src/pages/Model/components/ModelEvolution/index.less View File

@@ -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;


+ 1
- 1
react-ui/src/pages/Model/components/ModelMetrics/index.less View File

@@ -1,7 +1,7 @@
.model-metrics {
&__table {
margin-top: 10px;
padding: 20px 30px 0;
padding: 20px @content-padding 0;
background: white;
border-radius: 10px;



+ 4
- 1
react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less View File

@@ -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;


+ 147
- 128
react-ui/src/pages/User/Login/index.tsx View File

@@ -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<InputRef>(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 (
<div className={styles['user-login']}>
<div className={styles['user-login__left']}>
<div className={styles['user-login__left__top']}>
<img
src={require('@/assets/img/logo.png')}
style={{ width: '32px', marginRight: '12px' }}
draggable={false}
alt=""
/>
智能材料科研平台
</div>
<div className={styles['user-login__left__title']}>
<span>智能材料科研平台</span>
<img
src={require('@/assets/img/login-ai-logo.png')}
className={styles['user-login__left__title__img']}
draggable={false}
alt=""
/>
</div>
<div className={styles['user-login__left__message']}>
<span>大语言模型运维 统一管理平台</span>
</div>
<img
className={styles['user-login__left__bottom-img']}
src={require('@/assets/img/login-left-image.png')}
draggable={false}
alt=""
/>
</div>
<div className={styles['user-login__right']}>
<div>
<div className={styles['user-login__right__title']}>
<span style={{ color: '#111111' }}>欢迎登录</span>
<span>智能材料科研平台</span>
</div>
<div className={styles['user-login__right__content']}>
<div className={styles['user-login__right__content__title']}>账号登录</div>
<div className={styles['user-login__right__content__form']}>
<Form
labelCol={{ span: 0 }}
wrapperCol={{ span: 24 }}
initialValues={{ autoLogin: true }}
onFinish={handleSubmit}
autoComplete="off"
form={form}
>
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
<Input
placeholder="请输入用户名"
prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />}
allowClear
/>
</Form.Item>

<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password
placeholder="请输入密码"
prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />}
allowClear
/>
</Form.Item>

<Flex align="start" style={{ height: '98px' }}>
<div style={{ flex: 1 }}>
<Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}>
<Input
placeholder="请输入验证码"
prefix={
<LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} />
}
ref={captchaInputRef}
allowClear
/>
</Form.Item>
</div>
<Image
className={styles['user-login__right__content__form__captcha']}
src={captchaCode}
alt="验证码"
preview={false}
onClick={() => getCaptchaCode()}
/>
</Flex>

<Form.Item
name="autoLogin"
valuePropName="checked"
labelCol={{ span: 0 }}
wrapperCol={{ span: 16 }}
>
<Checkbox>记住密码</Checkbox>
</Form.Item>

<Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}>
<Button type="primary" htmlType="submit">
登录
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
</div>
</div>
);
return <div className={styles['user-login']}></div>;

// return (
// <div className={styles['user-login']}>
// <div className={styles['user-login__left']}>
// <div className={styles['user-login__left__top']}>
// <img
// src={require('@/assets/img/logo.png')}
// style={{ width: '32px', marginRight: '12px' }}
// draggable={false}
// alt=""
// />
// 智能材料科研平台
// </div>
// <div className={styles['user-login__left__title']}>
// <span>智能材料科研平台</span>
// <img
// src={require('@/assets/img/login-ai-logo.png')}
// className={styles['user-login__left__title__img']}
// draggable={false}
// alt=""
// />
// </div>
// <div className={styles['user-login__left__message']}>
// <span>大语言模型运维 统一管理平台</span>
// </div>
// <img
// className={styles['user-login__left__bottom-img']}
// src={require('@/assets/img/login-left-image.png')}
// draggable={false}
// alt=""
// />
// </div>
// <div className={styles['user-login__right']}>
// <div>
// <div className={styles['user-login__right__title']}>
// <span style={{ color: '#111111' }}>欢迎登录</span>
// <span>智能材料科研平台</span>
// </div>
// <div className={styles['user-login__right__content']}>
// <div className={styles['user-login__right__content__title']}>账号登录</div>
// <div className={styles['user-login__right__content__form']}>
// <Form
// labelCol={{ span: 0 }}
// wrapperCol={{ span: 24 }}
// initialValues={{ autoLogin: true }}
// onFinish={handleSubmit2}
// autoComplete="off"
// form={form}
// >
// <Form.Item name="username" rules={[{ required: false, message: '请输入用户名' }]}>
// <Input
// placeholder="请输入用户名"
// prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />}
// allowClear
// />
// </Form.Item>

// <Form.Item name="password" rules={[{ required: false, message: '请输入密码' }]}>
// <Input.Password
// placeholder="请输入密码"
// prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />}
// allowClear
// />
// </Form.Item>

// <Flex align="start" style={{ height: '98px' }}>
// <div style={{ flex: 1 }}>
// <Form.Item name="code" rules={[{ required: false, message: '请输入验证码' }]}>
// <Input
// placeholder="请输入验证码"
// prefix={
// <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} />
// }
// ref={captchaInputRef}
// allowClear
// />
// </Form.Item>
// </div>
// <Image
// className={styles['user-login__right__content__form__captcha']}
// src={captchaCode}
// alt="验证码"
// preview={false}
// onClick={() => getCaptchaCode()}
// />
// </Flex>

// <Form.Item
// name="autoLogin"
// valuePropName="checked"
// labelCol={{ span: 0 }}
// wrapperCol={{ span: 16 }}
// >
// <Checkbox>记住密码</Checkbox>
// </Form.Item>

// <Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}>
// <Button type="primary" htmlType="submit">
// 登录
// </Button>
// </Form.Item>
// </Form>
// </div>
// </div>
// </div>
// </div>
// </div>
// );
};

export default Login;

+ 221
- 0
react-ui/src/pages/User/Login/login.tsx View File

@@ -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 (
<div className={styles['login-input-prefix']}>
<img className={styles['login-input-prefix__icon']} src={icon} alt="" draggable={false} />
<div className={styles['login-input-prefix__line']}></div>
</div>
);
};

const Login = () => {
const { initialState, setInitialState } = useModel('@@initialState');
const [captchaCode, setCaptchaCode] = useState<string>('');
const [uuid, setUuid] = useState<string>('');
const [form] = Form.useForm();
const captchaInputRef = useRef<InputRef>(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 (
<div className={styles['user-login']}>
<div className={styles['user-login__left']}>
<div className={styles['user-login__left__top']}>
<img
src={require('@/assets/img/logo.png')}
style={{ width: '32px', marginRight: '12px' }}
draggable={false}
alt=""
/>
智能材料科研平台
</div>
<div className={styles['user-login__left__title']}>
<span>智能材料科研平台</span>
<img
src={require('@/assets/img/login-ai-logo.png')}
className={styles['user-login__left__title__img']}
draggable={false}
alt=""
/>
</div>
<div className={styles['user-login__left__message']}>
<span>大语言模型运维 统一管理平台</span>
</div>
<img
className={styles['user-login__left__bottom-img']}
src={require('@/assets/img/login-left-image.png')}
draggable={false}
alt=""
/>
</div>
<div className={styles['user-login__right']}>
<div>
<div className={styles['user-login__right__title']}>
<span style={{ color: '#111111' }}>欢迎登录</span>
<span>智能材料科研平台</span>
</div>
<div className={styles['user-login__right__content']}>
<div className={styles['user-login__right__content__title']}>账号登录</div>
<div className={styles['user-login__right__content__form']}>
<Form
labelCol={{ span: 0 }}
wrapperCol={{ span: 24 }}
initialValues={{ autoLogin: true }}
onFinish={handleSubmit}
autoComplete="off"
form={form}
>
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
<Input
placeholder="请输入用户名"
prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />}
allowClear
/>
</Form.Item>

<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password
placeholder="请输入密码"
prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />}
allowClear
/>
</Form.Item>

<Flex align="start" style={{ height: '98px' }}>
<div style={{ flex: 1 }}>
<Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}>
<Input
placeholder="请输入验证码"
prefix={
<LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} />
}
ref={captchaInputRef}
allowClear
/>
</Form.Item>
</div>
<Image
className={styles['user-login__right__content__form__captcha']}
src={captchaCode}
alt="验证码"
preview={false}
onClick={() => getCaptchaCode()}
/>
</Flex>

<Form.Item
name="autoLogin"
valuePropName="checked"
labelCol={{ span: 0 }}
wrapperCol={{ span: 16 }}
>
<Checkbox>记住密码</Checkbox>
</Form.Item>

<Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}>
<Button type="primary" htmlType="submit">
登录
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
</div>
</div>
);
};

export default Login;

+ 16
- 0
react-ui/src/services/auth/index.js View File

@@ -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',
});
}

+ 6
- 3
react-ui/src/styles/theme.less View File

@@ -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) {


+ 12
- 0
react-ui/src/types.ts View File

@@ -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<LayoutSettings>;
@@ -14,6 +25,7 @@ export type GlobalInitialState = {
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
loading?: boolean;
collapsed?: boolean;
clientInfo?: ClientInfo;
};

// 流水线全局参数


+ 2
- 0
react-ui/src/utils/sessionStorage.ts View File

@@ -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);


+ 12
- 3
react-ui/src/utils/ui.tsx View File

@@ -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);
}
};

/**
* 验证文件上传
*


+ 25
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/config/Oauth2ClientProperties.java View File

@@ -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;
}

+ 75
- 4
ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java View File

@@ -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<String,String> 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<String, String> 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<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<String> 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<String, Object> 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)
{


+ 119
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/form/AccessTokenVo.java View File

@@ -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;
}
}
}

+ 59
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java View File

@@ -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<LoginUser> 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;
}
}

+ 1
- 1
ruoyi-gateway/src/main/resources/bootstrap.yml View File

@@ -3,7 +3,7 @@ server:
port: 8082

# Spring
spring:
spring:
application:
# 应用名称
name: ruoyi-gateway


+ 1
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java View File

@@ -83,7 +83,7 @@ public class DevEnvironmentController extends BaseController {
* @return 删除是否成功
*/
@DeleteMapping("{id}")
public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) {
public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) throws Exception {
return genericsSuccess(this.devEnvironmentService.removeById(id));
}



+ 1
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java View File

@@ -54,5 +54,5 @@ public interface DevEnvironmentService {
*/
boolean deleteById(Integer id);

String removeById(Integer id);
String removeById(Integer id) throws Exception;
}

+ 2
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java View File

@@ -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?"删除成功":"删除失败";
}


+ 8
- 6
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java View File

@@ -63,7 +63,7 @@ public class ImageServiceImpl implements ImageService {
private MinioService minioService;
@Value("${minio.dataReleaseBucketName}")
private String bucketName;
// @Value("${harbor.bucketName}")
// @Value("${harbor.bucketName}")
// private String bucketName;
@Value("${harbor.repository}")
private String repository;
@@ -184,12 +184,14 @@ public class ImageServiceImpl implements ImageService {
// }
List<ImageVersion> 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 +378,10 @@ public class ImageServiceImpl implements ImageService {
if (oldImage != null) {
List<ImageVersion> 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();


+ 1
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ServiceServiceImpl.java View File

@@ -116,6 +116,7 @@ public class ServiceServiceImpl implements ServiceService {
LoginUser loginUser = SecurityUtils.getLoginUser();
serviceVersion.setCreateBy(loginUser.getUsername());
serviceVersion.setUpdateBy(loginUser.getUsername());
serviceVersion.setRunState(Constant.Pending);
serviceDao.insertServiceVersion(serviceVersion);
runServiceVersion(serviceVersion.getId());
return serviceVersion;


+ 23
- 10
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java View File

@@ -12,8 +12,7 @@ import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.AppsV1Api;
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;
@@ -62,9 +61,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异常");
@@ -624,7 +624,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<V1VolumeMount> volumeMounts = new ArrayList<>();
volumeMounts.add(new V1VolumeMount().name("workspace").mountPath(mountPath));

List<V1Volume> 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)
@@ -633,7 +643,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")
@@ -648,9 +660,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 {


Loading…
Cancel
Save