This commit is contained in:
Eric
2026-02-09 11:24:51 +08:00
commit f2173a9fa9
491 changed files with 43791 additions and 0 deletions

View File

@@ -0,0 +1,533 @@
## 安装
```bash
npm install unified-login-sdk --save
# 或
yarn add unified-login-sdk
```
## 快速开始
### 基本使用
```typescript
import unifiedLoginSDK from 'unified-login-sdk';
// 初始化配置
unifiedLoginSDK.init({
clientId: 'your-client-id',
authorizationEndpoint: 'https://auth.example.com/authorize',
tokenEndpoint: 'https://auth.example.com/token',
userInfoEndpoint: 'https://auth.example.com/userinfo',
redirectUri: 'https://your-app.example.com/callback',
storageType: 'localStorage',
autoRefreshToken: true,
tenantId: 'your-tenant-id' // 可选会自动添加到请求头中的tenant-id字段
});
// 登录
document.getElementById('login-btn')?.addEventListener('click', () => {
unifiedLoginSDK.login();
});
// 处理回调
if (unifiedLoginSDK.isAuthenticated()) {
// 已登录,获取用户信息
unifiedLoginSDK.getUserInfo().then(userInfo => {
console.log('User info:', userInfo);
});
} else if (unifiedLoginSDK.isCallback()) {
// 处理授权回调
unifiedLoginSDK.handleCallback().then(userInfo => {
console.log('Login successful:', userInfo);
// 跳转到首页
window.location.href = '/';
}).catch(error => {
console.error('Login failed:', error);
});
}
// 退出登录
document.getElementById('logout-btn')?.addEventListener('click', () => {
unifiedLoginSDK.logout().then(() => {
console.log('Logout successful');
window.location.href = '/login';
});
});
```
## 核心功能
### 初始化配置
```typescript
unifiedLoginSDK.init({
clientId: 'your-client-id',
clientSecret: 'your-client-secret', // 可选,某些场景下需要
authorizationEndpoint: 'https://auth.example.com/authorize',
tokenEndpoint: 'https://auth.example.com/token',
userInfoEndpoint: 'https://auth.example.com/userinfo',
redirectUri: 'https://your-app.example.com/callback',
storageType: 'localStorage', // 可选默认localStorage
autoRefreshToken: true, // 可选默认true
permissionsEndpoint: 'https://auth.example.com/permissions' // 可选,权限端点
});
```
### 登录流程
1. 调用`login()`方法跳转到授权页面
2. 用户在授权页面登录并授权
3. 授权服务器重定向到配置的`redirectUri`
4. 调用`handleCallback()`方法处理授权回调,获取用户信息
### Token管理
```typescript
// 获取访问令牌
const accessToken = unifiedLoginSDK.getAccessToken();
// 刷新令牌
unifiedLoginSDK.refreshToken().then(() => {
console.log('Token refreshed');
}).catch(error => {
console.error('Failed to refresh token:', error);
});
// 检查是否已认证
const isAuthenticated = unifiedLoginSDK.isAuthenticated();
```
### 用户信息管理
```typescript
// 获取用户信息
unifiedLoginSDK.getUserInfo().then(userInfo => {
console.log('User info:', userInfo);
});
// 获取用户权限列表
unifiedLoginSDK.getPermissions().then(permissions => {
console.log('Permissions:', permissions);
});
```
### 事件监听
```typescript
// 监听登录事件
unifiedLoginSDK.on('login', () => {
console.log('User logged in');
});
// 监听退出事件
unifiedLoginSDK.on('logout', () => {
console.log('User logged out');
});
// 监听Token过期事件
unifiedLoginSDK.on('tokenExpired', () => {
console.log('Token expired');
// 可以在这里执行自定义逻辑,如跳转到登录页
unifiedLoginSDK.login();
});
// 移除事件监听
const handleLogin = () => console.log('User logged in');
unifiedLoginSDK.on('login', handleLogin);
unifiedLoginSDK.off('login', handleLogin);
```
## 框架集成
### Vue 2
```javascript
// main.js
import Vue from 'vue';
import { createVuePlugin } from 'unified-login-sdk';
import App from './App.vue';
import router from './router';
// 创建Vue插件
const vuePlugin = createVuePlugin('localStorage');
// 安装插件
Vue.use(vuePlugin, {
config: {
clientId: 'your-client-id',
authorizationEndpoint: 'https://auth.example.com/authorize',
tokenEndpoint: 'https://auth.example.com/token',
userInfoEndpoint: 'https://auth.example.com/userinfo',
redirectUri: 'https://your-app.example.com/callback'
}
});
new Vue({
router,
render: h => h(App)
}).$mount('#app');
```
在组件中使用:
```vue
<template>
<div>
<div v-if="$auth.isAuthenticated()">
<h1>Welcome, {{ userInfo?.name }}</h1>
<button @click="logout">Logout</button>
</div>
<div v-else>
<button @click="login">Login</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
userInfo: null
};
},
mounted() {
if (this.$auth.isAuthenticated()) {
this.getUserInfo();
} else if (this.$auth.isCallback()) {
this.handleCallback();
}
},
methods: {
login() {
this.$auth.login();
},
async logout() {
await this.$auth.logout();
window.location.href = '/';
},
async getUserInfo() {
this.userInfo = await this.$auth.getUserInfo();
},
async handleCallback() {
try {
this.userInfo = await this.$auth.handleCallback();
window.location.href = '/';
} catch (error) {
console.error('Login failed:', error);
}
}
}
};
</script>
```
### Vue 3
```javascript
// main.js
import { createApp } from 'vue';
import { createVuePlugin } from 'unified-login-sdk';
import App from './App.vue';
import router from './router';
// 创建Vue插件
const vuePlugin = createVuePlugin('localStorage');
const app = createApp(App);
// 安装插件
app.use(vuePlugin, {
config: {
clientId: 'your-client-id',
authorizationEndpoint: 'https://auth.example.com/authorize',
tokenEndpoint: 'https://auth.example.com/token',
userInfoEndpoint: 'https://auth.example.com/userinfo',
redirectUri: 'https://your-app.example.com/callback'
}
});
app.use(router);
app.mount('#app');
```
在组件中使用Composition API
```vue
<template>
<div>
<div v-if="isAuthenticated">
<h1>Welcome, {{ userInfo?.name }}</h1>
<button @click="logout">Logout</button>
</div>
<div v-else>
<button @click="login">Login</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, inject } from 'vue';
// 注入SDK实例
const auth = inject('unifiedLogin');
const userInfo = ref(null);
const isAuthenticated = ref(auth.isAuthenticated());
onMounted(() => {
if (isAuthenticated.value) {
getUserInfo();
} else if (auth.isCallback()) {
handleCallback();
}
});
const login = () => {
auth.login();
};
const logout = async () => {
await auth.logout();
window.location.href = '/';
};
const getUserInfo = async () => {
userInfo.value = await auth.getUserInfo();
};
const handleCallback = async () => {
try {
userInfo.value = await auth.handleCallback();
isAuthenticated.value = true;
window.location.href = '/';
} catch (error) {
console.error('Login failed:', error);
}
};
</script>
```
```
## API参考
### 初始化
```typescript
init(config: SDKConfig): void
```
初始化SDK配置。
### 登录
```typescript
login(redirectUri?: string): void
```
触发登录流程,可选参数`redirectUri`可覆盖初始化时的配置。
### 退出登录
```typescript
logout(): Promise<void>
```
退出登录清除本地存储的Token和用户信息。
### 处理授权回调
```typescript
handleCallback(): Promise<UserInfo>
```
处理授权回调,获取用户信息。
### 获取用户信息
```typescript
getUserInfo(): Promise<UserInfo>
```
获取用户基本信息。
### 获取用户权限列表
```typescript
getPermissions(): Promise<string[]>
```
获取用户权限列表。
### 检查是否已认证
```typescript
isAuthenticated(): boolean
```
检查用户是否已认证。
### 获取访问令牌
```typescript
getAccessToken(): string | null
```
获取访问令牌。
### 刷新访问令牌
```typescript
refreshToken(): Promise<void>
```
刷新访问令牌。
### 事件监听
```typescript
on(event: 'login' | 'logout' | 'tokenExpired', callback: Function): void
```
监听登录、退出或Token过期事件。
### 移除事件监听
```typescript
off(event: 'login' | 'logout' | 'tokenExpired', callback: Function): void
```
移除事件监听。
## 配置选项
| 选项 | 类型 | 必填 | 默认值 | 描述 |
|------|------|------|--------|------|
| clientId | string | 是 | - | 客户端ID |
| clientSecret | string | 否 | - | 客户端密钥,某些场景下需要 |
| authorizationEndpoint | string | 是 | - | 授权端点URL |
| tokenEndpoint | string | 是 | - | Token端点URL |
| userInfoEndpoint | string | 是 | - | 用户信息端点URL |
| redirectUri | string | 是 | - | 重定向URL |
| storageType | 'localStorage' 'sessionStorage' 'cookie' | 否 | 'localStorage' | Token存储类型 |
| autoRefreshToken | boolean | 否 | true | 是否自动刷新Token |
| permissionsEndpoint | string | 否 | - | 权限端点URL |
| stateLength | number | 否 | 32 | 状态参数长度 |
| tenantId | string | 否 | - | 租户ID会自动添加到请求头中的tenant-id字段 |
## 事件处理
| 事件 | 描述 |
|------|------|
| login | 用户登录成功时触发 |
| logout | 用户退出登录时触发 |
| tokenExpired | Token过期时触发 |
## 路由守卫
### Vue路由守卫
```javascript
// router/index.js
import VueRouter from 'vue-router';
import { Auth } from 'unified-login-sdk';
import { Storage } from 'unified-login-sdk';
import { RouterGuard } from 'unified-login-sdk';
const storage = new Storage('localStorage');
const auth = new Auth(storage);
const routerGuard = new RouterGuard(auth);
const router = new VueRouter({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/protected',
name: 'Protected',
component: Protected,
meta: {
auth: {
requiresAuth: true,
requiredPermissions: ['read:protected']
}
}
}
]
});
// 添加路由守卫
router.beforeEach(routerGuard.createVueGuard());
export default router;
```
## 错误处理
### 网络错误处理
```typescript
try {
await unifiedLoginSDK.getUserInfo();
} catch (error) {
if (error.name === 'HttpError') {
// 处理HTTP错误
console.error('HTTP Error:', error.status, error.message);
if (error.status === 401) {
// 未授权,跳转到登录页
unifiedLoginSDK.login();
} else if (error.status === 403) {
// 权限不足
window.location.href = '/403';
}
} else {
// 处理其他错误
console.error('Error:', error.message);
}
}
```
### Token失效处理
```typescript
// 监听Token过期事件
unifiedLoginSDK.on('tokenExpired', () => {
console.log('Token expired');
// 跳转到登录页
unifiedLoginSDK.login();
});
```
## 最佳实践
1. **配置安全存储**根据项目需求选择合适的存储类型敏感信息建议使用cookie并设置secure和httpOnly标志。
2. **合理设置Token过期时间**根据项目安全性要求设置合适的Token过期时间建议access token过期时间较短refresh token过期时间较长。
3. **使用路由守卫保护敏感路由**:对需要登录或特定权限的路由使用路由守卫进行保护。
4. **处理网络错误**在调用SDK方法时使用try-catch捕获并处理可能的错误。
5. **监听Token过期事件**及时处理Token过期情况避免用户体验下降。
6. **不要直接暴露clientSecret**clientSecret应该只在后端使用前端SDK尽量避免使用clientSecret。
7. **使用HTTPS**确保所有与授权服务器的通信都使用HTTPS避免Token被窃取。
8. **定期清理存储**:在用户退出登录时,确保清理所有相关存储的信息。
## 浏览器兼容性
- Chrome (推荐)
- Firefox
- Safari
- Edge
## 许可证
MIT License

View 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

View 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"}

View 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

File diff suppressed because one or more lines are too long

View 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

View File

@@ -0,0 +1 @@
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}

View 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

File diff suppressed because one or more lines are too long

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=config.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":""}

View 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

View 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"}

View File

@@ -0,0 +1,3 @@
export * from './config';
export * from './user';
//# sourceMappingURL=index.js.map

View 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"}

View 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

View 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"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=user.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":""}

View 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

View 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"}

View 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

File diff suppressed because one or more lines are too long

View 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

View 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"}

View 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

View File

@@ -0,0 +1 @@
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,41 @@
{
"name": "oauth2-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"
}
}

File diff suppressed because it is too large Load Diff

View 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
})
]
};

View File

@@ -0,0 +1,280 @@
/**
* 认证核心逻辑
* 实现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 storage: Storage;
private eventHandlers: Record<EventType, Function[]> = {
login: [],
logout: [],
tokenExpired: []
};
private userInfoCache: UserInfo | null = null;
/**
* 构造函数
* @param storage 存储实例
*/
constructor(storage: Storage) {
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: SDKConfig): void {
this.config = config;
// 设置租户ID到HTTP客户端
this.httpClient.setTenantId(config.tenantId);
}
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 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(): Promise<void> {
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(): 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 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(): Promise<RouterInfo> {
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(): UserInfo {
return this.storage.get("userInfo");
}
/**
* 检查用户是否有指定角色
* @param role 角色编码或角色编码列表
* @returns Promise<boolean> 是否有指定角色
*/
async hasRole(role: string | string[]): Promise<boolean> {
if (!this.isAuthenticated()) {
return false;
}
const userInfo: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: string[]): Promise<boolean> {
if (!this.isAuthenticated()) {
return false;
}
const userInfo:UserInfo = this.storage.get("userInfo");
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: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: string[]): Promise<boolean> {
if (!this.isAuthenticated()) {
return false;
}
const userInfo:UserInfo = this.storage.get("userInfo");
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();
}
}

View File

@@ -0,0 +1,370 @@
/**
* 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 class HttpError extends Error {
/** 状态码 */
public status: number;
/** 状态文本 */
public statusText: string;
/** 错误数据 */
public data: any;
/**
* 构造函数
* @param message 错误信息
* @param status 状态码
* @param statusText 状态文本
* @param data 错误数据
*/
constructor(message: string, status: number, statusText: string, data: any) {
super(message);
this.name = 'HttpError';
this.status = status;
this.statusText = statusText;
this.data = data;
}
}
/**
* HTTP客户端类
*/
export class HttpClient {
private tokenGetter?: () => string | null;
private tenantId?: string;
/**
* 构造函数
* @param logout
* @param tokenGetter Token获取函数
*/
constructor(tokenGetter?: () => string | null) {
this.tokenGetter = tokenGetter;
}
/**
* 设置Token获取函数
* @param tokenGetter Token获取函数
*/
setTokenGetter(tokenGetter: () => string | null): void {
this.tokenGetter = tokenGetter;
}
/**
* 设置租户ID
* @param tenantId 租户ID
*/
setTenantId(tenantId?: string): void {
this.tenantId = tenantId;
}
/**
* 发送HTTP请求
* @param options 请求选项
* @returns Promise<HttpResponse<T>> 响应结果
*/
async request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>> {
const {
method,
url,
headers = {},
body,
needAuth = true
} = options;
// 构建请求头
const requestHeaders: Record<string, string> = {
'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: RequestInit = {
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: '' as T,
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> 处理后的响应
*/
private handleResponse<T = any>(response: Response, responseData: any): HttpResponse<T> {
// 检查是否为业务响应结构
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 as T,
headers: this.parseHeaders(response.headers)
};
}
// 非业务响应结构,直接返回原始数据
return {
status: response.status,
statusText: response.statusText,
data: responseData as T,
headers: this.parseHeaders(response.headers)
};
}
/**
* 检查是否为业务响应结构
* @param responseData 响应数据
* @returns boolean 是否为业务响应结构
*/
private isBusinessResponse(responseData: any): boolean {
return typeof responseData === 'object' &&
responseData !== null &&
('code' in responseData) &&
('msg' in responseData) &&
('data' in responseData);
}
/**
* 获取错误信息
* @param responseData 响应数据
* @returns string 错误信息
*/
private getErrorMessage(responseData: any): string {
// 如果是业务响应结构
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<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
});
}
/**
* 解析响应体
* @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;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Token管理模块
* 负责Token的存储、获取、刷新和过期处理
*/
import { Storage } from '../utils/storage';
/**
* Token管理类
*/
export class TokenManager {
private storage: Storage;
/**
* 构造函数
* @param storage 存储实例
* @param httpClient HTTP客户端实例
*/
constructor(storage: Storage) {
this.storage = storage;
}
/**
* 存储Token信息
* @param tokenInfo Token信息
*/
saveToken(tokenInfo: string): void {
this.storage.set('token', tokenInfo);
}
/**
* 获取Token信息
* @returns TokenInfo | null Token信息
*/
getToken(): string | null {
return this.storage.get('token');
}
/**
* 清除Token信息
*/
clearToken(): void {
this.storage.remove('token');
}
}

View File

@@ -0,0 +1,130 @@
/**
* 路由守卫模块
* 提供基于权限的路由拦截和未登录自动跳转登录页功能
*/
import { Auth } from '../core/auth';
/**
* 路由守卫选项
*/
export interface RouterGuardOptions {
/**
* 是否需要登录
*/
requiresAuth?: boolean;
/**
* 需要的权限列表
*/
requiredPermissions?: string[];
/**
* 登录后重定向的URL
*/
redirectUri?: string;
/**
* 权限不足时重定向的URL
*/
unauthorizedRedirectUri?: string;
}
/**
* 路由守卫类
*/
export class RouterGuard {
private auth: Auth;
/**
* 构造函数
* @param auth 认证实例
*/
constructor(auth: Auth) {
this.auth = auth;
}
/**
* 检查路由权限
* @param options 路由守卫选项
* @returns Promise<boolean> 是否通过权限检查
*/
async check(options: RouterGuardOptions): Promise<boolean> {
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: any, from: any, next: any) => {
// 从路由元信息中获取守卫选项
const options: RouterGuardOptions = to.meta?.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: string | string[]): Promise<boolean> {
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)
);
}
}

View File

@@ -0,0 +1,94 @@
/**
* 统一登录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';
// 导出Vue插件
export { VuePlugin, createVuePlugin } from './plugins/vue';
// 创建默认SDK实例
import { SDKConfig, UnifiedLoginSDK } from './types';
import { Auth as AuthCore } from './core/auth';
import { Storage as StorageCore } from './utils/storage';
/**
* 默认SDK实例
*/
const defaultStorage = new StorageCore();
const defaultAuth = new AuthCore(defaultStorage);
/**
* 默认导出的SDK实例
*/
export const unifiedLoginSDK: UnifiedLoginSDK = {
init: (config: SDKConfig) => {
defaultAuth.init(config);
},
getToken: () => {
return defaultAuth.getToken()
},
login: (redirectUri?: string) => {
return defaultAuth.login(redirectUri);
},
logout: () => {
return defaultAuth.logout();
},
handleCallback: () => {
return defaultAuth.handleCallback();
},
getRoutes: () => {
return defaultAuth.getRoutes();
},
getUserInfo: () => {
return defaultAuth.getUserInfo();
},
isAuthenticated: () => {
return defaultAuth.isAuthenticated();
},
hasRole: (role: string | string[]) => {
return defaultAuth.hasRole(role);
},
hasAllRoles: (roles: string[]) => {
return defaultAuth.hasAllRoles(roles);
},
hasPermission: (permission: string | string[]) => {
return defaultAuth.hasPermission(permission);
},
hasAllPermissions: (permissions: string[]) => {
return defaultAuth.hasAllPermissions(permissions);
},
on: (event, callback) => {
return defaultAuth.on(event, callback);
},
off: (event, callback) => {
return defaultAuth.off(event, callback);
},
isCallback: () => {
return defaultAuth.isCallback();
}
};
// 默认导出
export default unifiedLoginSDK;
// 版本信息
export const version = '1.0.0';

View File

@@ -0,0 +1,121 @@
/**
* 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 class VuePlugin {
private auth: Auth;
private routerGuard: RouterGuard;
/**
* 构造函数
* @param storage 存储实例
*/
constructor(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;
}
}
/**
* 创建Vue插件实例
* @param storageType 存储类型
* @returns VuePlugin Vue插件实例
*/
export function createVuePlugin(storageType?: 'localStorage' | 'sessionStorage' | 'cookie'): VuePlugin {
const storage = new Storage(storageType);
return new VuePlugin(storage);
}

View File

@@ -0,0 +1,40 @@
/**
* 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';

View File

@@ -0,0 +1,94 @@
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(): 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;
}

View 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
}

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

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

View 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"]
}