jdk 17
This commit is contained in:
60
sdk/frontend/unified-login-sdk/.gitignore
vendored
Normal file
60
sdk/frontend/unified-login-sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
# IDE-specific files
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
.vscode/
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
# SonarQube
|
||||
.sonar/
|
||||
|
||||
# Test coverage
|
||||
jacoco.exec
|
||||
119
sdk/frontend/unified-login-sdk/README.md
Normal file
119
sdk/frontend/unified-login-sdk/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Unified Login SDK
|
||||
|
||||
统一登录前端SDK,基于OAuth2协议实现前后端分离项目的认证和权限管理。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
npm install unified-login-sdk
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基础使用
|
||||
|
||||
```javascript
|
||||
import unifiedLoginSDK from 'unified-login-sdk';
|
||||
|
||||
// 初始化配置
|
||||
unifiedLoginSDK.init({
|
||||
clientId: 'your-client-id',
|
||||
basepath: 'https://api.example.com',
|
||||
homePage: '/dashboard',
|
||||
idpLogoutUrl: 'https://idp.example.com/logout'
|
||||
});
|
||||
|
||||
// 检查登录状态
|
||||
if (!unifiedLoginSDK.isAuthenticated()) {
|
||||
// 执行登录
|
||||
await unifiedLoginSDK.login();
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const userInfo = unifiedLoginSDK.getUserInfo();
|
||||
console.log('欢迎:', userInfo.nickName);
|
||||
```
|
||||
|
||||
### Vue 3 集成
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import { createApp } from 'vue';
|
||||
import { createVuePlugin } from 'unified-login-sdk';
|
||||
|
||||
const app = createApp(App);
|
||||
const loginPlugin = createVuePlugin('localStorage');
|
||||
|
||||
app.use(loginPlugin, {
|
||||
config: {
|
||||
clientId: 'your-client-id',
|
||||
basepath: 'https://api.example.com',
|
||||
homePage: '/dashboard',
|
||||
idpLogoutUrl: 'https://idp.example.com/logout'
|
||||
}
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
```
|
||||
|
||||
### 权限检查
|
||||
|
||||
```javascript
|
||||
// 检查单个权限
|
||||
const hasPermission = await unifiedLoginSDK.hasPermission('user:read');
|
||||
|
||||
// 检查多个权限
|
||||
const hasAllPermissions = await unifiedLoginSDK.hasAllPermissions(['user:read', 'user:write']);
|
||||
|
||||
// 检查角色
|
||||
const hasRole = await unifiedLoginSDK.hasRole('admin');
|
||||
```
|
||||
|
||||
## 核心功能
|
||||
|
||||
- ✅ OAuth2认证流程
|
||||
- ✅ Token自动管理
|
||||
- ✅ 用户信息获取
|
||||
- ✅ 权限和角色检查
|
||||
- ✅ Vue 2/3插件支持
|
||||
- ✅ 路由守卫集成
|
||||
- ✅ 事件监听机制
|
||||
|
||||
## API参考
|
||||
|
||||
### 主要方法
|
||||
|
||||
| 方法 | 说明 | 参数 | 返回值 |
|
||||
|------|------|------|--------|
|
||||
| `init(config)` | 初始化SDK | 配置对象 | void |
|
||||
| `login()` | 执行登录 | redirectUri(可选) | Promise<void> |
|
||||
| `logout()` | 退出登录 | 无 | Promise<void> |
|
||||
| `isAuthenticated()` | 检查认证状态 | 无 | boolean |
|
||||
| `getUserInfo()` | 获取用户信息 | 无 | UserInfo |
|
||||
| `hasPermission(permission)` | 检查权限 | 权限标识 | Promise<boolean> |
|
||||
| `hasRole(role)` | 检查角色 | 角色标识 | Promise<boolean> |
|
||||
|
||||
### 事件监听
|
||||
|
||||
```javascript
|
||||
// 登录事件
|
||||
unifiedLoginSDK.on('login', () => {
|
||||
console.log('用户已登录');
|
||||
});
|
||||
|
||||
// 退出事件
|
||||
unifiedLoginSDK.on('logout', () => {
|
||||
console.log('用户已退出');
|
||||
});
|
||||
```
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
- Chrome (推荐)
|
||||
- Firefox
|
||||
- Safari
|
||||
- Edge
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
101
sdk/frontend/unified-login-sdk/dist/core/auth.d.ts
vendored
Normal file
101
sdk/frontend/unified-login-sdk/dist/core/auth.d.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 认证核心逻辑
|
||||
* 实现OAuth2授权码模式的完整流程
|
||||
*/
|
||||
import { EventType, RouterInfo, SDKConfig, UserInfo } from '../types';
|
||||
import { Storage } from '../utils/storage';
|
||||
/**
|
||||
* 认证核心类
|
||||
*/
|
||||
export declare class Auth {
|
||||
private config;
|
||||
private tokenManager;
|
||||
private httpClient;
|
||||
private storage;
|
||||
private eventHandlers;
|
||||
private userInfoCache;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage);
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: SDKConfig): void;
|
||||
getToken(): string | null;
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
login(redirectUri?: string): Promise<void>;
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
logout(): Promise<void>;
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
handleCallback(): Promise<void>;
|
||||
getRoutes(): Promise<RouterInfo>;
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo 用户信息
|
||||
*/
|
||||
getUserInfo(): UserInfo;
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
hasRole(role: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
hasAllRoles(roles: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
hasPermission(permission: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
hasAllPermissions(permissions: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean;
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: EventType, callback: Function): void;
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: EventType, callback: Function): void;
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
private emit;
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean;
|
||||
}
|
||||
//# sourceMappingURL=auth.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/core/auth.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/core/auth.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAC,MAAM,UAAU,CAAC;AAGpE,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AAGzC;;GAEG;AACH,qBAAa,IAAI;IACf,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,aAAa,CAInB;IACF,OAAO,CAAC,aAAa,CAAyB;IAE9C;;;OAGG;gBACS,OAAO,EAAE,OAAO;IAQ5B;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAM7B,QAAQ,IAAG,MAAM,GAAG,IAAI;IAIxB;;;OAGG;IACG,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAchD;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC/B,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC;IAYtC;;;OAGG;IACF,WAAW,IAAI,QAAQ;IAOxB;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBxD;;;;OAIG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAWpD;;;;OAIG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBpE;;;;OAIG;IACG,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAYhE;;;OAGG;IACH,eAAe,IAAI,OAAO;IAK1B;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAI9C;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAI/C;;;;OAIG;IACH,OAAO,CAAC,IAAI;IAUZ;;;OAGG;IACH,UAAU,IAAI,OAAO;CAGtB"}
|
||||
241
sdk/frontend/unified-login-sdk/dist/core/auth.js
vendored
Normal file
241
sdk/frontend/unified-login-sdk/dist/core/auth.js
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* 认证核心逻辑
|
||||
* 实现OAuth2授权码模式的完整流程
|
||||
*/
|
||||
import { TokenManager } from './token';
|
||||
import { HttpClient } from './http';
|
||||
import { buildQueryParams, isCallbackUrl, parseQueryParams } from '../utils/url';
|
||||
/**
|
||||
* 认证核心类
|
||||
*/
|
||||
export class Auth {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage) {
|
||||
this.config = null;
|
||||
this.eventHandlers = {
|
||||
login: [],
|
||||
logout: [],
|
||||
tokenExpired: []
|
||||
};
|
||||
this.userInfoCache = null;
|
||||
this.storage = storage;
|
||||
// 先创建HttpClient,初始时tokenManager为undefined
|
||||
this.httpClient = new HttpClient(() => this.tokenManager.getToken() || null);
|
||||
// 然后创建TokenManager
|
||||
this.tokenManager = new TokenManager(storage);
|
||||
}
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config) {
|
||||
this.config = config;
|
||||
// 设置租户ID到HTTP客户端
|
||||
this.httpClient.setTenantId(config.tenantId);
|
||||
}
|
||||
getToken() {
|
||||
return this.tokenManager.getToken();
|
||||
}
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
async login(redirectUri) {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const registrationId = this.config.registrationId || 'idp';
|
||||
const basepath = this.config.basepath || '';
|
||||
const path = `${basepath}/oauth2/authorization/${registrationId}`;
|
||||
const tokenResponse = await this.httpClient.get(path, { needAuth: false });
|
||||
const redirect = tokenResponse.data.redirect_url;
|
||||
const params = parseQueryParams(redirect);
|
||||
this.storage.set(params.state, window.location.href);
|
||||
window.location.href = redirect;
|
||||
}
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
async logout() {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
// 清除本地存储的Token和用户信息
|
||||
this.tokenManager.clearToken();
|
||||
this.userInfoCache = null;
|
||||
this.storage.remove('userInfo');
|
||||
const basepath = this.config.basepath || '';
|
||||
await this.httpClient.post(`${basepath}/logout`, null, { needAuth: true });
|
||||
// 触发退出事件
|
||||
this.emit('logout');
|
||||
window.location.href = this.config.idpLogoutUrl + '?redirect=' + this.config.homePage;
|
||||
}
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
async handleCallback() {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const params = parseQueryParams();
|
||||
// 检查是否有错误
|
||||
if (params.error) {
|
||||
throw new Error(`Authorization error: ${params.error} - ${params.error_description || ''}`);
|
||||
}
|
||||
// 检查是否有授权码
|
||||
if (!params.code) {
|
||||
throw new Error('Authorization code not found');
|
||||
}
|
||||
const registrationId = this.config.registrationId || 'idp';
|
||||
const basepath = this.config.basepath || '';
|
||||
const callback = `${basepath}/login/oauth2/code/${registrationId}${buildQueryParams(params)}`;
|
||||
const tokenResponse = await this.httpClient.get(callback, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
needAuth: false
|
||||
});
|
||||
// 触发登录事件
|
||||
this.emit('login');
|
||||
this.storage.set('userInfo', tokenResponse.data.data);
|
||||
this.tokenManager.saveToken(tokenResponse.headers['authorization'] || tokenResponse.headers['Authorization']);
|
||||
let url = this.config.homePage;
|
||||
if (params.state) {
|
||||
url = this.storage.get(params.state) || url;
|
||||
}
|
||||
window.location.href = url;
|
||||
}
|
||||
async getRoutes() {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const basepath = this.config.basepath || '';
|
||||
const tokenResponse = await this.httpClient.get(`${basepath}/idp/routes`, { needAuth: true });
|
||||
if (tokenResponse.status === 401) {
|
||||
await this.logout();
|
||||
}
|
||||
return tokenResponse.data.data;
|
||||
}
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo 用户信息
|
||||
*/
|
||||
getUserInfo() {
|
||||
return this.storage.get("userInfo");
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
async hasRole(role) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const roleCodes = userInfo.roles || [];
|
||||
if (Array.isArray(role)) {
|
||||
// 检查是否有任一角色
|
||||
return role.some(r => roleCodes.includes(r));
|
||||
}
|
||||
// 检查是否有单个角色
|
||||
return roleCodes.includes(role);
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
async hasAllRoles(roles) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const roleCodes = userInfo.roles || [];
|
||||
// 检查是否有所有角色
|
||||
return roles.every(r => roleCodes.includes(r));
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
async hasPermission(permission) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const permissions = userInfo.permissions || [];
|
||||
if (Array.isArray(permission)) {
|
||||
// 检查是否有任一权限
|
||||
return permission.some(p => permissions.includes(p));
|
||||
}
|
||||
// 检查是否有单个权限
|
||||
return permissions.includes(permission);
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
async hasAllPermissions(permissions) {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
const userInfo = this.storage.get("userInfo");
|
||||
const userPermissions = userInfo.permissions || [];
|
||||
// 检查是否有所有权限
|
||||
return permissions.every(p => userPermissions.includes(p));
|
||||
}
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated() {
|
||||
// 检查Token是否存在且未过期
|
||||
return !!this.tokenManager.getToken();
|
||||
}
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event, callback) {
|
||||
this.eventHandlers[event].push(callback);
|
||||
}
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event, callback) {
|
||||
this.eventHandlers[event] = this.eventHandlers[event].filter(handler => handler !== callback);
|
||||
}
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
emit(event, data) {
|
||||
this.eventHandlers[event].forEach(handler => {
|
||||
try {
|
||||
handler(data);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error in ${event} event handler:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback() {
|
||||
return isCallbackUrl();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=auth.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/core/auth.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/core/auth.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
155
sdk/frontend/unified-login-sdk/dist/core/http.d.ts
vendored
Normal file
155
sdk/frontend/unified-login-sdk/dist/core/http.d.ts
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* HTTP客户端
|
||||
* 用于与后端API进行通信
|
||||
*/
|
||||
/**
|
||||
* HTTP请求方法类型
|
||||
*/
|
||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
/**
|
||||
* HTTP请求选项
|
||||
*/
|
||||
export interface HttpRequestOptions {
|
||||
/** 请求方法 */
|
||||
method: HttpMethod;
|
||||
/** 请求URL */
|
||||
url: string;
|
||||
/** 请求头 */
|
||||
headers?: Record<string, string>;
|
||||
/** 请求体 */
|
||||
body?: any;
|
||||
/** 是否需要认证 */
|
||||
needAuth?: boolean;
|
||||
}
|
||||
/**
|
||||
* HTTP响应类型
|
||||
*/
|
||||
export interface HttpResponse<T = any> {
|
||||
/** 状态码 */
|
||||
status: number;
|
||||
/** 状态文本 */
|
||||
statusText: string;
|
||||
/** 响应体 */
|
||||
data: T;
|
||||
/** 响应头 */
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
/**
|
||||
* HTTP错误类型
|
||||
*/
|
||||
export declare class HttpError extends Error {
|
||||
/** 状态码 */
|
||||
status: number;
|
||||
/** 状态文本 */
|
||||
statusText: string;
|
||||
/** 错误数据 */
|
||||
data: any;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 错误信息
|
||||
* @param status 状态码
|
||||
* @param statusText 状态文本
|
||||
* @param data 错误数据
|
||||
*/
|
||||
constructor(message: string, status: number, statusText: string, data: any);
|
||||
}
|
||||
/**
|
||||
* HTTP客户端类
|
||||
*/
|
||||
export declare class HttpClient {
|
||||
private tokenGetter?;
|
||||
private tenantId?;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param logout
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
constructor(tokenGetter?: () => string | null);
|
||||
/**
|
||||
* 设置Token获取函数
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
setTokenGetter(tokenGetter: () => string | null): void;
|
||||
/**
|
||||
* 设置租户ID
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
setTenantId(tenantId?: string): void;
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* 处理响应数据
|
||||
* @param response 响应对象
|
||||
* @param responseData 响应数据
|
||||
* @returns HttpResponse<T> 处理后的响应
|
||||
*/
|
||||
private handleResponse;
|
||||
/**
|
||||
* 检查是否为业务响应结构
|
||||
* @param responseData 响应数据
|
||||
* @returns boolean 是否为业务响应结构
|
||||
*/
|
||||
private isBusinessResponse;
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @param responseData 响应数据
|
||||
* @returns string 错误信息
|
||||
*/
|
||||
private getErrorMessage;
|
||||
/**
|
||||
* GET请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
get<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* POST请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
post<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* PUT请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
put<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* DELETE请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
delete<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* PATCH请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
patch<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>>;
|
||||
/**
|
||||
* 解析响应体
|
||||
* @param response 响应对象
|
||||
* @returns Promise<any> 解析后的响应体
|
||||
*/
|
||||
private parseResponse;
|
||||
/**
|
||||
* 解析响应头
|
||||
* @param headers 响应头对象
|
||||
* @returns Record<string, string> 解析后的响应头
|
||||
*/
|
||||
private parseHeaders;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=http.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/core/http.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/core/http.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,WAAW;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU;IACV,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU;IACV,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,aAAa;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACnC,UAAU;IACV,MAAM,EAAE,MAAM,CAAC;IACf,WAAW;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU;IACV,IAAI,EAAE,CAAC,CAAC;IACR,UAAU;IACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,UAAU;IACH,MAAM,EAAE,MAAM,CAAC;IACtB,WAAW;IACJ,UAAU,EAAE,MAAM,CAAC;IAC1B,WAAW;IACJ,IAAI,EAAE,GAAG,CAAC;IAEjB;;;;;;OAMG;gBACS,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAO3E;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAAC,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAAC,CAAS;IAE1B;;;;OAIG;gBACS,WAAW,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI;IAK7C;;;OAGG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI;IAItD;;;OAGG;IACH,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAIpC;;;;OAIG;IACG,OAAO,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IA0F7E;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAmCtB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;;OAKG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAQ/G;;;;;;OAMG;IACG,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IASrI;;;;;;OAMG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IASpI;;;;;OAKG;IACG,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAQlH;;;;;;OAMG;IACG,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAStI;;;;OAIG;YACW,aAAa;IAY3B;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAOrB"}
|
||||
274
sdk/frontend/unified-login-sdk/dist/core/http.js
vendored
Normal file
274
sdk/frontend/unified-login-sdk/dist/core/http.js
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* HTTP客户端
|
||||
* 用于与后端API进行通信
|
||||
*/
|
||||
/**
|
||||
* HTTP错误类型
|
||||
*/
|
||||
export class HttpError extends Error {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 错误信息
|
||||
* @param status 状态码
|
||||
* @param statusText 状态文本
|
||||
* @param data 错误数据
|
||||
*/
|
||||
constructor(message, status, statusText, data) {
|
||||
super(message);
|
||||
this.name = 'HttpError';
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* HTTP客户端类
|
||||
*/
|
||||
export class HttpClient {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param logout
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
constructor(tokenGetter) {
|
||||
this.tokenGetter = tokenGetter;
|
||||
}
|
||||
/**
|
||||
* 设置Token获取函数
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
setTokenGetter(tokenGetter) {
|
||||
this.tokenGetter = tokenGetter;
|
||||
}
|
||||
/**
|
||||
* 设置租户ID
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
setTenantId(tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async request(options) {
|
||||
const { method, url, headers = {}, body, needAuth = true } = options;
|
||||
// 构建请求头
|
||||
const requestHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
};
|
||||
// 添加认证头
|
||||
const addAuthHeader = () => {
|
||||
if (needAuth && this.tokenGetter) {
|
||||
const token = this.tokenGetter();
|
||||
if (token) {
|
||||
requestHeaders.Authorization = `${token}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 添加租户ID头
|
||||
if (this.tenantId) {
|
||||
requestHeaders['tenant-id'] = this.tenantId;
|
||||
}
|
||||
addAuthHeader();
|
||||
// 构建请求配置
|
||||
const fetchOptions = {
|
||||
method,
|
||||
headers: requestHeaders,
|
||||
credentials: 'include' // 包含cookie
|
||||
};
|
||||
// 添加请求体
|
||||
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
||||
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
}
|
||||
try {
|
||||
// 发送请求
|
||||
const response = await fetch(url, fetchOptions);
|
||||
const responseData = await this.parseResponse(response);
|
||||
// 检查响应状态
|
||||
if (!response.ok) {
|
||||
// 如果是401错误,尝试刷新Token并重试
|
||||
if (response.status === 401) {
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: '',
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
// 其他错误,直接抛出
|
||||
const errorMsg = this.getErrorMessage(responseData);
|
||||
throw new HttpError(errorMsg, response.status, response.statusText, responseData);
|
||||
}
|
||||
// 处理成功响应的业务逻辑
|
||||
return this.handleResponse(response, responseData);
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
throw error;
|
||||
}
|
||||
// 网络错误或其他错误
|
||||
throw new HttpError(error instanceof Error ? error.message : 'Network Error', 0, 'Network Error', null);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理响应数据
|
||||
* @param response 响应对象
|
||||
* @param responseData 响应数据
|
||||
* @returns HttpResponse<T> 处理后的响应
|
||||
*/
|
||||
handleResponse(response, responseData) {
|
||||
// 检查是否为业务响应结构
|
||||
if (this.isBusinessResponse(responseData)) {
|
||||
// 业务响应结构:{ code, msg, data }
|
||||
const { code, msg, data } = responseData;
|
||||
// 检查业务状态码
|
||||
if (code !== 0 && code !== 200 && code !== '0' && code !== '200') {
|
||||
// 业务错误,抛出HttpError
|
||||
throw new HttpError(msg || `Business Error: ${code}`, response.status, response.statusText, responseData);
|
||||
}
|
||||
// 业务成功,返回data字段作为实际数据
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: data,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
// 非业务响应结构,直接返回原始数据
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: responseData,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 检查是否为业务响应结构
|
||||
* @param responseData 响应数据
|
||||
* @returns boolean 是否为业务响应结构
|
||||
*/
|
||||
isBusinessResponse(responseData) {
|
||||
return typeof responseData === 'object' &&
|
||||
responseData !== null &&
|
||||
('code' in responseData) &&
|
||||
('msg' in responseData) &&
|
||||
('data' in responseData);
|
||||
}
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @param responseData 响应数据
|
||||
* @returns string 错误信息
|
||||
*/
|
||||
getErrorMessage(responseData) {
|
||||
// 如果是业务响应结构
|
||||
if (this.isBusinessResponse(responseData)) {
|
||||
return responseData.msg || `Business Error: ${responseData.code}`;
|
||||
}
|
||||
// 其他错误结构
|
||||
return responseData.message || responseData.error || `HTTP Error`;
|
||||
}
|
||||
/**
|
||||
* GET请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async get(url, options) {
|
||||
return this.request({
|
||||
method: 'GET',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* POST请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async post(url, body, options) {
|
||||
return this.request({
|
||||
method: 'POST',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* PUT请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async put(url, body, options) {
|
||||
return this.request({
|
||||
method: 'PUT',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* DELETE请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async delete(url, options) {
|
||||
return this.request({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* PATCH请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async patch(url, body, options) {
|
||||
return this.request({
|
||||
method: 'PATCH',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 解析响应体
|
||||
* @param response 响应对象
|
||||
* @returns Promise<any> 解析后的响应体
|
||||
*/
|
||||
async parseResponse(response) {
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (contentType.includes('application/json')) {
|
||||
return response.json();
|
||||
}
|
||||
else if (contentType.includes('text/')) {
|
||||
return response.text();
|
||||
}
|
||||
else {
|
||||
return response.blob();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 解析响应头
|
||||
* @param headers 响应头对象
|
||||
* @returns Record<string, string> 解析后的响应头
|
||||
*/
|
||||
parseHeaders(headers) {
|
||||
const result = {};
|
||||
headers.forEach((value, key) => {
|
||||
result[key] = value;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=http.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/core/http.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/core/http.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
32
sdk/frontend/unified-login-sdk/dist/core/token.d.ts
vendored
Normal file
32
sdk/frontend/unified-login-sdk/dist/core/token.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Token管理模块
|
||||
* 负责Token的存储、获取、刷新和过期处理
|
||||
*/
|
||||
import { Storage } from '../utils/storage';
|
||||
/**
|
||||
* Token管理类
|
||||
*/
|
||||
export declare class TokenManager {
|
||||
private storage;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
* @param httpClient HTTP客户端实例
|
||||
*/
|
||||
constructor(storage: Storage);
|
||||
/**
|
||||
* 存储Token信息
|
||||
* @param tokenInfo Token信息
|
||||
*/
|
||||
saveToken(tokenInfo: string): void;
|
||||
/**
|
||||
* 获取Token信息
|
||||
* @returns TokenInfo | null Token信息
|
||||
*/
|
||||
getToken(): string | null;
|
||||
/**
|
||||
* 清除Token信息
|
||||
*/
|
||||
clearToken(): void;
|
||||
}
|
||||
//# sourceMappingURL=token.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/core/token.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/core/token.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/core/token.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAU;IAEzB;;;;OAIG;gBACS,OAAO,EAAE,OAAO;IAI5B;;;OAGG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIlC;;;OAGG;IACH,QAAQ,IAAI,MAAM,GAAG,IAAI;IAIzB;;OAEG;IACH,UAAU,IAAI,IAAI;CAGnB"}
|
||||
38
sdk/frontend/unified-login-sdk/dist/core/token.js
vendored
Normal file
38
sdk/frontend/unified-login-sdk/dist/core/token.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Token管理模块
|
||||
* 负责Token的存储、获取、刷新和过期处理
|
||||
*/
|
||||
/**
|
||||
* Token管理类
|
||||
*/
|
||||
export class TokenManager {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
* @param httpClient HTTP客户端实例
|
||||
*/
|
||||
constructor(storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
/**
|
||||
* 存储Token信息
|
||||
* @param tokenInfo Token信息
|
||||
*/
|
||||
saveToken(tokenInfo) {
|
||||
this.storage.set('token', tokenInfo);
|
||||
}
|
||||
/**
|
||||
* 获取Token信息
|
||||
* @returns TokenInfo | null Token信息
|
||||
*/
|
||||
getToken() {
|
||||
return this.storage.get('token');
|
||||
}
|
||||
/**
|
||||
* 清除Token信息
|
||||
*/
|
||||
clearToken() {
|
||||
this.storage.remove('token');
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=token.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/core/token.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/core/token.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/core/token.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,OAAO,YAAY;IAGvB;;;;OAIG;IACH,YAAY,OAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,SAAiB;QACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;CACF"}
|
||||
55
sdk/frontend/unified-login-sdk/dist/guards/router.d.ts
vendored
Normal file
55
sdk/frontend/unified-login-sdk/dist/guards/router.d.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 路由守卫模块
|
||||
* 提供基于权限的路由拦截和未登录自动跳转登录页功能
|
||||
*/
|
||||
import { Auth } from '../core/auth';
|
||||
/**
|
||||
* 路由守卫选项
|
||||
*/
|
||||
export interface RouterGuardOptions {
|
||||
/**
|
||||
* 是否需要登录
|
||||
*/
|
||||
requiresAuth?: boolean;
|
||||
/**
|
||||
* 需要的权限列表
|
||||
*/
|
||||
requiredPermissions?: string[];
|
||||
/**
|
||||
* 登录后重定向的URL
|
||||
*/
|
||||
redirectUri?: string;
|
||||
/**
|
||||
* 权限不足时重定向的URL
|
||||
*/
|
||||
unauthorizedRedirectUri?: string;
|
||||
}
|
||||
/**
|
||||
* 路由守卫类
|
||||
*/
|
||||
export declare class RouterGuard {
|
||||
private auth;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param auth 认证实例
|
||||
*/
|
||||
constructor(auth: Auth);
|
||||
/**
|
||||
* 检查路由权限
|
||||
* @param options 路由守卫选项
|
||||
* @returns Promise<boolean> 是否通过权限检查
|
||||
*/
|
||||
check(options: RouterGuardOptions): Promise<boolean>;
|
||||
/**
|
||||
* 创建Vue路由守卫
|
||||
* @returns 路由守卫函数
|
||||
*/
|
||||
createVueGuard(): (to: any, from: any, next: any) => Promise<void>;
|
||||
/**
|
||||
* 检查当前用户是否有权限访问资源
|
||||
* @param permissions 需要的权限列表
|
||||
* @returns Promise<boolean> 是否拥有权限
|
||||
*/
|
||||
hasPermission(permissions: string | string[]): Promise<boolean>;
|
||||
}
|
||||
//# sourceMappingURL=router.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/guards/router.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/guards/router.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/guards/router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAO;IAEnB;;;OAGG;gBACS,IAAI,EAAE,IAAI;IAItB;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAmC1D;;;OAGG;IACH,cAAc,KACE,IAAI,GAAG,EAAE,MAAM,GAAG,EAAE,MAAM,GAAG;IAgB7C;;;;OAIG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAoBtE"}
|
||||
89
sdk/frontend/unified-login-sdk/dist/guards/router.js
vendored
Normal file
89
sdk/frontend/unified-login-sdk/dist/guards/router.js
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 路由守卫模块
|
||||
* 提供基于权限的路由拦截和未登录自动跳转登录页功能
|
||||
*/
|
||||
/**
|
||||
* 路由守卫类
|
||||
*/
|
||||
export class RouterGuard {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param auth 认证实例
|
||||
*/
|
||||
constructor(auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
/**
|
||||
* 检查路由权限
|
||||
* @param options 路由守卫选项
|
||||
* @returns Promise<boolean> 是否通过权限检查
|
||||
*/
|
||||
async check(options) {
|
||||
const { requiresAuth = true, requiredPermissions = [] } = options;
|
||||
// 检查是否需要登录
|
||||
if (requiresAuth) {
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
// 未认证,跳转到登录页
|
||||
this.auth.login(options.redirectUri);
|
||||
return false;
|
||||
}
|
||||
// 检查是否需要权限
|
||||
if (requiredPermissions.length > 0) {
|
||||
// 获取用户权限
|
||||
const userPermissions = [''];
|
||||
// 检查是否拥有所有需要的权限
|
||||
const hasPermission = requiredPermissions.every(permission => userPermissions.includes(permission));
|
||||
if (!hasPermission) {
|
||||
// 权限不足,跳转到权限不足页
|
||||
if (options.unauthorizedRedirectUri) {
|
||||
window.location.href = options.unauthorizedRedirectUri;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 创建Vue路由守卫
|
||||
* @returns 路由守卫函数
|
||||
*/
|
||||
createVueGuard() {
|
||||
return async (to, from, next) => {
|
||||
var _a;
|
||||
// 从路由元信息中获取守卫选项
|
||||
const options = ((_a = to.meta) === null || _a === void 0 ? void 0 : _a.auth) || {};
|
||||
try {
|
||||
const allowed = await this.check(options);
|
||||
if (allowed) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Route guard error:', error);
|
||||
next(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 检查当前用户是否有权限访问资源
|
||||
* @param permissions 需要的权限列表
|
||||
* @returns Promise<boolean> 是否拥有权限
|
||||
*/
|
||||
async hasPermission(permissions) {
|
||||
if (!permissions) {
|
||||
return true;
|
||||
}
|
||||
const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
// 获取用户权限
|
||||
const userPermissions = [''];
|
||||
// 检查是否拥有所有需要的权限
|
||||
return requiredPermissions.every(permission => userPermissions.includes(permission));
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=router.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/guards/router.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/guards/router.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/guards/router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA0BH;;GAEG;AACH,MAAM,OAAO,WAAW;IAGtB;;;OAGG;IACH,YAAY,IAAU;QACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,OAA2B;QACrC,MAAM,EAAE,YAAY,GAAG,IAAI,EAAE,mBAAmB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;QAElE,WAAW;QACX,IAAI,YAAY,EAAE,CAAC;YACjB,UAAU;YACV,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;gBACjC,aAAa;gBACb,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBACrC,OAAO,KAAK,CAAC;YACf,CAAC;YAED,WAAW;YACX,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnC,SAAS;gBACT,MAAM,eAAe,GAAG,CAAC,EAAE,CAAC,CAAC;gBAE7B,gBAAgB;gBAChB,MAAM,aAAa,GAAG,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAC3D,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,CACrC,CAAC;gBAEF,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,gBAAgB;oBAChB,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;wBACpC,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,uBAAuB,CAAC;oBACzD,CAAC;oBACD,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,OAAO,KAAK,EAAE,EAAO,EAAE,IAAS,EAAE,IAAS,EAAE,EAAE;;YAC7C,gBAAgB;YAChB,MAAM,OAAO,GAAuB,CAAA,MAAA,EAAE,CAAC,IAAI,0CAAE,IAAI,KAAI,EAAE,CAAC;YAExD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC1C,IAAI,OAAO,EAAE,CAAC;oBACZ,IAAI,EAAE,CAAC;gBACT,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;gBAC3C,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,aAAa,CAAC,WAA8B;QAChD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,mBAAmB,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QAErF,UAAU;QACV,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,SAAS;QACT,MAAM,eAAe,GAAG,CAAC,EAAE,CAAC,CAAA;QAE5B,gBAAgB;QAChB,OAAO,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAC5C,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,CACrC,CAAC;IACJ,CAAC;CACF"}
|
||||
20
sdk/frontend/unified-login-sdk/dist/index.d.ts
vendored
Normal file
20
sdk/frontend/unified-login-sdk/dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 统一登录SDK入口文件
|
||||
* 支持OAuth2授权码模式,提供完整的Token管理和用户信息管理功能
|
||||
*/
|
||||
export { Auth } from './core/auth';
|
||||
export { TokenManager } from './core/token';
|
||||
export { HttpClient, HttpError } from './core/http';
|
||||
export { Storage } from './utils/storage';
|
||||
export { RouterGuard, RouterGuardOptions } from './guards/router';
|
||||
export { generateRandomString, parseQueryParams, buildQueryParams, generateAuthorizationUrl, isCallbackUrl } from './utils/url';
|
||||
export * from './types';
|
||||
export { VuePlugin, createVuePlugin } from './plugins/vue';
|
||||
import { UnifiedLoginSDK } from './types';
|
||||
/**
|
||||
* 默认导出的SDK实例
|
||||
*/
|
||||
export declare const unifiedLoginSDK: UnifiedLoginSDK;
|
||||
export default unifiedLoginSDK;
|
||||
export declare const version = "1.0.0";
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/index.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/index.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,aAAa,EACd,MAAM,aAAa,CAAC;AAGrB,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAG3D,OAAO,EAAa,eAAe,EAAE,MAAM,SAAS,CAAC;AAUrD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,eA8C7B,CAAC;AAGF,eAAe,eAAe,CAAC;AAG/B,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
||||
1192
sdk/frontend/unified-login-sdk/dist/index.esm.js
vendored
Normal file
1192
sdk/frontend/unified-login-sdk/dist/index.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
sdk/frontend/unified-login-sdk/dist/index.esm.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/index.esm.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1211
sdk/frontend/unified-login-sdk/dist/index.js
vendored
Normal file
1211
sdk/frontend/unified-login-sdk/dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
sdk/frontend/unified-login-sdk/dist/index.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
56
sdk/frontend/unified-login-sdk/dist/plugins/vue.d.ts
vendored
Normal file
56
sdk/frontend/unified-login-sdk/dist/plugins/vue.d.ts
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Vue插件模块
|
||||
* 提供Vue应用中使用统一登录SDK的能力
|
||||
*/
|
||||
import { Auth } from '../core/auth';
|
||||
import { SDKConfig } from '../types';
|
||||
import { Storage } from '../utils/storage';
|
||||
import { RouterGuard } from '../guards/router';
|
||||
/**
|
||||
* Vue插件选项
|
||||
*/
|
||||
export interface VuePluginOptions {
|
||||
/**
|
||||
* SDK配置
|
||||
*/
|
||||
config: SDKConfig;
|
||||
/**
|
||||
* 插件名称,默认'unifiedLogin'
|
||||
*/
|
||||
pluginName?: string;
|
||||
}
|
||||
/**
|
||||
* Vue插件类
|
||||
*/
|
||||
export declare class VuePlugin {
|
||||
private auth;
|
||||
private routerGuard;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage);
|
||||
/**
|
||||
* 安装Vue插件
|
||||
* @param app Vue构造函数或Vue 3应用实例
|
||||
* @param options 插件选项
|
||||
*/
|
||||
install(app: any, options: VuePluginOptions): void;
|
||||
/**
|
||||
* 获取认证实例
|
||||
* @returns Auth 认证实例
|
||||
*/
|
||||
getAuth(): Auth;
|
||||
/**
|
||||
* 获取路由守卫实例
|
||||
* @returns RouterGuard 路由守卫实例
|
||||
*/
|
||||
getRouterGuard(): RouterGuard;
|
||||
}
|
||||
/**
|
||||
* 创建Vue插件实例
|
||||
* @param storageType 存储类型
|
||||
* @returns VuePlugin Vue插件实例
|
||||
*/
|
||||
export declare function createVuePlugin(storageType?: 'localStorage' | 'sessionStorage' | 'cookie'): VuePlugin;
|
||||
//# sourceMappingURL=vue.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/plugins/vue.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/plugins/vue.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"vue.d.ts","sourceRoot":"","sources":["../../src/plugins/vue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,WAAW,CAAc;IAEjC;;;OAGG;gBACS,OAAO,EAAE,OAAO;IAK5B;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAkDlD;;;OAGG;IACH,OAAO,IAAI,IAAI;IAIf;;;OAGG;IACH,cAAc,IAAI,WAAW;CAG9B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,cAAc,GAAG,gBAAgB,GAAG,QAAQ,GAAG,SAAS,CAGrG"}
|
||||
93
sdk/frontend/unified-login-sdk/dist/plugins/vue.js
vendored
Normal file
93
sdk/frontend/unified-login-sdk/dist/plugins/vue.js
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Vue插件模块
|
||||
* 提供Vue应用中使用统一登录SDK的能力
|
||||
*/
|
||||
import { Auth } from '../core/auth';
|
||||
import { Storage } from '../utils/storage';
|
||||
import { RouterGuard } from '../guards/router';
|
||||
/**
|
||||
* Vue插件类
|
||||
*/
|
||||
export class VuePlugin {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage) {
|
||||
this.auth = new Auth(storage);
|
||||
this.routerGuard = new RouterGuard(this.auth);
|
||||
}
|
||||
/**
|
||||
* 安装Vue插件
|
||||
* @param app Vue构造函数或Vue 3应用实例
|
||||
* @param options 插件选项
|
||||
*/
|
||||
install(app, options) {
|
||||
const { config, pluginName = 'unifiedLogin' } = options;
|
||||
// 初始化SDK
|
||||
this.auth.init(config);
|
||||
// 判断是Vue 2还是Vue 3
|
||||
const isVue3 = typeof app.config !== 'undefined';
|
||||
if (isVue3) {
|
||||
// Vue 3
|
||||
// 在全局属性上挂载SDK实例
|
||||
app.config.globalProperties[`${pluginName}`] = this.auth;
|
||||
app.config.globalProperties.$auth = this.auth; // 兼容简写
|
||||
// 提供Vue组件内的注入
|
||||
app.provide(pluginName, this.auth);
|
||||
app.provide('auth', this.auth); // 兼容简写
|
||||
// 处理路由守卫
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Vue 2
|
||||
// 在Vue实例上挂载SDK实例
|
||||
app.prototype[`${pluginName}`] = this.auth;
|
||||
app.prototype.$auth = this.auth; // 兼容简写
|
||||
// 全局混入
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取认证实例
|
||||
* @returns Auth 认证实例
|
||||
*/
|
||||
getAuth() {
|
||||
return this.auth;
|
||||
}
|
||||
/**
|
||||
* 获取路由守卫实例
|
||||
* @returns RouterGuard 路由守卫实例
|
||||
*/
|
||||
getRouterGuard() {
|
||||
return this.routerGuard;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 创建Vue插件实例
|
||||
* @param storageType 存储类型
|
||||
* @returns VuePlugin Vue插件实例
|
||||
*/
|
||||
export function createVuePlugin(storageType) {
|
||||
const storage = new Storage(storageType);
|
||||
return new VuePlugin(storage);
|
||||
}
|
||||
//# sourceMappingURL=vue.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/plugins/vue.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/plugins/vue.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"vue.js","sourceRoot":"","sources":["../../src/plugins/vue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAgB/C;;GAEG;AACH,MAAM,OAAO,SAAS;IAIpB;;;OAGG;IACH,YAAY,OAAgB;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,GAAQ,EAAE,OAAyB;QACzC,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,cAAc,EAAE,GAAG,OAAO,CAAC;QAExD,SAAS;QACT,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEvB,kBAAkB;QAClB,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC;QAEjD,IAAI,MAAM,EAAE,CAAC;YACX,QAAQ;YACR,gBAAgB;YAChB,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YACzD,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO;YAEtD,cAAc;YACd,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;YAEvC,SAAS;YACT,GAAG,CAAC,KAAK,CAAC;gBACR,YAAY;oBACV,gBAAgB;oBAChB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;wBACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;wBACpC,WAAW;wBACX,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC;oBACvD,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,QAAQ;YACR,iBAAiB;YACjB,GAAG,CAAC,SAAS,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YAC3C,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO;YAExC,OAAO;YACP,GAAG,CAAC,KAAK,CAAC;gBACR,YAAY;oBACV,gBAAgB;oBAChB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;wBACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;wBACpC,WAAW;wBACX,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC;oBACvD,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,WAA0D;IACxF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC"}
|
||||
39
sdk/frontend/unified-login-sdk/dist/types/config.d.ts
vendored
Normal file
39
sdk/frontend/unified-login-sdk/dist/types/config.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* SDK配置选项
|
||||
*/
|
||||
export interface SDKConfig {
|
||||
/** 客户端ID */
|
||||
clientId: string;
|
||||
/** 注册id **/
|
||||
registrationId: string;
|
||||
/** 后端basepath路径*/
|
||||
basepath: string;
|
||||
/** 存储类型,默认localStorage */
|
||||
storageType?: 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
idpLogoutUrl: string;
|
||||
homePage: string;
|
||||
/** 租户ID(可选) */
|
||||
tenantId?: string;
|
||||
}
|
||||
/**
|
||||
* Token信息
|
||||
*/
|
||||
export interface TokenInfo {
|
||||
/** 访问令牌 */
|
||||
accessToken: string;
|
||||
/** 刷新令牌 */
|
||||
refreshToken: string;
|
||||
/** 令牌类型,默认Bearer */
|
||||
tokenType?: string;
|
||||
/** 访问令牌过期时间(秒) */
|
||||
expiresIn: number;
|
||||
/** 刷新令牌过期时间(秒) */
|
||||
refreshExpiresIn?: number;
|
||||
/** 令牌颁发时间戳 */
|
||||
issuedAt: number;
|
||||
}
|
||||
/**
|
||||
* 事件类型
|
||||
*/
|
||||
export type EventType = 'login' | 'logout' | 'tokenExpired';
|
||||
//# sourceMappingURL=config.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/types/config.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/types/config.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,YAAY;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,cAAc,GAAG,gBAAgB,GAAG,QAAQ,CAAC;IAC3D,YAAY,EAAC,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,cAAc,CAAC"}
|
||||
2
sdk/frontend/unified-login-sdk/dist/types/config.js
vendored
Normal file
2
sdk/frontend/unified-login-sdk/dist/types/config.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=config.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/types/config.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/types/config.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":""}
|
||||
80
sdk/frontend/unified-login-sdk/dist/types/index.d.ts
vendored
Normal file
80
sdk/frontend/unified-login-sdk/dist/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
export * from './config';
|
||||
export * from './user';
|
||||
/**
|
||||
* 统一登录SDK接口
|
||||
*/
|
||||
export interface UnifiedLoginSDK {
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: import('./config').SDKConfig): void;
|
||||
getToken(): string | null;
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
login(redirectUri?: string): Promise<void>;
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
logout(): Promise<void>;
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
handleCallback(): Promise<void>;
|
||||
getRoutes(): Promise<import('./user').RouterInfo>;
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
getUserInfo(): import('./user').UserInfo;
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean;
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
hasRole(role: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
hasAllRoles(roles: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
hasPermission(permission: string | string[]): Promise<boolean>;
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
hasAllPermissions(permissions: string[]): Promise<boolean>;
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: import('./config').EventType, callback: Function): void;
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: import('./config').EventType, callback: Function): void;
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean;
|
||||
}
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/types/index.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/types/index.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAEA,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AAEvB;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,OAAO,UAAU,EAAE,SAAS,GAAG,IAAI,CAAC;IAEjD,QAAQ,IAAG,MAAM,GAAC,IAAI,CAAA;IACtB;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,SAAS,IAAI,OAAO,CAAC,OAAO,QAAQ,EAAE,UAAU,CAAC,CAAC;IAElD;;;OAGG;IACH,WAAW,IAAI,OAAO,QAAQ,EAAE,QAAQ,CAAC;IAEzC;;;OAGG;IACH,eAAe,IAAI,OAAO,CAAC;IAE3B;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnD;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/C;;;;OAIG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/D;;;;OAIG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3D;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,OAAO,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAElE;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,OAAO,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAEnE;;;OAGG;IACH,UAAU,IAAI,OAAO,CAAC;CACvB"}
|
||||
3
sdk/frontend/unified-login-sdk/dist/types/index.js
vendored
Normal file
3
sdk/frontend/unified-login-sdk/dist/types/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './config';
|
||||
export * from './user';
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/types/index.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/types/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAEA,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC"}
|
||||
83
sdk/frontend/unified-login-sdk/dist/types/user.d.ts
vendored
Normal file
83
sdk/frontend/unified-login-sdk/dist/types/user.d.ts
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 菜单信息
|
||||
*/
|
||||
export interface RouterInfo {
|
||||
/** 菜单名称 */
|
||||
name: string;
|
||||
/** 菜单路径 */
|
||||
path?: string;
|
||||
hidden: boolean;
|
||||
redirect: string;
|
||||
query: string;
|
||||
alwaysShow: boolean;
|
||||
/** 菜单组件 */
|
||||
component?: string;
|
||||
meta: MetaVo;
|
||||
children: RouterInfo;
|
||||
}
|
||||
export interface MetaVo {
|
||||
/**
|
||||
* 设置该路由在侧边栏和面包屑中展示的名字
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 设置该路由的图标,对应路径src/assets/icons/svg
|
||||
*/
|
||||
icon: string;
|
||||
/**
|
||||
* 设置为true,则不会被 <keep-alive>缓存
|
||||
*/
|
||||
noCache: boolean;
|
||||
/**
|
||||
* 内链地址(http(s)://开头)
|
||||
*/
|
||||
link: string;
|
||||
}
|
||||
/**
|
||||
* 用户基本信息
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/** 用户ID */
|
||||
userId: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 姓名 */
|
||||
nickName: string;
|
||||
/** 邮箱 */
|
||||
currentDeptId: string;
|
||||
/** 部门 */
|
||||
userDepts?: UserDept[];
|
||||
/** 岗位 */
|
||||
userPost?: UserPost[];
|
||||
/** 性别 */
|
||||
sex: string;
|
||||
/** 用户角色 */
|
||||
roles?: string[];
|
||||
/** 权限列表 */
|
||||
permissions?: string[];
|
||||
dataPermission: DataPermission;
|
||||
}
|
||||
export interface DataPermission {
|
||||
allowAll: boolean;
|
||||
onlySelf: boolean;
|
||||
deptList?: string[];
|
||||
areas?: string[];
|
||||
}
|
||||
export interface UserDept {
|
||||
postCode: string;
|
||||
postId: bigint;
|
||||
postName: string;
|
||||
postSort: bigint;
|
||||
remark: string;
|
||||
status: bigint;
|
||||
}
|
||||
export interface UserPost {
|
||||
ancestors: string;
|
||||
deptId: bigint;
|
||||
deptName: string;
|
||||
leader: string;
|
||||
orderNum: bigint;
|
||||
parentId: bigint;
|
||||
status: bigint;
|
||||
}
|
||||
//# sourceMappingURL=user.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/types/user.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/types/user.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAC,MAAM,CAAC;IACZ,QAAQ,EAAE,UAAU,CAAC;CACtB;AACD,MAAM,WAAW,MAAM;IACrB;;OAEG;IACH,KAAK,EAAC,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAC,MAAM,CAAC;IAEZ;;OAEG;IACH,OAAO,EAAC,OAAO,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAC,MAAM,CAAC;CACb;AAGD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,WAAW;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS;IACT,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS;IACT,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,SAAS;IACT,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,cAAc,CAAA;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAC,MAAM,EAAE,CAAA;CAChB;AACD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAC,MAAM,CAAA;IACf,MAAM,EAAC,MAAM,CAAA;IACb,QAAQ,EAAC,MAAM,CAAA;IACf,QAAQ,EAAC,MAAM,CAAA;IACf,MAAM,EAAC,MAAM,CAAA;IACb,MAAM,EAAC,MAAM,CAAA;CACd;AACD,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf"}
|
||||
2
sdk/frontend/unified-login-sdk/dist/types/user.js
vendored
Normal file
2
sdk/frontend/unified-login-sdk/dist/types/user.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=user.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/types/user.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/types/user.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":""}
|
||||
108
sdk/frontend/unified-login-sdk/dist/utils/storage.d.ts
vendored
Normal file
108
sdk/frontend/unified-login-sdk/dist/utils/storage.d.ts
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 存储工具类
|
||||
* 支持localStorage、sessionStorage和cookie三种存储方式
|
||||
*/
|
||||
type StorageType = 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
/**
|
||||
* 存储工具类
|
||||
*/
|
||||
export declare class Storage {
|
||||
private storageType;
|
||||
private prefix;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storageType 存储类型
|
||||
* @param prefix 存储前缀,默认'unified_login_'
|
||||
*/
|
||||
constructor(storageType?: StorageType, prefix?: string);
|
||||
/**
|
||||
* 设置存储项
|
||||
* @param key 存储键
|
||||
* @param value 存储值
|
||||
* @param options 可选参数,cookie存储时使用
|
||||
*/
|
||||
set(key: string, value: any, options?: {
|
||||
expires?: number;
|
||||
path?: string;
|
||||
domain?: string;
|
||||
secure?: boolean;
|
||||
}): void;
|
||||
/**
|
||||
* 获取存储项
|
||||
* @param key 存储键
|
||||
* @returns 存储值
|
||||
*/
|
||||
get(key: string): any;
|
||||
/**
|
||||
* 移除存储项
|
||||
* @param key 存储键
|
||||
*/
|
||||
remove(key: string): void;
|
||||
/**
|
||||
* 清空所有存储项
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* 检查存储类型是否可用
|
||||
* @returns boolean 是否可用
|
||||
*/
|
||||
isAvailable(): boolean;
|
||||
/**
|
||||
* 设置localStorage
|
||||
*/
|
||||
private setLocalStorage;
|
||||
/**
|
||||
* 获取localStorage
|
||||
*/
|
||||
private getLocalStorage;
|
||||
/**
|
||||
* 移除localStorage
|
||||
*/
|
||||
private removeLocalStorage;
|
||||
/**
|
||||
* 清空localStorage中所有带前缀的项
|
||||
*/
|
||||
private clearLocalStorage;
|
||||
/**
|
||||
* 检查localStorage是否可用
|
||||
*/
|
||||
private isLocalStorageAvailable;
|
||||
/**
|
||||
* 设置sessionStorage
|
||||
*/
|
||||
private setSessionStorage;
|
||||
/**
|
||||
* 获取sessionStorage
|
||||
*/
|
||||
private getSessionStorage;
|
||||
/**
|
||||
* 移除sessionStorage
|
||||
*/
|
||||
private removeSessionStorage;
|
||||
/**
|
||||
* 清空sessionStorage中所有带前缀的项
|
||||
*/
|
||||
private clearSessionStorage;
|
||||
/**
|
||||
* 检查sessionStorage是否可用
|
||||
*/
|
||||
private isSessionStorageAvailable;
|
||||
/**
|
||||
* 设置cookie
|
||||
*/
|
||||
private setCookie;
|
||||
/**
|
||||
* 获取cookie
|
||||
*/
|
||||
private getCookie;
|
||||
/**
|
||||
* 移除cookie
|
||||
*/
|
||||
private removeCookie;
|
||||
/**
|
||||
* 清空所有带前缀的cookie
|
||||
*/
|
||||
private clearCookie;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=storage.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/utils/storage.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/utils/storage.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/utils/storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,WAAW,GAAG,cAAc,GAAG,gBAAgB,GAAG,QAAQ,CAAC;AAEhE;;GAEG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAS;IAEvB;;;;OAIG;gBACS,WAAW,GAAE,WAA4B,EAAE,MAAM,GAAE,MAAyB;IAKxF;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAiBpH;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IA+BrB;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAgBzB;;OAEG;IACH,KAAK,IAAI,IAAI;IAcb;;;OAGG;IACH,WAAW,IAAI,OAAO;IAmBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAgBjC;;OAEG;IACH,OAAO,CAAC,SAAS;IAsCjB;;OAEG;IACH,OAAO,CAAC,SAAS;IAsBjB;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,WAAW;CAcpB"}
|
||||
316
sdk/frontend/unified-login-sdk/dist/utils/storage.js
vendored
Normal file
316
sdk/frontend/unified-login-sdk/dist/utils/storage.js
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 存储工具类
|
||||
* 支持localStorage、sessionStorage和cookie三种存储方式
|
||||
*/
|
||||
/**
|
||||
* 存储工具类
|
||||
*/
|
||||
export class Storage {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storageType 存储类型
|
||||
* @param prefix 存储前缀,默认'unified_login_'
|
||||
*/
|
||||
constructor(storageType = 'localStorage', prefix = 'unified_login_') {
|
||||
this.storageType = storageType;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
/**
|
||||
* 设置存储项
|
||||
* @param key 存储键
|
||||
* @param value 存储值
|
||||
* @param options 可选参数,cookie存储时使用
|
||||
*/
|
||||
set(key, value, options) {
|
||||
const fullKey = this.prefix + key;
|
||||
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.setLocalStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.setSessionStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.setCookie(fullKey, stringValue, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取存储项
|
||||
* @param key 存储键
|
||||
* @returns 存储值
|
||||
*/
|
||||
get(key) {
|
||||
const fullKey = this.prefix + key;
|
||||
let value;
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
value = this.getLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
value = this.getSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
value = this.getCookie(fullKey);
|
||||
break;
|
||||
default:
|
||||
value = null;
|
||||
}
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
// 尝试解析JSON
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (e) {
|
||||
// 如果不是JSON,直接返回字符串
|
||||
return value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 移除存储项
|
||||
* @param key 存储键
|
||||
*/
|
||||
remove(key) {
|
||||
const fullKey = this.prefix + key;
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.removeLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.removeSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.removeCookie(fullKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清空所有存储项
|
||||
*/
|
||||
clear() {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.clearLocalStorage();
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.clearSessionStorage();
|
||||
break;
|
||||
case 'cookie':
|
||||
this.clearCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查存储类型是否可用
|
||||
* @returns boolean 是否可用
|
||||
*/
|
||||
isAvailable() {
|
||||
try {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
return this.isLocalStorageAvailable();
|
||||
case 'sessionStorage':
|
||||
return this.isSessionStorageAvailable();
|
||||
case 'cookie':
|
||||
return typeof document !== 'undefined';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ------------------------ localStorage 操作 ------------------------
|
||||
/**
|
||||
* 设置localStorage
|
||||
*/
|
||||
setLocalStorage(key, value) {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取localStorage
|
||||
*/
|
||||
getLocalStorage(key) {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 移除localStorage
|
||||
*/
|
||||
removeLocalStorage(key) {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清空localStorage中所有带前缀的项
|
||||
*/
|
||||
clearLocalStorage() {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
localStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查localStorage是否可用
|
||||
*/
|
||||
isLocalStorageAvailable() {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
localStorage.setItem(testKey, testKey);
|
||||
localStorage.removeItem(testKey);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ------------------------ sessionStorage 操作 ------------------------
|
||||
/**
|
||||
* 设置sessionStorage
|
||||
*/
|
||||
setSessionStorage(key, value) {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取sessionStorage
|
||||
*/
|
||||
getSessionStorage(key) {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
return sessionStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 移除sessionStorage
|
||||
*/
|
||||
removeSessionStorage(key) {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清空sessionStorage中所有带前缀的项
|
||||
*/
|
||||
clearSessionStorage() {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
for (let i = 0; i < sessionStorage.length; i++) {
|
||||
const key = sessionStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
sessionStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查sessionStorage是否可用
|
||||
*/
|
||||
isSessionStorageAvailable() {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
sessionStorage.setItem(testKey, testKey);
|
||||
sessionStorage.removeItem(testKey);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ------------------------ cookie 操作 ------------------------
|
||||
/**
|
||||
* 设置cookie
|
||||
*/
|
||||
setCookie(key, value, options) {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
let cookieString = `${key}=${encodeURIComponent(value)}`;
|
||||
if (options) {
|
||||
// 设置过期时间(秒)
|
||||
if (options.expires) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + options.expires * 1000);
|
||||
cookieString += `; expires=${date.toUTCString()}`;
|
||||
}
|
||||
// 设置路径
|
||||
if (options.path) {
|
||||
cookieString += `; path=${options.path}`;
|
||||
}
|
||||
// 设置域名
|
||||
if (options.domain) {
|
||||
cookieString += `; domain=${options.domain}`;
|
||||
}
|
||||
// 设置secure
|
||||
if (options.secure) {
|
||||
cookieString += '; secure';
|
||||
}
|
||||
}
|
||||
document.cookie = cookieString;
|
||||
}
|
||||
/**
|
||||
* 获取cookie
|
||||
*/
|
||||
getCookie(key) {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
const name = `${key}=`;
|
||||
const decodedCookie = decodeURIComponent(document.cookie);
|
||||
const ca = decodedCookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) === 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 移除cookie
|
||||
*/
|
||||
removeCookie(key) {
|
||||
this.setCookie(key, '', { expires: -1 });
|
||||
}
|
||||
/**
|
||||
* 清空所有带前缀的cookie
|
||||
*/
|
||||
clearCookie() {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const cookies = document.cookie.split(';');
|
||||
for (const cookie of cookies) {
|
||||
const eqPos = cookie.indexOf('=');
|
||||
const key = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
||||
if (key.startsWith(this.prefix)) {
|
||||
this.removeCookie(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=storage.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/utils/storage.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/utils/storage.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
55
sdk/frontend/unified-login-sdk/dist/utils/url.d.ts
vendored
Normal file
55
sdk/frontend/unified-login-sdk/dist/utils/url.d.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* URL处理工具
|
||||
* 用于生成授权URL、解析URL参数等功能
|
||||
*/
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param length 字符串长度,默认32位
|
||||
* @returns 随机字符串
|
||||
*/
|
||||
export declare function generateRandomString(length?: number): string;
|
||||
/**
|
||||
* 解析URL查询参数
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 查询参数对象
|
||||
*/
|
||||
export declare function parseQueryParams(url?: string): Record<string, string>;
|
||||
/**
|
||||
* 构建URL查询参数
|
||||
* @param params 查询参数对象
|
||||
* @returns 查询参数字符串
|
||||
*/
|
||||
export declare function buildQueryParams(params: Record<string, any>): string;
|
||||
/**
|
||||
* 生成OAuth2授权URL
|
||||
* @param authorizationEndpoint 授权端点URL
|
||||
* @param clientId 客户端ID
|
||||
* @param redirectUri 重定向URL
|
||||
* @param options 可选参数
|
||||
* @returns 授权URL
|
||||
*/
|
||||
export declare function generateAuthorizationUrl(authorizationEndpoint: string, clientId: string, redirectUri: string, options?: {
|
||||
responseType?: string;
|
||||
scope?: string;
|
||||
state?: string;
|
||||
[key: string]: any;
|
||||
}): string;
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 是否为授权回调
|
||||
*/
|
||||
export declare function isCallbackUrl(url?: string): boolean;
|
||||
/**
|
||||
* 获取当前URL的路径名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 路径名
|
||||
*/
|
||||
export declare function getPathname(url?: string): string;
|
||||
/**
|
||||
* 获取当前URL的主机名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 主机名
|
||||
*/
|
||||
export declare function getHostname(url?: string): string;
|
||||
//# sourceMappingURL=url.d.ts.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/utils/url.d.ts.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/utils/url.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM,CAOhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAA6B,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgB3F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAQpE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,qBAAqB,EAAE,MAAM,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IACR,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,GACA,MAAM,CAmBR;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAA6B,GAAG,OAAO,CAGzE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAA6B,GAAG,MAAM,CAGtE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAA6B,GAAG,MAAM,CAGtE"}
|
||||
100
sdk/frontend/unified-login-sdk/dist/utils/url.js
vendored
Normal file
100
sdk/frontend/unified-login-sdk/dist/utils/url.js
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* URL处理工具
|
||||
* 用于生成授权URL、解析URL参数等功能
|
||||
*/
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param length 字符串长度,默认32位
|
||||
* @returns 随机字符串
|
||||
*/
|
||||
export function generateRandomString(length = 32) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 解析URL查询参数
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 查询参数对象
|
||||
*/
|
||||
export function parseQueryParams(url = window.location.href) {
|
||||
const params = {};
|
||||
const queryString = url.split('?')[1];
|
||||
if (!queryString) {
|
||||
return params;
|
||||
}
|
||||
const pairs = queryString.split('&');
|
||||
for (const pair of pairs) {
|
||||
const [key, value] = pair.split('=');
|
||||
if (key) {
|
||||
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
/**
|
||||
* 构建URL查询参数
|
||||
* @param params 查询参数对象
|
||||
* @returns 查询参数字符串
|
||||
*/
|
||||
export function buildQueryParams(params) {
|
||||
const pairs = [];
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
||||
}
|
||||
}
|
||||
return pairs.length ? `?${pairs.join('&')}` : '';
|
||||
}
|
||||
/**
|
||||
* 生成OAuth2授权URL
|
||||
* @param authorizationEndpoint 授权端点URL
|
||||
* @param clientId 客户端ID
|
||||
* @param redirectUri 重定向URL
|
||||
* @param options 可选参数
|
||||
* @returns 授权URL
|
||||
*/
|
||||
export function generateAuthorizationUrl(authorizationEndpoint, clientId, redirectUri, options) {
|
||||
const { responseType = 'code', scope, state = generateRandomString(32), ...extraParams } = options || {};
|
||||
const params = {
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: responseType,
|
||||
state,
|
||||
...(scope ? { scope } : {}),
|
||||
...extraParams
|
||||
};
|
||||
const queryString = buildQueryParams(params);
|
||||
return `${authorizationEndpoint}${queryString}`;
|
||||
}
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 是否为授权回调
|
||||
*/
|
||||
export function isCallbackUrl(url = window.location.href) {
|
||||
const params = parseQueryParams(url);
|
||||
return !!params.code || !!params.error;
|
||||
}
|
||||
/**
|
||||
* 获取当前URL的路径名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 路径名
|
||||
*/
|
||||
export function getPathname(url = window.location.href) {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.pathname;
|
||||
}
|
||||
/**
|
||||
* 获取当前URL的主机名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 主机名
|
||||
*/
|
||||
export function getHostname(url = window.location.href) {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.hostname;
|
||||
}
|
||||
//# sourceMappingURL=url.js.map
|
||||
1
sdk/frontend/unified-login-sdk/dist/utils/url.js.map
vendored
Normal file
1
sdk/frontend/unified-login-sdk/dist/utils/url.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB,EAAE;IACtD,MAAM,KAAK,GAAG,gEAAgE,CAAC;IAC/E,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IACjE,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,GAAG,kBAAkB,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAA2B;IAC1D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CACtC,qBAA6B,EAC7B,QAAgB,EAChB,WAAmB,EACnB,OAKC;IAED,MAAM,EACJ,YAAY,GAAG,MAAM,EACrB,KAAK,EACL,KAAK,GAAG,oBAAoB,CAAC,EAAE,CAAC,EAChC,GAAG,WAAW,EACf,GAAG,OAAO,IAAI,EAAE,CAAC;IAElB,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,QAAQ;QACnB,YAAY,EAAE,WAAW;QACzB,aAAa,EAAE,YAAY;QAC3B,KAAK;QACL,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,WAAW;KACf,CAAC;IAEF,MAAM,WAAW,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO,GAAG,qBAAqB,GAAG,WAAW,EAAE,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IAC9D,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IAC5D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,MAAM,CAAC,QAAQ,CAAC,IAAI;IAC5D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC"}
|
||||
41
sdk/frontend/unified-login-sdk/package.json
Normal file
41
sdk/frontend/unified-login-sdk/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "unified-login-sdk",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript前端SDK,用于前后端分离项目对接统一登录系统",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc && rollup -c",
|
||||
"dev": "tsc -w",
|
||||
"test": "jest",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"oauth2",
|
||||
"login",
|
||||
"sdk",
|
||||
"typescript",
|
||||
"unified-login"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.56.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"rollup": "^4.9.6",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
2103
sdk/frontend/unified-login-sdk/pnpm-lock.yaml
generated
Normal file
2103
sdk/frontend/unified-login-sdk/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
sdk/frontend/unified-login-sdk/rollup.config.js
Normal file
23
sdk/frontend/unified-login-sdk/rollup.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
|
||||
export default {
|
||||
input: 'src/index.ts',
|
||||
output: [
|
||||
{
|
||||
file: 'dist/index.js',
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
},
|
||||
{
|
||||
file: 'dist/index.esm.js',
|
||||
format: 'esm',
|
||||
sourcemap: true
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: './tsconfig.json',
|
||||
clean: true
|
||||
})
|
||||
]
|
||||
};
|
||||
340
sdk/frontend/unified-login-sdk/src/core/auth.ts
Normal file
340
sdk/frontend/unified-login-sdk/src/core/auth.ts
Normal file
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* 认证核心逻辑
|
||||
* 实现OAuth2授权码模式的完整流程
|
||||
*/
|
||||
|
||||
import {EventType, RouterInfo, SDKConfig, UserInfo} from '../types';
|
||||
import {TokenManager} from './token';
|
||||
import {HttpClient} from './http';
|
||||
import {Storage} from '../utils/storage';
|
||||
import {buildQueryParams, isCallbackUrl, parseQueryParams} from '../utils/url';
|
||||
|
||||
/**
|
||||
* 认证核心类
|
||||
*/
|
||||
export class Auth {
|
||||
private config: SDKConfig | null = null;
|
||||
private tokenManager!: TokenManager;
|
||||
private httpClient: HttpClient;
|
||||
private readonly storage: Storage;
|
||||
private eventHandlers: Record<EventType, Function[]> = {
|
||||
login: [],
|
||||
logout: [],
|
||||
tokenExpired: []
|
||||
};
|
||||
private userInfoCache: UserInfo | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage) {
|
||||
this.storage = storage;
|
||||
// 创建带有基础配置的HttpClient
|
||||
this.httpClient = new HttpClient({
|
||||
timeout: 15000,
|
||||
withCredentials: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: SDKConfig): void {
|
||||
this.config = config;
|
||||
|
||||
// 更新HTTP客户端配置
|
||||
this.httpClient.updateConfig({
|
||||
baseURL: config.basepath || '',
|
||||
tenantId: config.tenantId
|
||||
});
|
||||
|
||||
// 创建TokenManager
|
||||
this.tokenManager = new TokenManager(this.storage, this.config.clientId);
|
||||
|
||||
// 设置Token获取函数
|
||||
this.httpClient.setTokenGetter(() => this.getToken());
|
||||
}
|
||||
|
||||
getToken():string | null{
|
||||
return this.tokenManager.getToken()
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
async login(redirectUri?: string): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const registrationId = this.config.registrationId || 'idp'
|
||||
const path = `/oauth2/authorization/${registrationId}`
|
||||
const tokenResponse = await this.httpClient.get(path,{needAuth:false})
|
||||
const redirect = tokenResponse.data.redirect_url
|
||||
const params = parseQueryParams(redirect)
|
||||
|
||||
// 安全存储当前页面URL用于回调后重定向
|
||||
if (params.state) {
|
||||
// 确保存储的URL是有效的
|
||||
const currentUrl = redirectUri || window.location.href;
|
||||
const safeUrl = currentUrl && typeof currentUrl === 'string' ? currentUrl : '/';
|
||||
this.storage.set(params.state, safeUrl);
|
||||
console.log('💾 存储重定向状态:', params.state, '->', safeUrl);
|
||||
}
|
||||
|
||||
window.location.href = redirect
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
async logout(): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
// 清除本地存储的Token和用户信息缓存
|
||||
this.tokenManager.clearToken();
|
||||
this.userInfoCache = null;
|
||||
await this.httpClient.post(`/logout`,null,{needAuth:true})
|
||||
// 触发退出事件
|
||||
this.emit('logout');
|
||||
window.location.href = this.config.idpLogoutUrl+'?redirect='+this.config.homePage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
async handleCallback(): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
|
||||
const params = parseQueryParams();
|
||||
|
||||
// 检查是否有错误
|
||||
if (params.error) {
|
||||
throw new Error(`Authorization error: ${params.error} - ${params.error_description || ''}`);
|
||||
}
|
||||
|
||||
// 检查是否有授权码
|
||||
if (!params.code) {
|
||||
throw new Error('Authorization code not found');
|
||||
}
|
||||
|
||||
const registrationId = this.config.registrationId || 'idp'
|
||||
const callback = `/login/oauth2/code/${registrationId}${buildQueryParams(params)}`
|
||||
const tokenResponse = await this.httpClient.get(callback,{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
needAuth: false
|
||||
})
|
||||
// 触发登录事件
|
||||
this.emit('login');
|
||||
// 缓存用户信息
|
||||
this.userInfoCache = tokenResponse.data.data;
|
||||
this.tokenManager.saveToken(tokenResponse.headers['authorization']||tokenResponse.headers['Authorization'])
|
||||
// 安全处理重定向URL
|
||||
let redirectUrl = this.config.homePage || '/';
|
||||
|
||||
if (params.state) {
|
||||
const storedUrl = this.storage.get(params.state);
|
||||
if (storedUrl && typeof storedUrl === 'string') {
|
||||
redirectUrl = storedUrl;
|
||||
}
|
||||
this.storage.remove(params.state);
|
||||
}
|
||||
|
||||
// 确保URL格式正确
|
||||
if (!redirectUrl.startsWith('http') && !redirectUrl.startsWith('/')) {
|
||||
redirectUrl = '/' + redirectUrl;
|
||||
}
|
||||
|
||||
console.log('🔄 重定向到:', redirectUrl);
|
||||
window.location.href = redirectUrl;
|
||||
|
||||
}
|
||||
|
||||
async getRoutes(): Promise<RouterInfo> {
|
||||
if (!this.config) {
|
||||
throw new Error('SDK not initialized');
|
||||
}
|
||||
const tokenResponse = await this.httpClient.get(`/idp/routes`,{needAuth:true})
|
||||
if(tokenResponse.status===401){
|
||||
await this.logout()
|
||||
}
|
||||
return tokenResponse.data.data
|
||||
|
||||
}
|
||||
/**
|
||||
* 刷新用户信息缓存
|
||||
* @returns Promise<UserInfo> 更新后的用户信息
|
||||
*/
|
||||
async refreshUserInfo(): Promise<UserInfo> {
|
||||
console.log('🔄 刷新用户信息缓存...');
|
||||
this.userInfoCache = null; // 清除缓存
|
||||
return await this.getUserInfo(); // 重新获取
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo 用户信息
|
||||
*/
|
||||
async getUserInfo(): Promise<UserInfo> {
|
||||
// 首先检查缓存
|
||||
if (this.userInfoCache) {
|
||||
console.log('📋 从缓存获取用户信息');
|
||||
return this.userInfoCache;
|
||||
}
|
||||
|
||||
// 检查是否已认证
|
||||
if (!this.isAuthenticated()) {
|
||||
throw new Error('User not authenticated');
|
||||
}
|
||||
|
||||
try {
|
||||
// 从后端接口获取用户信息
|
||||
console.log('🌐 从后端获取用户信息...');
|
||||
const response = await this.httpClient.get('/idp/getUserInfo', { needAuth: true });
|
||||
this.userInfoCache = response.data;
|
||||
console.log('✅ 用户信息获取成功');
|
||||
return this.userInfoCache!;
|
||||
} catch (error) {
|
||||
console.error('❌ 获取用户信息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
async hasRole(role: string | string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo = await this.getUserInfo();
|
||||
const roleCodes = userInfo.roles||[];
|
||||
|
||||
if (Array.isArray(role)) {
|
||||
// 检查是否有任一角色
|
||||
return role.some(r => roleCodes.includes(r));
|
||||
}
|
||||
|
||||
// 检查是否有单个角色
|
||||
return roleCodes.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
async hasAllRoles(roles: string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo = await this.getUserInfo();
|
||||
const roleCodes = userInfo.roles||[];
|
||||
// 检查是否有所有角色
|
||||
return roles.every(r => roleCodes.includes(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
async hasPermission(permission: string | string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo = await this.getUserInfo();
|
||||
const permissions = userInfo.permissions||[];
|
||||
|
||||
if (Array.isArray(permission)) {
|
||||
// 检查是否有任一权限
|
||||
return permission.some(p => permissions.includes(p));
|
||||
}
|
||||
|
||||
// 检查是否有单个权限
|
||||
return permissions.includes(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
async hasAllPermissions(permissions: string[]): Promise<boolean> {
|
||||
if (!this.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo = await this.getUserInfo();
|
||||
const userPermissions = userInfo.permissions||[];
|
||||
|
||||
// 检查是否有所有权限
|
||||
return permissions.every(p => userPermissions.includes(p));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean {
|
||||
// 检查Token是否存在且未过期
|
||||
return !!this.tokenManager.getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: EventType, callback: Function): void {
|
||||
this.eventHandlers[event].push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: EventType, callback: Function): void {
|
||||
this.eventHandlers[event] = this.eventHandlers[event].filter(handler => handler !== callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
private emit(event: EventType, data?: any): void {
|
||||
this.eventHandlers[event].forEach(handler => {
|
||||
try {
|
||||
handler(data);
|
||||
} catch (error) {
|
||||
console.error(`Error in ${event} event handler:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean {
|
||||
return isCallbackUrl();
|
||||
}
|
||||
}
|
||||
287
sdk/frontend/unified-login-sdk/src/core/http.examples.ts
Normal file
287
sdk/frontend/unified-login-sdk/src/core/http.examples.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* HTTP客户端使用示例
|
||||
*/
|
||||
|
||||
import { HttpClient, RequestInterceptor, ResponseInterceptor } from './http';
|
||||
import { Storage } from '../utils/storage';
|
||||
|
||||
// 基础使用示例
|
||||
export function basicUsageExample() {
|
||||
// 创建HTTP客户端实例
|
||||
const httpClient = new HttpClient({
|
||||
baseURL: 'https://api.example.com',
|
||||
timeout: 10000,
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
// 设置Token获取函数
|
||||
httpClient.setTokenGetter(() => {
|
||||
// 从TokenManager或其他地方获取Token
|
||||
return localStorage.getItem('auth_token') || '';
|
||||
});
|
||||
|
||||
// 设置租户ID
|
||||
httpClient.setTenantId('tenant-123');
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
// 拦截器使用示例
|
||||
export function interceptorExample() {
|
||||
const httpClient = new HttpClient();
|
||||
|
||||
// 请求拦截器 - 添加通用头信息
|
||||
const authInterceptor: RequestInterceptor = {
|
||||
onRequest: (options) => {
|
||||
// 添加认证头
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
'Authorization': `Bearer ${token}`
|
||||
};
|
||||
}
|
||||
|
||||
// 添加请求ID用于追踪
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
'X-Request-ID': Math.random().toString(36).substr(2, 9)
|
||||
};
|
||||
|
||||
return options;
|
||||
},
|
||||
onRequestError: (error) => {
|
||||
console.error('请求拦截器错误:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 响应拦截器 - 统一处理响应格式
|
||||
const responseInterceptor: ResponseInterceptor = {
|
||||
onResponse: (response) => {
|
||||
// 记录响应时间
|
||||
console.log(`Response received: ${response.status} ${response.statusText}`);
|
||||
|
||||
// 可以在这里统一处理某些业务逻辑
|
||||
return response;
|
||||
},
|
||||
onResponseError: (error) => {
|
||||
// 统一错误处理
|
||||
if (error.status === 401) {
|
||||
// Token过期,跳转到登录页
|
||||
window.location.href = '/login';
|
||||
}
|
||||
|
||||
console.error('Response error:', error.message);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加拦截器
|
||||
httpClient.addRequestInterceptor(authInterceptor);
|
||||
httpClient.addResponseInterceptor(responseInterceptor);
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
// 工厂方法使用示例
|
||||
export function factoryMethodExample() {
|
||||
// 创建不同环境的客户端实例
|
||||
const devClient = HttpClient.create('https://dev-api.example.com', {
|
||||
timeout: 5000,
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
const prodClient = HttpClient.create('https://api.example.com', {
|
||||
timeout: 15000,
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
return { devClient, prodClient };
|
||||
}
|
||||
|
||||
// 配置管理示例
|
||||
export function configManagementExample() {
|
||||
const httpClient = new HttpClient({
|
||||
baseURL: 'https://api.example.com'
|
||||
});
|
||||
|
||||
// 获取当前配置
|
||||
const currentConfig = httpClient.getConfig();
|
||||
console.log('Current config:', currentConfig);
|
||||
|
||||
// 动态更新配置
|
||||
httpClient.updateConfig({
|
||||
timeout: 20000,
|
||||
headers: {
|
||||
'X-API-Version': 'v2'
|
||||
}
|
||||
});
|
||||
|
||||
// 验证配置更新
|
||||
const updatedConfig = httpClient.getConfig();
|
||||
console.log('Updated config:', updatedConfig);
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
// 实际API调用示例
|
||||
export async function apiCallExample() {
|
||||
const httpClient = basicUsageExample();
|
||||
|
||||
try {
|
||||
// GET请求
|
||||
const getUsers = await httpClient.get('/users', {
|
||||
needAuth: true,
|
||||
timeout: 8000
|
||||
});
|
||||
console.log('Users:', getUsers.data);
|
||||
|
||||
// POST请求
|
||||
const createUser = await httpClient.post('/users', {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
}, {
|
||||
needAuth: true
|
||||
});
|
||||
console.log('Created user:', createUser.data);
|
||||
|
||||
// PUT请求
|
||||
const updateUser = await httpClient.put('/users/1', {
|
||||
name: 'Jane Doe'
|
||||
});
|
||||
console.log('Updated user:', updateUser.data);
|
||||
|
||||
// DELETE请求
|
||||
const deleteUser = await httpClient.delete('/users/1');
|
||||
console.log('Delete result:', deleteUser.data);
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('API call failed:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 错误处理示例
|
||||
export function errorHandlingExample() {
|
||||
// 不同类型的错误处理
|
||||
const handleHttpError = (error: any) => {
|
||||
if (error instanceof Error) {
|
||||
if ('status' in error) {
|
||||
// HTTP错误
|
||||
const httpError = error as any;
|
||||
switch (httpError.status) {
|
||||
case 401:
|
||||
console.log('未授权,请重新登录');
|
||||
break;
|
||||
case 403:
|
||||
console.log('禁止访问');
|
||||
break;
|
||||
case 404:
|
||||
console.log('资源不存在');
|
||||
break;
|
||||
case 500:
|
||||
console.log('服务器内部错误');
|
||||
break;
|
||||
default:
|
||||
console.log(`HTTP错误: ${httpError.status} - ${httpError.message}`);
|
||||
}
|
||||
} else {
|
||||
// 网络错误或其他错误
|
||||
console.log('网络错误:', error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return handleHttpError;
|
||||
}
|
||||
|
||||
// 完整的实际应用示例
|
||||
export class ApiService {
|
||||
private httpClient: HttpClient;
|
||||
|
||||
constructor() {
|
||||
this.httpClient = new HttpClient({
|
||||
baseURL: process.env.API_BASE_URL || 'https://api.example.com',
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
// 设置全局拦截器
|
||||
this.setupInterceptors();
|
||||
}
|
||||
|
||||
private setupInterceptors() {
|
||||
// 请求拦截器
|
||||
this.httpClient.addRequestInterceptor({
|
||||
onRequest: (options) => {
|
||||
// 添加时间戳防止缓存
|
||||
if (options.method === 'GET') {
|
||||
const separator = options.url.includes('?') ? '&' : '?';
|
||||
options.url += `${separator}_t=${Date.now()}`;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
});
|
||||
|
||||
// 响应拦截器
|
||||
this.httpClient.addResponseInterceptor({
|
||||
onResponse: (response) => {
|
||||
// 统一日志记录
|
||||
console.log(`[${response.status}] ${response.statusText}`);
|
||||
return response;
|
||||
},
|
||||
onResponseError: (error) => {
|
||||
// 统一错误上报
|
||||
console.error('API Error:', {
|
||||
status: error.status,
|
||||
message: error.message,
|
||||
url: window.location.href
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 用户相关API
|
||||
async getUsers(page: number = 1, size: number = 10) {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
size: size.toString()
|
||||
});
|
||||
return this.httpClient.get(`/users?${queryParams}`);
|
||||
}
|
||||
|
||||
async getUserById(id: string) {
|
||||
return this.httpClient.get(`/users/${id}`);
|
||||
}
|
||||
|
||||
async createUser(userData: any) {
|
||||
return this.httpClient.post('/users', userData);
|
||||
}
|
||||
|
||||
async updateUser(id: string, userData: any) {
|
||||
return this.httpClient.put(`/users/${id}`, userData);
|
||||
}
|
||||
|
||||
async deleteUser(id: string) {
|
||||
return this.httpClient.delete(`/users/${id}`);
|
||||
}
|
||||
|
||||
// 认证相关API
|
||||
async login(credentials: { username: string; password: string }) {
|
||||
return this.httpClient.post('/auth/login', credentials, {
|
||||
needAuth: false
|
||||
});
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string) {
|
||||
return this.httpClient.post('/auth/refresh', { refreshToken }, {
|
||||
needAuth: false
|
||||
});
|
||||
}
|
||||
|
||||
async logout() {
|
||||
return this.httpClient.post('/auth/logout');
|
||||
}
|
||||
}
|
||||
136
sdk/frontend/unified-login-sdk/src/core/http.test.ts
Normal file
136
sdk/frontend/unified-login-sdk/src/core/http.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* HTTP客户端测试文件
|
||||
*/
|
||||
|
||||
import { HttpClient, HttpError, RequestInterceptor, ResponseInterceptor } from './http';
|
||||
import { Storage } from '../utils/storage';
|
||||
|
||||
describe('HttpClient', () => {
|
||||
let httpClient: HttpClient;
|
||||
let storage: Storage;
|
||||
|
||||
beforeEach(() => {
|
||||
storage = new Storage('localStorage');
|
||||
httpClient = new HttpClient({
|
||||
baseURL: 'https://api.example.com',
|
||||
timeout: 5000
|
||||
});
|
||||
});
|
||||
|
||||
describe('构造函数', () => {
|
||||
test('应该正确初始化默认配置', () => {
|
||||
const client = new HttpClient();
|
||||
expect(client.getConfig()).toMatchObject({
|
||||
baseURL: '',
|
||||
timeout: 10000,
|
||||
withCredentials: true
|
||||
});
|
||||
});
|
||||
|
||||
test('应该正确合并自定义配置', () => {
|
||||
const config = {
|
||||
baseURL: 'https://test.com',
|
||||
timeout: 3000,
|
||||
withCredentials: false
|
||||
};
|
||||
|
||||
const client = new HttpClient(config);
|
||||
expect(client.getConfig()).toMatchObject(config);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token管理', () => {
|
||||
test('应该能够设置和获取Token获取函数', () => {
|
||||
const tokenGetter = () => 'test-token';
|
||||
httpClient.setTokenGetter(tokenGetter);
|
||||
|
||||
// 这里需要通过反射或其他方式验证内部状态
|
||||
// 由于是私有属性,我们通过实际请求来间接测试
|
||||
});
|
||||
|
||||
test('应该能够设置租户ID', () => {
|
||||
httpClient.setTenantId('test-tenant');
|
||||
expect(httpClient.getConfig().tenantId).toBe('test-tenant');
|
||||
});
|
||||
});
|
||||
|
||||
describe('拦截器', () => {
|
||||
test('应该能够添加和移除请求拦截器', () => {
|
||||
const interceptor: RequestInterceptor = {
|
||||
onRequest: (options) => {
|
||||
options.headers = { ...options.headers, 'X-Custom': 'test' };
|
||||
return options;
|
||||
}
|
||||
};
|
||||
|
||||
httpClient.addRequestInterceptor(interceptor);
|
||||
// 测试逻辑需要配合实际请求
|
||||
|
||||
httpClient.removeRequestInterceptor(interceptor);
|
||||
});
|
||||
|
||||
test('应该能够添加和移除响应拦截器', () => {
|
||||
const interceptor: ResponseInterceptor = {
|
||||
onResponse: (response) => {
|
||||
return { ...response, data: { ...response.data, intercepted: true } };
|
||||
}
|
||||
};
|
||||
|
||||
httpClient.addResponseInterceptor(interceptor);
|
||||
// 测试逻辑需要配合实际请求
|
||||
|
||||
httpClient.removeResponseInterceptor(interceptor);
|
||||
});
|
||||
});
|
||||
|
||||
describe('静态方法', () => {
|
||||
test('应该能够创建带基础URL的客户端实例', () => {
|
||||
const client = HttpClient.create('https://api.test.com', {
|
||||
timeout: 8000
|
||||
});
|
||||
|
||||
expect(client.getConfig().baseURL).toBe('https://api.test.com');
|
||||
expect(client.getConfig().timeout).toBe(8000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('配置管理', () => {
|
||||
test('应该能够获取和更新配置', () => {
|
||||
const initialConfig = httpClient.getConfig();
|
||||
expect(initialConfig.timeout).toBe(5000);
|
||||
|
||||
httpClient.updateConfig({ timeout: 8000 });
|
||||
const updatedConfig = httpClient.getConfig();
|
||||
expect(updatedConfig.timeout).toBe(8000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTTP错误处理', () => {
|
||||
test('HttpError应该正确初始化', () => {
|
||||
const error = new HttpError(
|
||||
'Test error',
|
||||
404,
|
||||
'Not Found',
|
||||
{ detail: 'Resource not found' },
|
||||
'RESOURCE_NOT_FOUND'
|
||||
);
|
||||
|
||||
expect(error.message).toBe('Test error');
|
||||
expect(error.status).toBe(404);
|
||||
expect(error.statusText).toBe('Not Found');
|
||||
expect(error.data).toEqual({ detail: 'Resource not found' });
|
||||
expect(error.code).toBe('RESOURCE_NOT_FOUND');
|
||||
expect(error.name).toBe('HttpError');
|
||||
});
|
||||
|
||||
test('HttpError应该有正确的堆栈跟踪', () => {
|
||||
const error = new HttpError('Test error');
|
||||
expect(error.stack).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('工具函数', () => {
|
||||
// 这里可以添加对私有方法的测试(如果需要的话)
|
||||
// 通常通过公共接口间接测试私有逻辑
|
||||
});
|
||||
655
sdk/frontend/unified-login-sdk/src/core/http.ts
Normal file
655
sdk/frontend/unified-login-sdk/src/core/http.ts
Normal file
@@ -0,0 +1,655 @@
|
||||
/**
|
||||
* HTTP客户端
|
||||
* 用于与后端API进行通信,支持拦截器、超时控制等功能
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTTP请求方法类型
|
||||
*/
|
||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
||||
|
||||
/**
|
||||
* HTTP客户端配置
|
||||
*/
|
||||
export interface HttpClientConfig {
|
||||
/** 基础URL */
|
||||
baseURL?: string;
|
||||
/** 默认请求头 */
|
||||
headers?: Record<string, string>;
|
||||
/** 超时时间(毫秒) */
|
||||
timeout?: number;
|
||||
/** 是否携带凭证 */
|
||||
withCredentials?: boolean;
|
||||
/** 租户ID */
|
||||
tenantId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP请求选项
|
||||
*/
|
||||
export interface HttpRequestOptions {
|
||||
/** 请求方法 */
|
||||
method: HttpMethod;
|
||||
/** 请求URL */
|
||||
url: string;
|
||||
/** 请求头 */
|
||||
headers?: Record<string, string>;
|
||||
/** 请求体 */
|
||||
body?: any;
|
||||
/** 是否需要认证 */
|
||||
needAuth?: boolean;
|
||||
/** 超时时间 */
|
||||
timeout?: number;
|
||||
/** 是否携带凭证 */
|
||||
withCredentials?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP响应类型
|
||||
*/
|
||||
export interface HttpResponse<T = any> {
|
||||
/** 状态码 */
|
||||
status: number;
|
||||
/** 状态文本 */
|
||||
statusText: string;
|
||||
/** 响应体 */
|
||||
data: T;
|
||||
/** 响应头 */
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求拦截器
|
||||
*/
|
||||
export interface RequestInterceptor {
|
||||
/** 请求前拦截 */
|
||||
onRequest?: (options: HttpRequestOptions) => HttpRequestOptions | Promise<HttpRequestOptions>;
|
||||
/** 请求错误拦截 */
|
||||
onRequestError?: (error: any) => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应拦截器
|
||||
*/
|
||||
export interface ResponseInterceptor<T = any> {
|
||||
/** 响应后拦截 */
|
||||
onResponse?: (response: HttpResponse<T>) => HttpResponse<T> | Promise<HttpResponse<T>>;
|
||||
/** 响应错误拦截 */
|
||||
onResponseError?: (error: HttpError) => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP错误类型
|
||||
*/
|
||||
export class HttpError extends Error {
|
||||
/** 状态码 */
|
||||
public status: number;
|
||||
/** 状态文本 */
|
||||
public statusText: string;
|
||||
/** 错误数据 */
|
||||
public data: any;
|
||||
/** 错误代码 */
|
||||
public code?: string;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 错误信息
|
||||
* @param status 状态码
|
||||
* @param statusText 状态文本
|
||||
* @param data 错误数据
|
||||
* @param code 错误代码
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
status: number = 0,
|
||||
statusText: string = 'Unknown Error',
|
||||
data: any = null,
|
||||
code?: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'HttpError';
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.data = data;
|
||||
this.code = code;
|
||||
|
||||
// 保持堆栈跟踪
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, HttpError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP客户端类
|
||||
*/
|
||||
export class HttpClient {
|
||||
private config: Required<HttpClientConfig>;
|
||||
private tokenGetter?: () => string | null;
|
||||
private requestInterceptors: RequestInterceptor[] = [];
|
||||
private responseInterceptors: ResponseInterceptor[] = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param config 客户端配置
|
||||
*/
|
||||
constructor(config: HttpClientConfig = {}) {
|
||||
this.config = {
|
||||
baseURL: '',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 10000,
|
||||
withCredentials: true,
|
||||
tenantId: '',
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Token获取函数
|
||||
* @param tokenGetter Token获取函数
|
||||
*/
|
||||
setTokenGetter(tokenGetter: () => string | null): void {
|
||||
this.tokenGetter = tokenGetter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户ID
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
setTenantId(tenantId?: string): void {
|
||||
this.config.tenantId = tenantId || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加请求拦截器
|
||||
* @param interceptor 请求拦截器
|
||||
*/
|
||||
addRequestInterceptor(interceptor: RequestInterceptor): void {
|
||||
this.requestInterceptors.push(interceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加响应拦截器
|
||||
* @param interceptor 响应拦截器
|
||||
*/
|
||||
addResponseInterceptor(interceptor: ResponseInterceptor): void {
|
||||
this.responseInterceptors.push(interceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除请求拦截器
|
||||
* @param interceptor 要移除的拦截器
|
||||
*/
|
||||
removeRequestInterceptor(interceptor: RequestInterceptor): void {
|
||||
const index = this.requestInterceptors.indexOf(interceptor);
|
||||
if (index > -1) {
|
||||
this.requestInterceptors.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除响应拦截器
|
||||
* @param interceptor 要移除的拦截器
|
||||
*/
|
||||
removeResponseInterceptor(interceptor: ResponseInterceptor): void {
|
||||
const index = this.responseInterceptors.indexOf(interceptor);
|
||||
if (index > -1) {
|
||||
this.responseInterceptors.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>> {
|
||||
// 合并配置
|
||||
const mergedOptions = this.mergeOptions(options);
|
||||
|
||||
try {
|
||||
// 执行请求拦截器
|
||||
let processedOptions = await this.executeRequestInterceptors(mergedOptions);
|
||||
|
||||
// 构建完整URL
|
||||
const fullUrl = this.buildFullUrl(processedOptions.url);
|
||||
|
||||
// 构建请求配置
|
||||
const fetchOptions = this.buildFetchOptions(processedOptions);
|
||||
|
||||
// 发送请求(包含超时控制)
|
||||
const response = await this.fetchWithTimeout(
|
||||
fullUrl,
|
||||
fetchOptions,
|
||||
processedOptions.timeout ?? this.config.timeout
|
||||
);
|
||||
|
||||
const responseData = await this.parseResponse(response);
|
||||
|
||||
// 构建响应对象
|
||||
let httpResponse: HttpResponse<T> = {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: responseData,
|
||||
headers: this.parseHeaders(response.headers)
|
||||
};
|
||||
|
||||
// 处理业务逻辑
|
||||
httpResponse = this.handleBusinessLogic(httpResponse);
|
||||
|
||||
// 执行响应拦截器
|
||||
httpResponse = await this.executeResponseInterceptors(httpResponse);
|
||||
|
||||
return httpResponse;
|
||||
|
||||
} catch (error) {
|
||||
// 执行响应错误拦截器
|
||||
return this.executeResponseErrorInterceptors(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 合并请求选项与默认配置
|
||||
* @param options 请求选项
|
||||
* @returns 合并后的选项
|
||||
*/
|
||||
private mergeOptions(options: HttpRequestOptions): HttpRequestOptions {
|
||||
return {
|
||||
method: options.method,
|
||||
url: options.url,
|
||||
headers: {
|
||||
...this.config.headers,
|
||||
...options.headers
|
||||
},
|
||||
body: options.body,
|
||||
needAuth: options.needAuth ?? true,
|
||||
timeout: options.timeout ?? this.config.timeout,
|
||||
withCredentials: options.withCredentials ?? this.config.withCredentials
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整URL
|
||||
* @param url 相对URL或绝对URL
|
||||
* @returns 完整URL
|
||||
*/
|
||||
private buildFullUrl(url: string): string {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
return this.config.baseURL ? `${this.config.baseURL}${url}` : url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建fetch选项
|
||||
* @param options 处理后的请求选项
|
||||
* @returns fetch配置
|
||||
*/
|
||||
private buildFetchOptions(options: HttpRequestOptions): RequestInit {
|
||||
const requestHeaders: Record<string, string> = { ...options.headers };
|
||||
|
||||
// 添加认证头
|
||||
if (options.needAuth && this.tokenGetter) {
|
||||
const token = this.tokenGetter();
|
||||
if (token) {
|
||||
requestHeaders.Authorization = token;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加租户ID头
|
||||
if (this.config.tenantId) {
|
||||
requestHeaders['tenant-id'] = this.config.tenantId;
|
||||
}
|
||||
|
||||
const fetchOptions: RequestInit = {
|
||||
method: options.method,
|
||||
headers: requestHeaders,
|
||||
credentials: options.withCredentials ? 'include' : 'omit'
|
||||
};
|
||||
|
||||
// 添加请求体
|
||||
if (options.body && ['POST', 'PUT', 'PATCH'].includes(options.method)) {
|
||||
fetchOptions.body = typeof options.body === 'string'
|
||||
? options.body
|
||||
: JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
return fetchOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带超时控制的fetch
|
||||
* @param url 请求URL
|
||||
* @param options fetch选项
|
||||
* @param timeout 超时时间
|
||||
* @returns Promise<Response>
|
||||
*/
|
||||
private async fetchWithTimeout(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
timeout: number
|
||||
): Promise<Response> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: controller.signal
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw new HttpError(
|
||||
`Request timeout after ${timeout}ms`,
|
||||
408,
|
||||
'Request Timeout',
|
||||
null,
|
||||
'TIMEOUT_ERROR'
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务逻辑
|
||||
* @param response 响应对象
|
||||
* @returns 处理后的响应
|
||||
*/
|
||||
private handleBusinessLogic<T = any>(response: HttpResponse<T>): HttpResponse<T> {
|
||||
const { data, status } = response;
|
||||
|
||||
// 检查是否为业务响应结构 { code, msg, data }
|
||||
if (this.isBusinessResponse(data)) {
|
||||
const { code, msg, data: businessData } = data;
|
||||
|
||||
// 检查业务状态码
|
||||
if (code !== 0 && code !== 200 && code !== '0' && code !== '200') {
|
||||
// 业务错误
|
||||
throw new HttpError(
|
||||
msg || `Business Error: ${code}`,
|
||||
status,
|
||||
response.statusText,
|
||||
data,
|
||||
`BUSINESS_ERROR_${code}`
|
||||
);
|
||||
}
|
||||
|
||||
// 业务成功,返回data字段
|
||||
return {
|
||||
...response,
|
||||
data: businessData as T
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为业务响应结构
|
||||
* @param data 响应数据
|
||||
* @returns boolean 是否为业务响应结构
|
||||
*/
|
||||
private isBusinessResponse(data: any): data is { code: any; msg: string; data: any } {
|
||||
return (
|
||||
typeof data === 'object' &&
|
||||
data !== null &&
|
||||
('code' in data) &&
|
||||
('msg' in data) &&
|
||||
('data' in data)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求拦截器
|
||||
* @param options 请求选项
|
||||
* @returns 处理后的请求选项
|
||||
*/
|
||||
private async executeRequestInterceptors(options: HttpRequestOptions): Promise<HttpRequestOptions> {
|
||||
let processedOptions = options;
|
||||
|
||||
for (const interceptor of this.requestInterceptors) {
|
||||
try {
|
||||
if (interceptor.onRequest) {
|
||||
processedOptions = await interceptor.onRequest(processedOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
if (interceptor.onRequestError) {
|
||||
interceptor.onRequestError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return processedOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行响应拦截器
|
||||
* @param response 响应对象
|
||||
* @returns 处理后的响应对象
|
||||
*/
|
||||
private async executeResponseInterceptors<T = any>(response: HttpResponse<T>): Promise<HttpResponse<T>> {
|
||||
let processedResponse = response;
|
||||
|
||||
for (const interceptor of this.responseInterceptors) {
|
||||
try {
|
||||
if (interceptor.onResponse) {
|
||||
processedResponse = await interceptor.onResponse(processedResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
if (interceptor.onResponseError) {
|
||||
return interceptor.onResponseError(error as HttpError);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return processedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行响应错误拦截器
|
||||
* @param error 错误对象
|
||||
* @returns Promise<never>
|
||||
*/
|
||||
private executeResponseErrorInterceptors(error: any): never {
|
||||
let processedError = error;
|
||||
|
||||
for (const interceptor of this.responseInterceptors) {
|
||||
if (interceptor.onResponseError) {
|
||||
try {
|
||||
processedError = interceptor.onResponseError(processedError);
|
||||
} catch (interceptorError) {
|
||||
processedError = interceptorError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (processedError instanceof HttpError) {
|
||||
throw processedError;
|
||||
}
|
||||
|
||||
// 转换为HttpError
|
||||
throw new HttpError(
|
||||
processedError instanceof Error ? processedError.message : 'Unknown Error',
|
||||
0,
|
||||
'Unknown Error',
|
||||
null,
|
||||
'UNKNOWN_ERROR'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* HEAD请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async head<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'HEAD',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OPTIONS请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async options<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'OPTIONS',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async get<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'GET',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async post<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'POST',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async put<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'PUT',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE请求
|
||||
* @param url 请求URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async delete<T = any>(url: string, options?: Omit<HttpRequestOptions, 'method' | 'url'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH请求
|
||||
* @param url 请求URL
|
||||
* @param body 请求体
|
||||
* @param options 请求选项
|
||||
* @returns Promise<HttpResponse<T>> 响应结果
|
||||
*/
|
||||
async patch<T = any>(url: string, body?: any, options?: Omit<HttpRequestOptions, 'method' | 'url' | 'body'>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({
|
||||
method: 'PATCH',
|
||||
url,
|
||||
body,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带基础URL的客户端实例
|
||||
* @param baseURL 基础URL
|
||||
* @param config 其他配置
|
||||
* @returns HttpClient 新的客户端实例
|
||||
*/
|
||||
static create(baseURL: string, config?: Omit<HttpClientConfig, 'baseURL'>): HttpClient {
|
||||
return new HttpClient({
|
||||
baseURL,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前配置
|
||||
* @returns HttpClientConfig 当前配置
|
||||
*/
|
||||
getConfig(): HttpClientConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
* @param config 新的配置
|
||||
*/
|
||||
updateConfig(config: Partial<HttpClientConfig>): void {
|
||||
this.config = {
|
||||
...this.config,
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析响应体
|
||||
* @param response 响应对象
|
||||
* @returns Promise<any> 解析后的响应体
|
||||
*/
|
||||
private async parseResponse(response: Response): Promise<any> {
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
return response.json();
|
||||
} else if (contentType.includes('text/')) {
|
||||
return response.text();
|
||||
} else {
|
||||
return response.blob();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析响应头
|
||||
* @param headers 响应头对象
|
||||
* @returns Record<string, string> 解析后的响应头
|
||||
*/
|
||||
private parseHeaders(headers: Headers): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
headers.forEach((value, key) => {
|
||||
result[key] = value;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
92
sdk/frontend/unified-login-sdk/src/core/token.ts
Normal file
92
sdk/frontend/unified-login-sdk/src/core/token.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Token管理模块
|
||||
* 负责Token的存储、获取、刷新和过期处理
|
||||
*/
|
||||
|
||||
import { Storage } from '../utils/storage';
|
||||
|
||||
/**
|
||||
* Token存储键名常量
|
||||
*/
|
||||
const TOKEN_KEY_PREFIX = 'token_';
|
||||
|
||||
/**
|
||||
* Token管理类
|
||||
* 提供Token的增删改查功能
|
||||
*/
|
||||
export class TokenManager {
|
||||
private readonly storage: Storage;
|
||||
private readonly tokenKey: string;
|
||||
private readonly fullTokenKey: string;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
* @param clientId 客户端ID,用于生成唯一的Token键名
|
||||
*/
|
||||
constructor(storage: Storage, clientId: string) {
|
||||
if (!storage) {
|
||||
throw new Error('Storage instance is required');
|
||||
}
|
||||
|
||||
if (!clientId || typeof clientId !== 'string') {
|
||||
throw new Error('Client ID must be a non-empty string');
|
||||
}
|
||||
|
||||
this.storage = storage;
|
||||
this.tokenKey = clientId;
|
||||
this.fullTokenKey = `${TOKEN_KEY_PREFIX}${clientId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储Token信息
|
||||
* @param token Token字符串
|
||||
* @throws {Error} 当token为空或非字符串时抛出错误
|
||||
*/
|
||||
saveToken(token: string): void {
|
||||
if (!token) {
|
||||
throw new Error('Token must be a non-empty string');
|
||||
}
|
||||
|
||||
this.storage.set(this.fullTokenKey, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token信息
|
||||
* @returns string | null 返回Token字符串或null
|
||||
*/
|
||||
getToken(): string | null {
|
||||
return this.storage.get(this.fullTokenKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除Token信息
|
||||
*/
|
||||
clearToken(): void {
|
||||
this.storage.remove(this.fullTokenKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Token是否存在
|
||||
* @returns boolean Token是否存在
|
||||
*/
|
||||
hasToken(): boolean {
|
||||
return this.getToken() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端ID
|
||||
* @returns string 客户端ID
|
||||
*/
|
||||
getClientId(): string {
|
||||
return this.tokenKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整的Token键名
|
||||
* @returns string 完整的Token键名
|
||||
*/
|
||||
getFullTokenKey(): string {
|
||||
return this.fullTokenKey;
|
||||
}
|
||||
}
|
||||
281
sdk/frontend/unified-login-sdk/src/guards/router.examples.ts
Normal file
281
sdk/frontend/unified-login-sdk/src/guards/router.examples.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* 路由守卫使用示例
|
||||
*/
|
||||
|
||||
import { RouterGuard, RouterGuardOptions } from './router';
|
||||
import { Auth } from '../core/auth';
|
||||
import { Storage } from '../utils/storage';
|
||||
|
||||
// 基础使用示例
|
||||
export function basicUsageExample() {
|
||||
// 创建存储实例
|
||||
const storage = new Storage('localStorage');
|
||||
|
||||
// 创建认证实例
|
||||
const auth = new Auth(storage);
|
||||
|
||||
// 初始化认证配置
|
||||
auth.init({
|
||||
clientId: 'your-client-id',
|
||||
basepath: 'https://api.example.com',
|
||||
homePage: '/dashboard',
|
||||
idpLogoutUrl: 'https://idp.example.com/logout'
|
||||
} as any);
|
||||
|
||||
// 创建路由守卫
|
||||
const routerGuard = new RouterGuard(auth);
|
||||
|
||||
return routerGuard;
|
||||
}
|
||||
|
||||
// Vue路由配置示例
|
||||
export function vueRouterConfigExample() {
|
||||
// 在Vue项目中的路由配置
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: 'HomeView', // 示例组件名
|
||||
meta: {
|
||||
auth: {
|
||||
requiresAuth: false // 首页不需要认证
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: 'DashboardView', // 示例组件名
|
||||
meta: {
|
||||
auth: {
|
||||
requiresAuth: true, // 需要登录
|
||||
redirectUri: '/login' // 登录页面
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'Admin',
|
||||
component: 'AdminView', // 示例组件名
|
||||
meta: {
|
||||
auth: {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['admin:access'], // 需要管理员权限
|
||||
unauthorizedRedirectUri: '/unauthorized' // 权限不足跳转页面
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/user-management',
|
||||
name: 'UserManagement',
|
||||
component: 'UserManagementView', // 示例组件名
|
||||
meta: {
|
||||
auth: {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['user:read', 'user:write'], // 需要多个权限
|
||||
requireAllPermissions: true, // 需要所有权限
|
||||
requiredRoles: ['admin'], // 需要管理员角色
|
||||
requireAllRoles: true // 需要所有角色
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
component: 'ProfileView', // 示例组件名
|
||||
meta: {
|
||||
auth: {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['profile:view', 'profile:edit'], // 多个权限
|
||||
requireAllPermissions: false // 任一权限即可
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
// Vue路由守卫集成示例
|
||||
export function vueRouterIntegrationExample() {
|
||||
// 创建路由守卫实例
|
||||
const storage = new Storage('localStorage');
|
||||
const auth = new Auth(storage);
|
||||
const routerGuard = new RouterGuard(auth);
|
||||
|
||||
// Vue Router 3.x 配置
|
||||
const routerConfigV3 = {
|
||||
routes: vueRouterConfigExample(),
|
||||
beforeEach: routerGuard.createVueGuard()
|
||||
};
|
||||
|
||||
// Vue Router 4.x 配置
|
||||
const routerConfigV4 = {
|
||||
routes: vueRouterConfigExample(),
|
||||
beforeEnter: routerGuard.createVueGuard()
|
||||
};
|
||||
|
||||
return { routerConfigV3, routerConfigV4 };
|
||||
}
|
||||
|
||||
// 手动权限检查示例
|
||||
export async function manualPermissionCheckExample(routerGuard: RouterGuard) {
|
||||
try {
|
||||
// 检查单个权限
|
||||
const hasReadPermission = await routerGuard.hasPermission('user:read');
|
||||
console.log('Has read permission:', hasReadPermission);
|
||||
|
||||
// 检查多个权限(需要全部)
|
||||
const hasAllPermissions = await routerGuard.hasPermission(
|
||||
['user:read', 'user:write'],
|
||||
true
|
||||
);
|
||||
console.log('Has all permissions:', hasAllPermissions);
|
||||
|
||||
// 检查多个权限(需要任一)
|
||||
const hasAnyPermission = await routerGuard.hasPermission(
|
||||
['user:read', 'admin:access'],
|
||||
false
|
||||
);
|
||||
console.log('Has any permission:', hasAnyPermission);
|
||||
|
||||
// 检查角色
|
||||
const hasAdminRole = await routerGuard.hasRole('admin');
|
||||
console.log('Has admin role:', hasAdminRole);
|
||||
|
||||
// 获取当前用户
|
||||
const currentUser = routerGuard.getCurrentUser();
|
||||
console.log('Current user:', currentUser);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Permission check failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 高级配置示例
|
||||
export function advancedConfigurationExample() {
|
||||
const storage = new Storage('localStorage');
|
||||
const auth = new Auth(storage);
|
||||
const routerGuard = new RouterGuard(auth);
|
||||
|
||||
// 自定义路由守卫选项
|
||||
const customGuardOptions: RouterGuardOptions = {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['custom:permission'],
|
||||
requiredRoles: ['custom:role'],
|
||||
requireAllPermissions: true,
|
||||
requireAllRoles: false,
|
||||
redirectUri: '/custom-login',
|
||||
unauthorizedRedirectUri: '/custom-forbidden'
|
||||
};
|
||||
|
||||
return { routerGuard, customGuardOptions };
|
||||
}
|
||||
|
||||
// 错误处理示例
|
||||
export async function errorHandlingExample(routerGuard: RouterGuard) {
|
||||
try {
|
||||
// 执行权限检查
|
||||
const result = await routerGuard.check({
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['some:permission']
|
||||
});
|
||||
|
||||
if (!result.allowed) {
|
||||
switch (result.reason) {
|
||||
case 'NOT_AUTHENTICATED':
|
||||
console.log('用户未认证,将跳转到登录页');
|
||||
break;
|
||||
case 'INSUFFICIENT_PERMISSIONS':
|
||||
console.log('权限不足:', result.details);
|
||||
break;
|
||||
case 'INSUFFICIENT_ROLES':
|
||||
console.log('角色不足:', result.details);
|
||||
break;
|
||||
default:
|
||||
console.log('访问被拒绝');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('路由守卫检查出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 实际应用示例 - 完整的Vue组件集成
|
||||
export class SecureRouteManager {
|
||||
private routerGuard: RouterGuard;
|
||||
private auth: Auth;
|
||||
|
||||
constructor() {
|
||||
const storage = new Storage('localStorage');
|
||||
this.auth = new Auth(storage);
|
||||
this.routerGuard = new RouterGuard(this.auth);
|
||||
}
|
||||
|
||||
// 初始化SDK
|
||||
initializeSDK(config: any) {
|
||||
this.auth.init(config);
|
||||
}
|
||||
|
||||
// 获取Vue路由守卫
|
||||
getVueRouterGuard() {
|
||||
return this.routerGuard.createVueGuard();
|
||||
}
|
||||
|
||||
// 手动检查路由权限
|
||||
async checkRouteAccess(routeMeta: any) {
|
||||
const options: RouterGuardOptions = routeMeta?.auth || {};
|
||||
return await this.routerGuard.check(options);
|
||||
}
|
||||
|
||||
// 检查特定权限
|
||||
async checkSpecificPermission(permission: string) {
|
||||
return await this.routerGuard.hasPermission(permission);
|
||||
}
|
||||
|
||||
// 检查特定角色
|
||||
async checkSpecificRole(role: string) {
|
||||
return await this.routerGuard.hasRole(role);
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
getCurrentUser() {
|
||||
return this.routerGuard.getCurrentUser();
|
||||
}
|
||||
|
||||
// 处理认证状态变化
|
||||
setupAuthListeners() {
|
||||
// 监听登录事件
|
||||
this.auth.on('login', () => {
|
||||
console.log('用户已登录');
|
||||
// 可以在这里刷新路由权限等
|
||||
});
|
||||
|
||||
// 监听登出事件
|
||||
this.auth.on('logout', () => {
|
||||
console.log('用户已登出');
|
||||
// 可以在这里清理相关状态
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
export function usageDemo() {
|
||||
// 1. 基础使用
|
||||
const routerGuard = basicUsageExample();
|
||||
|
||||
// 2. 手动权限检查
|
||||
manualPermissionCheckExample(routerGuard);
|
||||
|
||||
// 3. 获取路由配置
|
||||
const routes = vueRouterConfigExample();
|
||||
console.log('路由配置:', routes);
|
||||
|
||||
// 4. 创建安全路由管理器
|
||||
const secureManager = new SecureRouteManager();
|
||||
|
||||
// 5. 设置监听器
|
||||
secureManager.setupAuthListeners();
|
||||
|
||||
return { routerGuard, secureManager };
|
||||
}
|
||||
358
sdk/frontend/unified-login-sdk/src/guards/router.test.ts
Normal file
358
sdk/frontend/unified-login-sdk/src/guards/router.test.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 路由守卫测试文件
|
||||
*/
|
||||
|
||||
import { RouterGuard, RouterGuardOptions } from './router';
|
||||
import { Auth } from '../core/auth';
|
||||
import { Storage } from '../utils/storage';
|
||||
|
||||
// Mock Auth类
|
||||
class MockAuth {
|
||||
private _isAuthenticated = false;
|
||||
private _userInfo: any = null;
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
return this._isAuthenticated;
|
||||
}
|
||||
|
||||
setAuthenticated(authenticated: boolean): void {
|
||||
this._isAuthenticated = authenticated;
|
||||
}
|
||||
|
||||
getUserInfo(): any {
|
||||
return this._userInfo;
|
||||
}
|
||||
|
||||
setUserInfo(userInfo: any): void {
|
||||
this._userInfo = userInfo;
|
||||
}
|
||||
|
||||
async login(redirectUri?: string): Promise<void> {
|
||||
// Mock login implementation
|
||||
console.log('Mock login called with redirectUri:', redirectUri);
|
||||
}
|
||||
|
||||
async hasPermission(permission: string | string[]): Promise<boolean> {
|
||||
if (!this._isAuthenticated || !this._userInfo) return false;
|
||||
const permissions = Array.isArray(permission) ? permission : [permission];
|
||||
const userPermissions = this._userInfo.permissions || [];
|
||||
return permissions.some(p => userPermissions.includes(p));
|
||||
}
|
||||
|
||||
async hasAllPermissions(permissions: string[]): Promise<boolean> {
|
||||
if (!this._isAuthenticated || !this._userInfo) return false;
|
||||
const userPermissions = this._userInfo.permissions || [];
|
||||
return permissions.every(p => userPermissions.includes(p));
|
||||
}
|
||||
|
||||
async hasRole(role: string | string[]): Promise<boolean> {
|
||||
if (!this._isAuthenticated || !this._userInfo) return false;
|
||||
const roles = Array.isArray(role) ? role : [role];
|
||||
const userRoles = this._userInfo.roles || [];
|
||||
return roles.some(r => userRoles.includes(r));
|
||||
}
|
||||
|
||||
async hasAllRoles(roles: string[]): Promise<boolean> {
|
||||
if (!this._isAuthenticated || !this._userInfo) return false;
|
||||
const userRoles = this._userInfo.roles || [];
|
||||
return roles.every(r => userRoles.includes(r));
|
||||
}
|
||||
}
|
||||
|
||||
describe('RouterGuard', () => {
|
||||
let routerGuard: RouterGuard;
|
||||
let mockAuth: MockAuth;
|
||||
let storage: Storage;
|
||||
|
||||
beforeEach(() => {
|
||||
storage = new Storage('localStorage');
|
||||
mockAuth = new MockAuth() as any;
|
||||
routerGuard = new RouterGuard(mockAuth as unknown as Auth);
|
||||
});
|
||||
|
||||
describe('构造函数', () => {
|
||||
test('应该正确初始化', () => {
|
||||
expect(routerGuard).toBeInstanceOf(RouterGuard);
|
||||
});
|
||||
|
||||
test('应该在没有Auth实例时抛出错误', () => {
|
||||
expect(() => new RouterGuard(null as any)).toThrow('Auth instance is required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('权限检查', () => {
|
||||
test('应该允许不需要认证的路由', async () => {
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: false
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(true);
|
||||
expect(result.reason).toBeUndefined();
|
||||
});
|
||||
|
||||
test('应该拒绝未认证用户的访问', async () => {
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.reason).toBe('NOT_AUTHENTICATED');
|
||||
});
|
||||
|
||||
test('应该允许已认证用户的访问', async () => {
|
||||
// 设置用户为已认证状态
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user'],
|
||||
roles: ['user']
|
||||
});
|
||||
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
test('应该检查单个权限', async () => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user', 'write:user'],
|
||||
roles: ['user']
|
||||
});
|
||||
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['read:user']
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
test('应该拒绝缺少权限的访问', async () => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user'],
|
||||
roles: ['user']
|
||||
});
|
||||
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['write:user']
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.reason).toBe('INSUFFICIENT_PERMISSIONS');
|
||||
});
|
||||
|
||||
test('应该检查多个权限(需要全部)', async () => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user', 'write:user', 'delete:user'],
|
||||
roles: ['admin']
|
||||
});
|
||||
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['read:user', 'write:user'],
|
||||
requireAllPermissions: true
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
test('应该检查多个权限(需要任一)', async () => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user'],
|
||||
roles: ['user']
|
||||
});
|
||||
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true,
|
||||
requiredPermissions: ['read:user', 'write:user'],
|
||||
requireAllPermissions: false
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
test('应该检查角色', async () => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user'],
|
||||
roles: ['admin', 'user']
|
||||
});
|
||||
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true,
|
||||
requiredRoles: ['admin']
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
test('应该拒绝缺少角色的访问', async () => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user'],
|
||||
roles: ['user']
|
||||
});
|
||||
|
||||
const options: RouterGuardOptions = {
|
||||
requiresAuth: true,
|
||||
requiredRoles: ['admin']
|
||||
};
|
||||
|
||||
const result = await routerGuard.check(options);
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.reason).toBe('INSUFFICIENT_ROLES');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasPermission方法', () => {
|
||||
beforeEach(() => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user', 'write:user'],
|
||||
roles: ['user']
|
||||
});
|
||||
});
|
||||
|
||||
test('应该检查单个权限', async () => {
|
||||
const result = await routerGuard.hasPermission('read:user');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('应该检查多个权限(需要全部)', async () => {
|
||||
const result = await routerGuard.hasPermission(['read:user', 'write:user'], true);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('应该检查多个权限(需要任一)', async () => {
|
||||
const result = await routerGuard.hasPermission(['read:user', 'delete:user'], false);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('应该在未认证时返回false', async () => {
|
||||
mockAuth.setAuthenticated(false);
|
||||
const result = await routerGuard.hasPermission('read:user');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test('应该在空权限时返回true', async () => {
|
||||
const result = await routerGuard.hasPermission([]);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('应该在无权限参数时返回true', async () => {
|
||||
const result = await routerGuard.hasPermission(null as any);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasRole方法', () => {
|
||||
beforeEach(() => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user'],
|
||||
roles: ['admin', 'user']
|
||||
});
|
||||
});
|
||||
|
||||
test('应该检查单个角色', async () => {
|
||||
const result = await routerGuard.hasRole('admin');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('应该检查多个角色(需要全部)', async () => {
|
||||
const result = await routerGuard.hasRole(['admin', 'user'], true);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('应该检查多个角色(需要任一)', async () => {
|
||||
const result = await routerGuard.hasRole(['admin', 'superuser'], false);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('应该在未认证时返回false', async () => {
|
||||
mockAuth.setAuthenticated(false);
|
||||
const result = await routerGuard.hasRole('admin');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentUser方法', () => {
|
||||
test('应该返回当前用户信息', () => {
|
||||
const userInfo = { userId: '1', username: 'test' };
|
||||
mockAuth.setUserInfo(userInfo);
|
||||
|
||||
const result = routerGuard.getCurrentUser();
|
||||
expect(result).toEqual(userInfo);
|
||||
});
|
||||
|
||||
test('应该在获取用户信息失败时返回null', () => {
|
||||
// Mock getUserInfo 抛出异常
|
||||
jest.spyOn(mockAuth, 'getUserInfo').mockImplementation(() => {
|
||||
throw new Error('Failed to get user info');
|
||||
});
|
||||
|
||||
const result = routerGuard.getCurrentUser();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vue路由守卫', () => {
|
||||
test('应该创建有效的路由守卫函数', () => {
|
||||
const guard = routerGuard.createVueGuard();
|
||||
expect(typeof guard).toBe('function');
|
||||
});
|
||||
|
||||
test('应该正确处理允许的路由', async () => {
|
||||
mockAuth.setAuthenticated(true);
|
||||
mockAuth.setUserInfo({
|
||||
permissions: ['read:user'],
|
||||
roles: ['user']
|
||||
});
|
||||
|
||||
const guard = routerGuard.createVueGuard();
|
||||
const to = { meta: { auth: { requiresAuth: true } } };
|
||||
const from = {};
|
||||
const next = jest.fn();
|
||||
|
||||
await guard(to as any, from as any, next);
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('应该正确处理拒绝的路由', async () => {
|
||||
const guard = routerGuard.createVueGuard();
|
||||
const to = { meta: { auth: { requiresAuth: true } } };
|
||||
const from = {};
|
||||
const next = jest.fn();
|
||||
|
||||
await guard(to as any, from as any, next);
|
||||
expect(next).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test('应该处理路由守卫错误', async () => {
|
||||
// Mock check 方法抛出异常
|
||||
jest.spyOn(routerGuard, 'check').mockRejectedValue(new Error('Test error'));
|
||||
|
||||
const guard = routerGuard.createVueGuard();
|
||||
const to = { meta: { auth: {} } };
|
||||
const from = {};
|
||||
const next = jest.fn();
|
||||
|
||||
await guard(to as any, from as any, next);
|
||||
expect(next).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
311
sdk/frontend/unified-login-sdk/src/guards/router.ts
Normal file
311
sdk/frontend/unified-login-sdk/src/guards/router.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* 路由守卫模块
|
||||
* 提供基于权限的路由拦截和未登录自动跳转登录页功能
|
||||
*/
|
||||
|
||||
import { Auth } from '../core/auth';
|
||||
import { UserInfo } from '../types';
|
||||
|
||||
/**
|
||||
* 路由守卫选项
|
||||
*/
|
||||
export interface RouterGuardOptions {
|
||||
/**
|
||||
* 是否需要登录
|
||||
* @default true
|
||||
*/
|
||||
requiresAuth?: boolean;
|
||||
/**
|
||||
* 需要的权限列表
|
||||
*/
|
||||
requiredPermissions?: string[];
|
||||
/**
|
||||
* 需要的角色列表
|
||||
*/
|
||||
requiredRoles?: string[];
|
||||
/**
|
||||
* 登录后重定向的URL
|
||||
*/
|
||||
redirectUri?: string;
|
||||
/**
|
||||
* 权限不足时重定向的URL
|
||||
*/
|
||||
unauthorizedRedirectUri?: string;
|
||||
/**
|
||||
* 是否需要所有权限(true)还是任一权限(false)
|
||||
* @default true
|
||||
*/
|
||||
requireAllPermissions?: boolean;
|
||||
/**
|
||||
* 是否需要所有角色(true)还是任一角色(false)
|
||||
* @default true
|
||||
*/
|
||||
requireAllRoles?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由守卫检查结果
|
||||
*/
|
||||
export interface GuardCheckResult {
|
||||
/** 是否允许访问 */
|
||||
allowed: boolean;
|
||||
/** 拒绝原因 */
|
||||
reason?: 'NOT_AUTHENTICATED' | 'INSUFFICIENT_PERMISSIONS' | 'INSUFFICIENT_ROLES';
|
||||
/** 详细信息 */
|
||||
details?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue路由守卫函数类型
|
||||
*/
|
||||
type VueRouteGuard = (to: any, from: any, next: any) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 路由守卫类
|
||||
*/
|
||||
export class RouterGuard {
|
||||
private readonly auth: Auth;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param auth 认证实例
|
||||
*/
|
||||
constructor(auth: Auth) {
|
||||
if (!auth) {
|
||||
throw new Error('Auth instance is required');
|
||||
}
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路由权限
|
||||
* @param options 路由守卫选项
|
||||
* @returns Promise<GuardCheckResult> 权限检查结果
|
||||
*/
|
||||
async check(options: RouterGuardOptions): Promise<GuardCheckResult> {
|
||||
const {
|
||||
requiresAuth = true,
|
||||
requiredPermissions = [],
|
||||
requiredRoles = [],
|
||||
requireAllPermissions = true,
|
||||
requireAllRoles = true
|
||||
} = options;
|
||||
|
||||
// 如果不需要认证,直接允许
|
||||
if (!requiresAuth) {
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
// 未认证,触发登录流程
|
||||
try {
|
||||
await this.auth.login(options.redirectUri);
|
||||
} catch (error) {
|
||||
console.error('Failed to initiate login:', error);
|
||||
}
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'NOT_AUTHENTICATED',
|
||||
details: 'User is not authenticated'
|
||||
};
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if (requiredPermissions.length > 0) {
|
||||
const hasPermission = await this.checkPermissions(
|
||||
requiredPermissions,
|
||||
requireAllPermissions
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
this.handleUnauthorizedAccess(options.unauthorizedRedirectUri);
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'INSUFFICIENT_PERMISSIONS',
|
||||
details: `Missing required permissions: ${requiredPermissions.join(', ')}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 检查角色
|
||||
if (requiredRoles.length > 0) {
|
||||
const hasRole = await this.checkRoles(requiredRoles, requireAllRoles);
|
||||
|
||||
if (!hasRole) {
|
||||
this.handleUnauthorizedAccess(options.unauthorizedRedirectUri);
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'INSUFFICIENT_ROLES',
|
||||
details: `Missing required roles: ${requiredRoles.join(', ')}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Vue路由守卫
|
||||
* @returns Vue路由守卫函数
|
||||
*/
|
||||
createVueGuard(): VueRouteGuard {
|
||||
return async (to: any, from: any, next: any) => {
|
||||
try {
|
||||
// 从路由元信息中获取守卫选项
|
||||
const options: RouterGuardOptions = to.meta?.auth || {};
|
||||
|
||||
const result = await this.check(options);
|
||||
|
||||
if (result.allowed) {
|
||||
next();
|
||||
} else {
|
||||
// 根据拒绝原因决定如何处理
|
||||
switch (result.reason) {
|
||||
case 'NOT_AUTHENTICATED':
|
||||
// 登录已经在check方法中处理
|
||||
next(false);
|
||||
break;
|
||||
case 'INSUFFICIENT_PERMISSIONS':
|
||||
case 'INSUFFICIENT_ROLES':
|
||||
// 权限不足已经在check方法中处理
|
||||
next(false);
|
||||
break;
|
||||
default:
|
||||
next(false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Route guard error:', error);
|
||||
next(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否有权限访问资源
|
||||
* @param permissions 需要的权限列表
|
||||
* @param requireAll 是否需要所有权限
|
||||
* @returns Promise<boolean> 是否拥有权限
|
||||
*/
|
||||
async hasPermission(permissions: string | string[], requireAll: boolean = true): Promise<boolean> {
|
||||
if (!permissions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];
|
||||
|
||||
if (requiredPermissions.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用Auth类的内置权限检查方法
|
||||
try {
|
||||
if (requireAll) {
|
||||
return await this.auth.hasAllPermissions(requiredPermissions);
|
||||
} else {
|
||||
return await this.auth.hasPermission(requiredPermissions);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Permission check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否有指定角色
|
||||
* @param roles 需要的角色列表
|
||||
* @param requireAll 是否需要所有角色
|
||||
* @returns Promise<boolean> 是否拥有角色
|
||||
*/
|
||||
async hasRole(roles: string | string[], requireAll: boolean = true): Promise<boolean> {
|
||||
if (!roles) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const requiredRoles = Array.isArray(roles) ? roles : [roles];
|
||||
|
||||
if (requiredRoles.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否已认证
|
||||
if (!this.auth.isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用Auth类的内置角色检查方法
|
||||
try {
|
||||
if (requireAll) {
|
||||
return await this.auth.hasAllRoles(requiredRoles);
|
||||
} else {
|
||||
return await this.auth.hasRole(requiredRoles);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Role check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* @returns UserInfo | null 用户信息
|
||||
*/
|
||||
async getCurrentUser(): Promise<UserInfo | null> {
|
||||
try {
|
||||
return await this.auth.getUserInfo();
|
||||
} catch (error) {
|
||||
console.error('Failed to get user info:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查权限
|
||||
* @private
|
||||
*/
|
||||
private async checkPermissions(permissions: string[], requireAll: boolean): Promise<boolean> {
|
||||
try {
|
||||
return requireAll
|
||||
? await this.auth.hasAllPermissions(permissions)
|
||||
: await this.auth.hasPermission(permissions);
|
||||
} catch (error) {
|
||||
console.error('Permission validation failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查角色
|
||||
* @private
|
||||
*/
|
||||
private async checkRoles(roles: string[], requireAll: boolean): Promise<boolean> {
|
||||
try {
|
||||
return requireAll
|
||||
? await this.auth.hasAllRoles(roles)
|
||||
: await this.auth.hasRole(roles);
|
||||
} catch (error) {
|
||||
console.error('Role validation failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理未授权访问
|
||||
* @private
|
||||
*/
|
||||
private handleUnauthorizedAccess(redirectUri?: string): void {
|
||||
if (redirectUri) {
|
||||
try {
|
||||
window.location.href = redirectUri;
|
||||
} catch (error) {
|
||||
console.error('Failed to redirect to unauthorized page:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
sdk/frontend/unified-login-sdk/src/index.ts
Normal file
108
sdk/frontend/unified-login-sdk/src/index.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 统一登录SDK入口文件
|
||||
* 支持OAuth2授权码模式,提供完整的认证和权限管理功能
|
||||
*/
|
||||
|
||||
// 核心功能导出
|
||||
export { Auth } from './core/auth';
|
||||
export { TokenManager } from './core/token';
|
||||
export { HttpClient, HttpError } from './core/http';
|
||||
|
||||
// 工具类导出
|
||||
export { Storage } from './utils/storage';
|
||||
export { RouterGuard, RouterGuardOptions } from './guards/router';
|
||||
|
||||
// Vue集成导出
|
||||
export { VuePlugin, createVuePlugin, useAuth } from './plugins/vue';
|
||||
|
||||
// 工具函数导出
|
||||
export {
|
||||
generateRandomString,
|
||||
parseQueryParams,
|
||||
buildQueryParams,
|
||||
generateAuthorizationUrl,
|
||||
isCallbackUrl
|
||||
} from './utils/url';
|
||||
|
||||
// 类型定义导出
|
||||
export type {
|
||||
SDKConfig,
|
||||
UnifiedLoginSDK,
|
||||
UserInfo,
|
||||
RouterInfo,
|
||||
EventType
|
||||
} from './types';
|
||||
|
||||
// 创建默认SDK实例
|
||||
import { SDKConfig, UnifiedLoginSDK } from './types';
|
||||
import { Auth } from './core/auth';
|
||||
import { Storage } from './utils/storage';
|
||||
|
||||
// 全局单例实例
|
||||
let defaultSDK: UnifiedLoginSDK | null = null;
|
||||
let defaultAuth: Auth | null = null;
|
||||
|
||||
/**
|
||||
* 获取默认SDK实例
|
||||
* @returns UnifiedLoginSDK SDK实例
|
||||
*/
|
||||
function getDefaultSDK(): UnifiedLoginSDK {
|
||||
if (!defaultSDK) {
|
||||
const storage = new Storage();
|
||||
defaultAuth = new Auth(storage);
|
||||
|
||||
defaultSDK = {
|
||||
init: (config: SDKConfig) => defaultAuth!.init(config),
|
||||
getToken: () => defaultAuth!.getToken(),
|
||||
login: (redirectUri?: string) => defaultAuth!.login(redirectUri),
|
||||
logout: () => defaultAuth!.logout(),
|
||||
handleCallback: () => defaultAuth!.handleCallback(),
|
||||
getRoutes: () => defaultAuth!.getRoutes(),
|
||||
getUserInfo: () => defaultAuth!.getUserInfo(),
|
||||
refreshUserInfo: () => defaultAuth!.refreshUserInfo(),
|
||||
isAuthenticated: () => defaultAuth!.isAuthenticated(),
|
||||
hasRole: (role: string | string[]) => defaultAuth!.hasRole(role),
|
||||
hasAllRoles: (roles: string[]) => defaultAuth!.hasAllRoles(roles),
|
||||
hasPermission: (permission: string | string[]) => defaultAuth!.hasPermission(permission),
|
||||
hasAllPermissions: (permissions: string[]) => defaultAuth!.hasAllPermissions(permissions),
|
||||
on: (event, callback) => defaultAuth!.on(event, callback),
|
||||
off: (event, callback) => defaultAuth!.off(event, callback),
|
||||
isCallback: () => defaultAuth!.isCallback()
|
||||
};
|
||||
}
|
||||
|
||||
return defaultSDK!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认导出的SDK实例
|
||||
* 提供最简化的使用方式
|
||||
*/
|
||||
export const unifiedLoginSDK = getDefaultSDK();
|
||||
|
||||
/**
|
||||
* 默认导出
|
||||
*/
|
||||
export default unifiedLoginSDK;
|
||||
|
||||
// 版本信息
|
||||
export const VERSION = '1.0.0';
|
||||
export const version = VERSION;
|
||||
|
||||
// 便捷别名导出
|
||||
export {
|
||||
unifiedLoginSDK as sdk,
|
||||
unifiedLoginSDK as auth
|
||||
};
|
||||
|
||||
// 全局类型声明
|
||||
declare global {
|
||||
interface Window {
|
||||
unifiedLoginSDK: typeof unifiedLoginSDK;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果在浏览器环境中,将SDK挂载到window对象
|
||||
if (typeof window !== 'undefined') {
|
||||
window.unifiedLoginSDK = unifiedLoginSDK;
|
||||
}
|
||||
260
sdk/frontend/unified-login-sdk/src/plugins/vue.test.ts
Normal file
260
sdk/frontend/unified-login-sdk/src/plugins/vue.test.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Vue插件测试文件
|
||||
*/
|
||||
|
||||
import { VuePlugin, VuePluginOptions, createVuePlugin, useAuth } from './vue';
|
||||
import { Storage } from '../utils/storage';
|
||||
|
||||
// Mock Storage类
|
||||
class MockStorage {
|
||||
private data: Record<string, any> = {};
|
||||
|
||||
set(key: string, value: any): void {
|
||||
this.data[key] = value;
|
||||
}
|
||||
|
||||
get(key: string): any {
|
||||
return this.data[key];
|
||||
}
|
||||
|
||||
remove(key: string): void {
|
||||
delete this.data[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Mock Auth类
|
||||
class MockAuth {
|
||||
init(config: any): void {
|
||||
console.log('Auth initialized with config:', config);
|
||||
}
|
||||
|
||||
login(): void {
|
||||
console.log('Login called');
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
console.log('Logout called');
|
||||
}
|
||||
}
|
||||
|
||||
// Mock RouterGuard类
|
||||
class MockRouterGuard {
|
||||
createVueGuard() {
|
||||
return (to: any, from: any, next: any) => {
|
||||
console.log('Route guard called');
|
||||
next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Mock Vue 3 App
|
||||
class MockVue3App {
|
||||
config = {
|
||||
globalProperties: {} as Record<string, any>
|
||||
};
|
||||
|
||||
provide = jest.fn();
|
||||
mixin = jest.fn();
|
||||
use = jest.fn();
|
||||
}
|
||||
|
||||
// Mock Vue 2 Constructor
|
||||
class MockVue2Constructor {
|
||||
static prototype: Record<string, any> = {};
|
||||
static mixin = jest.fn();
|
||||
static use = jest.fn();
|
||||
}
|
||||
|
||||
describe('VuePlugin', () => {
|
||||
let storage: Storage;
|
||||
let vuePlugin: VuePlugin;
|
||||
|
||||
beforeEach(() => {
|
||||
storage = new MockStorage() as any;
|
||||
vuePlugin = new VuePlugin(storage);
|
||||
});
|
||||
|
||||
describe('构造函数', () => {
|
||||
test('应该正确初始化', () => {
|
||||
expect(vuePlugin).toBeInstanceOf(VuePlugin);
|
||||
expect(vuePlugin.getAuth()).toBeDefined();
|
||||
expect(vuePlugin.getRouterGuard()).toBeDefined();
|
||||
expect(vuePlugin.getStorage()).toBe(storage);
|
||||
});
|
||||
|
||||
test('应该在没有存储实例时抛出错误', () => {
|
||||
expect(() => new VuePlugin(null as any)).toThrow('Storage instance is required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('install方法', () => {
|
||||
test('应该在没有配置时抛出错误', () => {
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = { config: undefined as any };
|
||||
|
||||
expect(() => vuePlugin.install(app as any, options)).toThrow('SDK config is required');
|
||||
});
|
||||
|
||||
test('应该正确安装Vue 3插件', () => {
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any,
|
||||
pluginName: 'testLogin'
|
||||
};
|
||||
|
||||
vuePlugin.install(app as any, options);
|
||||
|
||||
// 检查全局属性
|
||||
expect(app.config.globalProperties.$testLogin).toBeDefined();
|
||||
expect(app.config.globalProperties.$auth).toBeDefined();
|
||||
|
||||
// 检查provide调用
|
||||
expect(app.provide).toHaveBeenCalledWith('testLogin', expect.anything());
|
||||
expect(app.provide).toHaveBeenCalledWith('auth', expect.anything());
|
||||
});
|
||||
|
||||
test('应该正确安装Vue 2插件', () => {
|
||||
const app = MockVue2Constructor;
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any,
|
||||
pluginName: 'testLogin'
|
||||
};
|
||||
|
||||
vuePlugin.install(app as any, options);
|
||||
|
||||
// 检查原型属性
|
||||
expect(app.prototype.$testLogin).toBeDefined();
|
||||
expect(app.prototype.$auth).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该防止重复初始化', () => {
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any
|
||||
};
|
||||
|
||||
// 第一次安装
|
||||
vuePlugin.install(app as any, options);
|
||||
|
||||
// 第二次安装应该输出警告
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
vuePlugin.install(app as any, options);
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith('Vue plugin is already initialized');
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('应该支持手动指定Vue版本', () => {
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any,
|
||||
vueVersion: 'vue2' // 强制使用Vue 2模式
|
||||
};
|
||||
|
||||
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
vuePlugin.install(app as any, options);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith('Unified Login SDK installed successfully (vue2)');
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getter方法', () => {
|
||||
test('应该正确返回各个实例', () => {
|
||||
expect(vuePlugin.getAuth()).toBeDefined();
|
||||
expect(vuePlugin.getRouterGuard()).toBeDefined();
|
||||
expect(vuePlugin.getStorage()).toBe(storage);
|
||||
expect(vuePlugin.getSDK()).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('辅助函数', () => {
|
||||
test('createVuePlugin应该创建插件实例', () => {
|
||||
const plugin = createVuePlugin('localStorage', 'test_prefix');
|
||||
expect(plugin).toBeInstanceOf(VuePlugin);
|
||||
expect(plugin.getStorage()).toBeInstanceOf(Storage);
|
||||
});
|
||||
|
||||
test('useAuth应该返回正确的composable对象', () => {
|
||||
const composable = useAuth(vuePlugin);
|
||||
|
||||
expect(composable.auth).toBe(vuePlugin.getAuth());
|
||||
expect(composable.routerGuard).toBe(vuePlugin.getRouterGuard());
|
||||
expect(composable.storage).toBe(vuePlugin.getStorage());
|
||||
expect(composable.sdk).toBe(vuePlugin.getSDK());
|
||||
});
|
||||
});
|
||||
|
||||
describe('路由守卫注册', () => {
|
||||
test('应该在启用时注册路由守卫', () => {
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any,
|
||||
autoRegisterGuards: true
|
||||
};
|
||||
|
||||
vuePlugin.install(app as any, options);
|
||||
expect(app.mixin).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('应该在禁用时不注册路由守卫', () => {
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any,
|
||||
autoRegisterGuards: false
|
||||
};
|
||||
|
||||
vuePlugin.install(app as any, options);
|
||||
expect(app.mixin).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vue版本检测', () => {
|
||||
test('应该正确检测Vue 3', () => {
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any
|
||||
};
|
||||
|
||||
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
vuePlugin.install(app as any, options);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('(vue3)'));
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('应该正确检测Vue 2', () => {
|
||||
const app = MockVue2Constructor;
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any
|
||||
};
|
||||
|
||||
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
vuePlugin.install(app as any, options);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('(vue2)'));
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误处理', () => {
|
||||
test('应该正确处理安装过程中的错误', () => {
|
||||
const storage = new MockStorage() as any;
|
||||
const plugin = new VuePlugin(storage);
|
||||
|
||||
// Mock auth.init 抛出错误
|
||||
const mockAuth = plugin.getAuth();
|
||||
jest.spyOn(mockAuth, 'init').mockImplementation(() => {
|
||||
throw new Error('Init failed');
|
||||
});
|
||||
|
||||
const app = new MockVue3App();
|
||||
const options: VuePluginOptions = {
|
||||
config: { clientId: 'test-client' } as any
|
||||
};
|
||||
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
expect(() => plugin.install(app as any, options)).toThrow('Init failed');
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to install Vue plugin:', expect.any(Error));
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
185
sdk/frontend/unified-login-sdk/src/plugins/vue.ts
Normal file
185
sdk/frontend/unified-login-sdk/src/plugins/vue.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Vue插件模块
|
||||
* 提供Vue应用中使用统一登录SDK的能力
|
||||
*/
|
||||
|
||||
import { Auth } from '../core/auth';
|
||||
import { SDKConfig, UnifiedLoginSDK } from '../types';
|
||||
import { Storage } from '../utils/storage';
|
||||
import { RouterGuard } from '../guards/router';
|
||||
|
||||
/**
|
||||
* Vue版本类型
|
||||
*/
|
||||
type VueVersion = 'vue2' | 'vue3';
|
||||
|
||||
/**
|
||||
* Vue插件选项
|
||||
*/
|
||||
export interface VuePluginOptions {
|
||||
/**
|
||||
* SDK配置
|
||||
*/
|
||||
config: SDKConfig;
|
||||
/**
|
||||
* 插件名称,默认'unifiedLogin'
|
||||
*/
|
||||
pluginName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue插件类
|
||||
*/
|
||||
export class VuePlugin {
|
||||
private readonly auth: Auth;
|
||||
private readonly routerGuard: RouterGuard;
|
||||
private readonly storage: Storage;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storage 存储实例
|
||||
*/
|
||||
constructor(storage: Storage) {
|
||||
if (!storage) {
|
||||
throw new Error('Storage instance is required');
|
||||
}
|
||||
|
||||
this.storage = storage;
|
||||
this.auth = new Auth(storage);
|
||||
this.routerGuard = new RouterGuard(this.auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装Vue插件
|
||||
* @param app Vue构造函数或Vue 3应用实例
|
||||
* @param options 插件选项
|
||||
*/
|
||||
install(app: any, options: VuePluginOptions): void {
|
||||
const { config, pluginName = 'unifiedLogin' } = options;
|
||||
|
||||
// 初始化SDK
|
||||
this.auth.init(config);
|
||||
|
||||
// 判断是Vue 2还是Vue 3
|
||||
const isVue3 = typeof app.config !== 'undefined';
|
||||
|
||||
if (isVue3) {
|
||||
// Vue 3
|
||||
// 在全局属性上挂载SDK实例
|
||||
app.config.globalProperties[`${pluginName}`] = this.auth;
|
||||
app.config.globalProperties.$auth = this.auth; // 兼容简写
|
||||
|
||||
// 提供Vue组件内的注入
|
||||
app.provide(pluginName, this.auth);
|
||||
app.provide('auth', this.auth); // 兼容简写
|
||||
|
||||
// 处理路由守卫
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Vue 2
|
||||
// 在Vue实例上挂载SDK实例
|
||||
app.prototype[`${pluginName}`] = this.auth;
|
||||
app.prototype.$auth = this.auth; // 兼容简写
|
||||
|
||||
// 全局混入
|
||||
app.mixin({
|
||||
beforeCreate() {
|
||||
// 如果是根组件,添加路由守卫
|
||||
if (this.$options.router) {
|
||||
const router = this.$options.router;
|
||||
// 添加全局前置守卫
|
||||
router.beforeEach(this.routerGuard.createVueGuard());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取认证实例
|
||||
* @returns Auth 认证实例
|
||||
*/
|
||||
getAuth(): Auth {
|
||||
return this.auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路由守卫实例
|
||||
* @returns RouterGuard 路由守卫实例
|
||||
*/
|
||||
getRouterGuard(): RouterGuard {
|
||||
return this.routerGuard;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储实例
|
||||
* @returns Storage 存储实例
|
||||
*/
|
||||
getStorage(): Storage {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SDK实例(兼容旧版本)
|
||||
* @returns UnifiedLoginSDK SDK实例
|
||||
*/
|
||||
getSDK(): UnifiedLoginSDK {
|
||||
return this.auth as unknown as UnifiedLoginSDK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Vue插件实例
|
||||
* @param storageType 存储类型
|
||||
* @param prefix 存储前缀
|
||||
* @returns VuePlugin Vue插件实例
|
||||
*/
|
||||
export function createVuePlugin(
|
||||
storageType?: 'localStorage' | 'sessionStorage' | 'cookie',
|
||||
prefix?: string
|
||||
): VuePlugin {
|
||||
const storage = new Storage(storageType, prefix);
|
||||
return new VuePlugin(storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Vue 3 Composition API使用的composable函数
|
||||
* @param plugin Vue插件实例
|
||||
* @returns composable函数
|
||||
*/
|
||||
export function useAuth(plugin: VuePlugin) {
|
||||
return {
|
||||
/** 认证实例 */
|
||||
auth: plugin.getAuth(),
|
||||
/** 路由守卫实例 */
|
||||
routerGuard: plugin.getRouterGuard(),
|
||||
/** 存储实例 */
|
||||
storage: plugin.getStorage(),
|
||||
/** SDK实例 */
|
||||
sdk: plugin.getSDK()
|
||||
};
|
||||
}
|
||||
|
||||
// 全局类型声明(可根据需要在项目中添加)
|
||||
// declare module '@vue/runtime-core' {
|
||||
// interface ComponentCustomProperties {
|
||||
// $unifiedLogin: Auth;
|
||||
// $auth: Auth;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// declare module 'vue/types/vue' {
|
||||
// interface Vue {
|
||||
// $unifiedLogin: Auth;
|
||||
// $auth: Auth;
|
||||
// }
|
||||
// }
|
||||
22
sdk/frontend/unified-login-sdk/src/types/config.ts
Normal file
22
sdk/frontend/unified-login-sdk/src/types/config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* SDK配置选项
|
||||
*/
|
||||
export interface SDKConfig {
|
||||
/** 客户端ID */
|
||||
clientId: string;
|
||||
/** 注册id **/
|
||||
registrationId: string,
|
||||
/** 后端basepath路径*/
|
||||
basepath: string,
|
||||
/** 存储类型,默认localStorage */
|
||||
storageType?: 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
idpLogoutUrl:string;
|
||||
homePage: string;
|
||||
/** 租户ID(可选) */
|
||||
tenantId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件类型
|
||||
*/
|
||||
export type EventType = 'login' | 'logout' | 'tokenExpired';
|
||||
100
sdk/frontend/unified-login-sdk/src/types/index.ts
Normal file
100
sdk/frontend/unified-login-sdk/src/types/index.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import {RouterInfo} from "./user";
|
||||
|
||||
export * from './config';
|
||||
export * from './user';
|
||||
|
||||
/**
|
||||
* 统一登录SDK接口
|
||||
*/
|
||||
export interface UnifiedLoginSDK {
|
||||
/**
|
||||
* 初始化SDK配置
|
||||
* @param config SDK配置选项
|
||||
*/
|
||||
init(config: import('./config').SDKConfig): void;
|
||||
|
||||
getToken():string|null
|
||||
/**
|
||||
* 触发登录流程
|
||||
* @param redirectUri 可选的重定向URL,覆盖初始化时的配置
|
||||
*/
|
||||
login(redirectUri?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
logout(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 处理授权回调
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
handleCallback(): Promise<void>;
|
||||
getRoutes(): Promise<import('./user').RouterInfo>;
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns Promise<UserInfo> 用户信息
|
||||
*/
|
||||
getUserInfo(): Promise<import('./user').UserInfo>;
|
||||
|
||||
/**
|
||||
* 刷新用户信息缓存
|
||||
* @returns Promise<UserInfo> 更新后的用户信息
|
||||
*/
|
||||
refreshUserInfo(): Promise<import('./user').UserInfo>;
|
||||
|
||||
/**
|
||||
* 检查用户是否已认证
|
||||
* @returns boolean 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean;
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定角色
|
||||
* @param role 角色编码或角色编码列表
|
||||
* @returns Promise<boolean> 是否有指定角色
|
||||
*/
|
||||
hasRole(role: string | string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定角色
|
||||
* @param roles 角色编码列表
|
||||
* @returns Promise<boolean> 是否有所有指定角色
|
||||
*/
|
||||
hasAllRoles(roles: string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 检查用户是否有指定权限
|
||||
* @param permission 权限标识或权限标识列表
|
||||
* @returns Promise<boolean> 是否有指定权限
|
||||
*/
|
||||
hasPermission(permission: string | string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 检查用户是否有所有指定权限
|
||||
* @param permissions 权限标识列表
|
||||
* @returns Promise<boolean> 是否有所有指定权限
|
||||
*/
|
||||
hasAllPermissions(permissions: string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
on(event: import('./config').EventType, callback: Function): void;
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param event 事件类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
off(event: import('./config').EventType, callback: Function): void;
|
||||
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @returns boolean 是否为授权回调
|
||||
*/
|
||||
isCallback(): boolean;
|
||||
}
|
||||
88
sdk/frontend/unified-login-sdk/src/types/user.ts
Normal file
88
sdk/frontend/unified-login-sdk/src/types/user.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 菜单信息
|
||||
*/
|
||||
export interface RouterInfo {
|
||||
/** 菜单名称 */
|
||||
name: string;
|
||||
/** 菜单路径 */
|
||||
path?: string;
|
||||
hidden: boolean;
|
||||
redirect: string;
|
||||
query: string;
|
||||
alwaysShow: boolean;
|
||||
/** 菜单组件 */
|
||||
component?: string;
|
||||
meta:MetaVo;
|
||||
children: RouterInfo;
|
||||
}
|
||||
export interface MetaVo {
|
||||
/**
|
||||
* 设置该路由在侧边栏和面包屑中展示的名字
|
||||
*/
|
||||
title:string;
|
||||
|
||||
/**
|
||||
* 设置该路由的图标,对应路径src/assets/icons/svg
|
||||
*/
|
||||
icon:string;
|
||||
|
||||
/**
|
||||
* 设置为true,则不会被 <keep-alive>缓存
|
||||
*/
|
||||
noCache:boolean;
|
||||
|
||||
/**
|
||||
* 内链地址(http(s)://开头)
|
||||
*/
|
||||
link:string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户基本信息
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/** 用户ID */
|
||||
userId: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 姓名 */
|
||||
nickName: string;
|
||||
/** 邮箱 */
|
||||
currentDeptId: string;
|
||||
/** 部门 */
|
||||
userDepts?: UserDept[];
|
||||
/** 岗位 */
|
||||
userPost?: UserPost[];
|
||||
/** 性别 */
|
||||
sex: string;
|
||||
/** 用户角色 */
|
||||
roles?: string[];
|
||||
/** 权限列表 */
|
||||
permissions?: string[];
|
||||
dataPermission: DataPermission
|
||||
}
|
||||
|
||||
export interface DataPermission {
|
||||
allowAll: boolean;
|
||||
onlySelf: boolean;
|
||||
deptList?: string[];
|
||||
areas?:string[]
|
||||
}
|
||||
export interface UserDept{
|
||||
postCode:string
|
||||
postId:bigint
|
||||
postName:string
|
||||
postSort:bigint
|
||||
remark:string
|
||||
status:bigint
|
||||
}
|
||||
export interface UserPost{
|
||||
ancestors: string
|
||||
deptId: bigint
|
||||
deptName: string
|
||||
leader: string
|
||||
orderNum: bigint
|
||||
parentId: bigint
|
||||
status: bigint
|
||||
}
|
||||
358
sdk/frontend/unified-login-sdk/src/utils/storage.ts
Normal file
358
sdk/frontend/unified-login-sdk/src/utils/storage.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 存储工具类
|
||||
* 支持localStorage、sessionStorage和cookie三种存储方式
|
||||
*/
|
||||
|
||||
type StorageType = 'localStorage' | 'sessionStorage' | 'cookie';
|
||||
|
||||
/**
|
||||
* 存储工具类
|
||||
*/
|
||||
export class Storage {
|
||||
private storageType: StorageType;
|
||||
private prefix: string;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param storageType 存储类型
|
||||
* @param prefix 存储前缀,默认'unified_login_'
|
||||
*/
|
||||
constructor(storageType: StorageType = 'localStorage', prefix: string = 'unified_login_') {
|
||||
this.storageType = storageType;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置存储项
|
||||
* @param key 存储键
|
||||
* @param value 存储值
|
||||
* @param options 可选参数,cookie存储时使用
|
||||
*/
|
||||
set(key: string, value: any, options?: { expires?: number; path?: string; domain?: string; secure?: boolean }): void {
|
||||
const fullKey = this.prefix + key;
|
||||
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.setLocalStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.setSessionStorage(fullKey, stringValue);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.setCookie(fullKey, stringValue, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储项
|
||||
* @param key 存储键
|
||||
* @returns 存储值
|
||||
*/
|
||||
get(key: string): any {
|
||||
const fullKey = this.prefix + key;
|
||||
let value: any;
|
||||
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
value = this.getLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
value = this.getSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
value = this.getCookie(fullKey);
|
||||
break;
|
||||
default:
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 尝试解析JSON
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
// 如果不是JSON,直接返回字符串
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除存储项
|
||||
* @param key 存储键
|
||||
*/
|
||||
remove(key: string): void {
|
||||
const fullKey = this.prefix + key;
|
||||
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.removeLocalStorage(fullKey);
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.removeSessionStorage(fullKey);
|
||||
break;
|
||||
case 'cookie':
|
||||
this.removeCookie(fullKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有存储项
|
||||
*/
|
||||
clear(): void {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
this.clearLocalStorage();
|
||||
break;
|
||||
case 'sessionStorage':
|
||||
this.clearSessionStorage();
|
||||
break;
|
||||
case 'cookie':
|
||||
this.clearCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查存储类型是否可用
|
||||
* @returns boolean 是否可用
|
||||
*/
|
||||
isAvailable(): boolean {
|
||||
try {
|
||||
switch (this.storageType) {
|
||||
case 'localStorage':
|
||||
return this.isLocalStorageAvailable();
|
||||
case 'sessionStorage':
|
||||
return this.isSessionStorageAvailable();
|
||||
case 'cookie':
|
||||
return typeof document !== 'undefined';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ localStorage 操作 ------------------------
|
||||
|
||||
/**
|
||||
* 设置localStorage
|
||||
*/
|
||||
private setLocalStorage(key: string, value: string): void {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取localStorage
|
||||
*/
|
||||
private getLocalStorage(key: string): string | null {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除localStorage
|
||||
*/
|
||||
private removeLocalStorage(key: string): void {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空localStorage中所有带前缀的项
|
||||
*/
|
||||
private clearLocalStorage(): void {
|
||||
if (this.isLocalStorageAvailable()) {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
localStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查localStorage是否可用
|
||||
*/
|
||||
private isLocalStorageAvailable(): boolean {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
localStorage.setItem(testKey, testKey);
|
||||
localStorage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ sessionStorage 操作 ------------------------
|
||||
|
||||
/**
|
||||
* 设置sessionStorage
|
||||
*/
|
||||
private setSessionStorage(key: string, value: string): void {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sessionStorage
|
||||
*/
|
||||
private getSessionStorage(key: string): string | null {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
return sessionStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除sessionStorage
|
||||
*/
|
||||
private removeSessionStorage(key: string): void {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空sessionStorage中所有带前缀的项
|
||||
*/
|
||||
private clearSessionStorage(): void {
|
||||
if (this.isSessionStorageAvailable()) {
|
||||
for (let i = 0; i < sessionStorage.length; i++) {
|
||||
const key = sessionStorage.key(i);
|
||||
if (key && key.startsWith(this.prefix)) {
|
||||
sessionStorage.removeItem(key);
|
||||
i--; // 索引调整
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查sessionStorage是否可用
|
||||
*/
|
||||
private isSessionStorageAvailable(): boolean {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const testKey = '__storage_test__';
|
||||
sessionStorage.setItem(testKey, testKey);
|
||||
sessionStorage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ cookie 操作 ------------------------
|
||||
|
||||
/**
|
||||
* 设置cookie
|
||||
*/
|
||||
private setCookie(
|
||||
key: string,
|
||||
value: string,
|
||||
options?: { expires?: number; path?: string; domain?: string; secure?: boolean }
|
||||
): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
let cookieString = `${key}=${encodeURIComponent(value)}`;
|
||||
|
||||
if (options) {
|
||||
// 设置过期时间(秒)
|
||||
if (options.expires) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + options.expires * 1000);
|
||||
cookieString += `; expires=${date.toUTCString()}`;
|
||||
}
|
||||
|
||||
// 设置路径
|
||||
if (options.path) {
|
||||
cookieString += `; path=${options.path}`;
|
||||
}
|
||||
|
||||
// 设置域名
|
||||
if (options.domain) {
|
||||
cookieString += `; domain=${options.domain}`;
|
||||
}
|
||||
|
||||
// 设置secure
|
||||
if (options.secure) {
|
||||
cookieString += '; secure';
|
||||
}
|
||||
}
|
||||
|
||||
document.cookie = cookieString;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取cookie
|
||||
*/
|
||||
private getCookie(key: string): string | null {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = `${key}=`;
|
||||
const decodedCookie = decodeURIComponent(document.cookie);
|
||||
const ca = decodedCookie.split(';');
|
||||
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) === 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除cookie
|
||||
*/
|
||||
private removeCookie(key: string): void {
|
||||
this.setCookie(key, '', { expires: -1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有带前缀的cookie
|
||||
*/
|
||||
private clearCookie(): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const cookies = document.cookie.split(';');
|
||||
for (const cookie of cookies) {
|
||||
const eqPos = cookie.indexOf('=');
|
||||
const key = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
||||
if (key.startsWith(this.prefix)) {
|
||||
this.removeCookie(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
sdk/frontend/unified-login-sdk/src/utils/url.ts
Normal file
125
sdk/frontend/unified-login-sdk/src/utils/url.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* URL处理工具
|
||||
* 用于生成授权URL、解析URL参数等功能
|
||||
*/
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param length 字符串长度,默认32位
|
||||
* @returns 随机字符串
|
||||
*/
|
||||
export function generateRandomString(length: number = 32): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析URL查询参数
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 查询参数对象
|
||||
*/
|
||||
export function parseQueryParams(url: string = window.location.href): Record<string, string> {
|
||||
const params: Record<string, string> = {};
|
||||
const queryString = url.split('?')[1];
|
||||
if (!queryString) {
|
||||
return params;
|
||||
}
|
||||
|
||||
const pairs = queryString.split('&');
|
||||
for (const pair of pairs) {
|
||||
const [key, value] = pair.split('=');
|
||||
if (key) {
|
||||
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL查询参数
|
||||
* @param params 查询参数对象
|
||||
* @returns 查询参数字符串
|
||||
*/
|
||||
export function buildQueryParams(params: Record<string, any>): string {
|
||||
const pairs: string[] = [];
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
||||
}
|
||||
}
|
||||
return pairs.length ? `?${pairs.join('&')}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成OAuth2授权URL
|
||||
* @param authorizationEndpoint 授权端点URL
|
||||
* @param clientId 客户端ID
|
||||
* @param redirectUri 重定向URL
|
||||
* @param options 可选参数
|
||||
* @returns 授权URL
|
||||
*/
|
||||
export function generateAuthorizationUrl(
|
||||
authorizationEndpoint: string,
|
||||
clientId: string,
|
||||
redirectUri: string,
|
||||
options?: {
|
||||
responseType?: string;
|
||||
scope?: string;
|
||||
state?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
): string {
|
||||
const {
|
||||
responseType = 'code',
|
||||
scope,
|
||||
state = generateRandomString(32),
|
||||
...extraParams
|
||||
} = options || {};
|
||||
|
||||
const params = {
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: responseType,
|
||||
state,
|
||||
...(scope ? { scope } : {}),
|
||||
...extraParams
|
||||
};
|
||||
|
||||
const queryString = buildQueryParams(params);
|
||||
return `${authorizationEndpoint}${queryString}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前URL是否为授权回调
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 是否为授权回调
|
||||
*/
|
||||
export function isCallbackUrl(url: string = window.location.href): boolean {
|
||||
const params = parseQueryParams(url);
|
||||
return !!params.code || !!params.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前URL的路径名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 路径名
|
||||
*/
|
||||
export function getPathname(url: string = window.location.href): string {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前URL的主机名
|
||||
* @param url URL字符串,默认为当前URL
|
||||
* @returns 主机名
|
||||
*/
|
||||
export function getHostname(url: string = window.location.href): string {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.hostname;
|
||||
}
|
||||
27
sdk/frontend/unified-login-sdk/tsconfig.json
Normal file
27
sdk/frontend/unified-login-sdk/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["ES2018", "DOM"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user