feat(auth): 对接原本项目的单点登录功能(后端回调有问题)
- 新增 SSO 登录相关代码和接口 -重构登录逻辑,支持单点登录 - 添加 token 管理和用户信息存储 - 优化环境变量配置master
parent
dd1cdac9f1
commit
95017c0f91
@ -0,0 +1,5 @@
|
||||
# API基础URL
|
||||
NEXT_PUBLIC_API_BASE_URL=/
|
||||
|
||||
# 开发服务器主机地址
|
||||
NEXT_PUBLIC_DEV_SERVER_HOST=https://p29.ngsk.tech:7001
|
||||
@ -0,0 +1,57 @@
|
||||
import axios from 'axios';
|
||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { Message } from '@arco-design/web-react';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import useUser from '@/hooks/user';
|
||||
|
||||
export interface HttpResponse<T = unknown> {
|
||||
status: number;
|
||||
msg: string;
|
||||
code: number;
|
||||
data: T;
|
||||
}
|
||||
|
||||
if (process.env.NEXT_API_BASE_URL) {
|
||||
axios.defaults.baseURL = process.env.NEXT_API_BASE_URL;
|
||||
}
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
if (!config.headers) {
|
||||
config.headers = {};
|
||||
}
|
||||
// config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
// add response interceptors
|
||||
axios.interceptors.response.use(
|
||||
(response: AxiosResponse<HttpResponse>) => {
|
||||
const res = response.data;
|
||||
if (res.code && res.code !== 200) {
|
||||
Message.error({
|
||||
content: res.msg || 'Error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
if ([-401].includes(res.code)) {
|
||||
const { logout } = useUser();
|
||||
logout();
|
||||
}
|
||||
return Promise.reject(new Error(res.msg || 'Error'));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
error => {
|
||||
Message.error({
|
||||
content: error.msg || '系统异常',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
@ -0,0 +1,225 @@
|
||||
// application
|
||||
export interface CronModel {
|
||||
appId: string;
|
||||
cron: string;
|
||||
}
|
||||
|
||||
export interface applicationModel {
|
||||
id?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
identify?: string;
|
||||
logo?: string;
|
||||
path?: string;
|
||||
published?: boolean | number;
|
||||
sceneId?: string;
|
||||
createBy?: string;
|
||||
createTime?: string | number;
|
||||
updateBy?: string;
|
||||
updateTime?: string | number;
|
||||
compNum?: number;
|
||||
instanceNum?: number;
|
||||
nodeNum?: number;
|
||||
cron?: string;
|
||||
scheduled?: number;
|
||||
createUser?: string;
|
||||
instanceId?: string | number;
|
||||
}
|
||||
|
||||
export interface queryParams {
|
||||
sceneId?: string;
|
||||
resId?: string;
|
||||
name?: string;
|
||||
currPage?: number;
|
||||
pageSize?: number;
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface apiResData {
|
||||
list: applicationModel[];
|
||||
}
|
||||
|
||||
export interface FlowDefinition {
|
||||
id: string;
|
||||
lineConfigs: any[];
|
||||
nodeConfigs: any[];
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface appFlowModel {
|
||||
id?: string;
|
||||
name?: string;
|
||||
compIds?: string[];
|
||||
description?: string;
|
||||
nodeNum?: number;
|
||||
main?: FlowDefinition;
|
||||
subs?: any;
|
||||
}
|
||||
|
||||
export interface publishType {
|
||||
appId?: string;
|
||||
flowId?: string;
|
||||
isMain?: number;
|
||||
name?: string;
|
||||
published?: number | string;
|
||||
tag?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface publishApi {
|
||||
appId: string;
|
||||
needAuthor: number;
|
||||
time?: number;
|
||||
}
|
||||
|
||||
export interface paramsT {
|
||||
code: number;
|
||||
msg: string;
|
||||
data?: any;
|
||||
traceId: string | null;
|
||||
}
|
||||
|
||||
export interface flowType {
|
||||
id: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
// aichat
|
||||
export interface RecommendResponse {
|
||||
code: number;
|
||||
compent_list: string[];
|
||||
}
|
||||
|
||||
// dashboard
|
||||
export interface ContentDataRecord {
|
||||
x: string;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface PopularRecord {
|
||||
key: number;
|
||||
clickNumber: string;
|
||||
title: string;
|
||||
increases: number;
|
||||
}
|
||||
|
||||
// scene
|
||||
export interface monitorCfg {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface sceneModel {
|
||||
id?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
identify?: string;
|
||||
logo?: string;
|
||||
published?: boolean | number;
|
||||
createBy?: string;
|
||||
createTime?: string;
|
||||
updateBy?: string;
|
||||
updateTime?: string;
|
||||
createUser?: string;
|
||||
configUrls?: Array<monitorCfg>;
|
||||
}
|
||||
|
||||
export interface querySceneParams {
|
||||
currPage?: number;
|
||||
name?: string;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface sceneApiResData {
|
||||
list: sceneModel[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
// user
|
||||
export interface LoginRes {
|
||||
token: string;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface authParams {
|
||||
domain: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface getTokenParams extends authParams {
|
||||
authCode: string;
|
||||
callbackUrl: string;
|
||||
}
|
||||
|
||||
export interface getLinkParams {
|
||||
callbackUrl: string;
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
account: string;
|
||||
avatar?: string;
|
||||
realName: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
// message
|
||||
export interface MessageRecord {
|
||||
id: number;
|
||||
type: string;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
avatar?: string;
|
||||
content: string;
|
||||
time: string;
|
||||
status: 0 | 1;
|
||||
messageType?: number;
|
||||
}
|
||||
|
||||
export type MessageListType = MessageRecord[];
|
||||
|
||||
export interface MessageStatus {
|
||||
ids: number[];
|
||||
}
|
||||
|
||||
export interface ChatRecord {
|
||||
id: number;
|
||||
username: string;
|
||||
content: string;
|
||||
time: string;
|
||||
isCollect: boolean;
|
||||
}
|
||||
|
||||
// event
|
||||
export interface AddEventParams {
|
||||
name: string,
|
||||
topic: string,
|
||||
description: string,
|
||||
sceneId: string,
|
||||
}
|
||||
|
||||
export interface DeleteEventParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface DeleteEventForAppParams {
|
||||
appId: string,
|
||||
eventIds: string[]
|
||||
}
|
||||
|
||||
// runtime
|
||||
export interface StepRunEventParams {
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export interface ExecuteCurrentEventParams {
|
||||
appId: string,
|
||||
nodeId: string,
|
||||
socketId: string
|
||||
}
|
||||
|
||||
export interface ReconnectRunParams {
|
||||
instanceId: string,
|
||||
newSocketId: string
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import { md5 } from 'js-md5';
|
||||
import { ssoHost } from '@/utils/env';
|
||||
import { LoginRes } from '@/api/interface';
|
||||
|
||||
export function post(url: string, params: Record<string, any>) {
|
||||
const temp = document.createElement('form');
|
||||
temp.action = url;
|
||||
temp.method = 'post';
|
||||
temp.style.display = 'none';
|
||||
Object.keys(params).forEach(key => {
|
||||
const opt = document.createElement('textarea');
|
||||
opt.name = key;
|
||||
opt.value = params[key];
|
||||
temp.appendChild(opt);
|
||||
});
|
||||
document.body.appendChild(temp);
|
||||
temp.submit();
|
||||
return temp;
|
||||
}
|
||||
|
||||
export const ssoLogin = ({ username, password }: Record<string, any>) => {
|
||||
// 模拟表单提交
|
||||
const ssoUri = `${ssoHost}/api/blade-auth/oauth/form`;
|
||||
return post(ssoUri, {
|
||||
tenant_id: '000000',
|
||||
username,
|
||||
password: md5(password),
|
||||
client_id: 'isdp2',
|
||||
// 统一认证的地址
|
||||
sso_uri: `${ssoHost || window.location.origin}/api/blade-auth`,
|
||||
// 必须加/
|
||||
redirect_uri: `${window.location.origin}/dashboard/workplace`
|
||||
});
|
||||
};
|
||||
|
||||
export const verify = ({ username, password }): Promise<LoginRes> => {
|
||||
return fetch('/api/user-center/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, password: md5(password) })
|
||||
}).then(async (res) => {
|
||||
const data = await res.json();
|
||||
// 登录成功后设置用户状态
|
||||
if (data && res.status === 200) {
|
||||
localStorage.setItem('userStatus', 'login');
|
||||
}
|
||||
return data;
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
import { Message } from '@arco-design/web-react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import axios from 'axios';
|
||||
|
||||
interface UseUserReturnType {
|
||||
logout: () => Promise<void>;
|
||||
}
|
||||
|
||||
export default function useUser(): UseUserReturnType {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
// 调用后端登出接口
|
||||
await axios.post('/api/user/logout');
|
||||
} catch (error) {
|
||||
// 即使后端登出失败,也要清除本地状态
|
||||
console.error('Logout error:', error);
|
||||
} finally {
|
||||
// 清除用户信息状态
|
||||
dispatch({
|
||||
type: 'update-userInfo',
|
||||
payload: {
|
||||
userInfo: { permissions: {} },
|
||||
userLoading: false
|
||||
}
|
||||
});
|
||||
|
||||
// 清除本地存储的用户状态
|
||||
localStorage.removeItem('userStatus');
|
||||
|
||||
// 显示成功消息
|
||||
Message.success('退出成功');
|
||||
|
||||
// 重定向到登录页面
|
||||
const { protocol, host } = window.location;
|
||||
window.location.href = `${protocol}//${host}/login`;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
logout
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
const TOKEN_KEY = 'token';
|
||||
const ISDP_KEY = 'saber-token';
|
||||
|
||||
const isLogin = () => {
|
||||
return !!localStorage.getItem(TOKEN_KEY);
|
||||
};
|
||||
|
||||
const getToken = () => {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
};
|
||||
|
||||
const setToken = (token: string) => {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
const obj = {
|
||||
dataType: typeof token,
|
||||
content: token,
|
||||
datetime: new Date().getTime()
|
||||
};
|
||||
localStorage.setItem(ISDP_KEY, JSON.stringify(obj));
|
||||
document.cookie = `saber-access-token=${token};`;
|
||||
};
|
||||
|
||||
const clearToken = () => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(ISDP_KEY);
|
||||
};
|
||||
|
||||
const USER_INFO_KEY = 'userInfo';
|
||||
export const setSessionUserInfo = data => {
|
||||
sessionStorage.setItem(USER_INFO_KEY, JSON.stringify(data));
|
||||
};
|
||||
export const getSessionUserInfo = () => {
|
||||
const data = sessionStorage.getItem(USER_INFO_KEY);
|
||||
return data ? JSON.parse(data) : null;
|
||||
};
|
||||
|
||||
export { isLogin, getToken, setToken, clearToken };
|
||||
@ -0,0 +1,5 @@
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
|
||||
// 使用NEXT_PUBLIC_前缀的环境变量才能在客户端访问
|
||||
export const ssoHost = debug ? process.env.NEXT_PUBLIC_DEV_SERVER_HOST : '';
|
||||
export default debug;
|
||||
Loading…
Reference in New Issue