feat(auth): 对接原本项目的单点登录功能(后端回调有问题)
- 新增 SSO 登录相关代码和接口 -重构登录逻辑,支持单点登录 - 添加 token 管理和用户信息存储 - 优化环境变量配置production
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