前端服务共享
This commit is contained in:
@@ -1,6 +1,92 @@
|
||||
import { api } from '@/api/index'
|
||||
import type { LoginParam, LoginDomain } from '@/types'
|
||||
|
||||
/**
|
||||
* 认证 API
|
||||
* 通过 Gateway (8180) 访问 Auth Service (8181)
|
||||
* 路由规则:/urban-lifeline/auth/** → auth-service/urban-lifeline/auth/**
|
||||
*/
|
||||
export const authAPI = {
|
||||
baseUrl: "/auth",
|
||||
baseUrl: "/urban-lifeline/auth",
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param loginParam 登录参数
|
||||
* @returns 登录结果(包含 token 和用户信息)
|
||||
*/
|
||||
login(loginParam: LoginParam) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/login`, loginParam)
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
* @returns 登出结果
|
||||
*/
|
||||
logout() {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/logout`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取验证码(统一接口)
|
||||
* @param loginParam 登录参数(包含验证码类型)
|
||||
* @returns 验证码结果
|
||||
*/
|
||||
getCaptcha(loginParam: LoginParam) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/captcha`, loginParam)
|
||||
},
|
||||
|
||||
/**
|
||||
* 刷新 Token
|
||||
* @returns 新的登录信息
|
||||
*/
|
||||
refreshToken() {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/refresh`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码
|
||||
* @param email 邮箱地址
|
||||
* @returns 发送结果
|
||||
*/
|
||||
sendEmailCode(email: string) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/send-email-code`, { email })
|
||||
},
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @param phone 手机号
|
||||
* @returns 发送结果
|
||||
*/
|
||||
sendSmsCode(phone: string) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/send-sms-code`, { phone })
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
* @param registerData 注册数据
|
||||
* @returns 注册结果(成功后自动登录,返回 token)
|
||||
*/
|
||||
register(registerData: {
|
||||
registerType: 'username' | 'phone' | 'email'
|
||||
username?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
password: string
|
||||
confirmPassword: string
|
||||
smsCode?: string
|
||||
emailCode?: string
|
||||
smsSessionId?: string
|
||||
emailSessionId?: string
|
||||
studentId?: string
|
||||
}) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/register`, registerData)
|
||||
},
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
* @returns 健康状态
|
||||
*/
|
||||
health() {
|
||||
return api.get<string>(`${this.baseUrl}/health`)
|
||||
}
|
||||
}
|
||||
|
||||
274
urbanLifelineWeb/packages/shared/src/utils/crypto/README.md
Normal file
274
urbanLifelineWeb/packages/shared/src/utils/crypto/README.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# 加密工具
|
||||
|
||||
## AES-256-GCM 加密
|
||||
|
||||
### 概述
|
||||
|
||||
前端 AES 加密工具,与后端 `AesEncryptUtil` 保持一致,用于敏感信息传输加密。
|
||||
|
||||
### 使用场景
|
||||
|
||||
1. **密码传输**:登录时加密密码
|
||||
2. **敏感信息**:加密手机号、身份证号等
|
||||
|
||||
### 快速开始
|
||||
|
||||
#### 1. 初始化(应用启动时)
|
||||
|
||||
```typescript
|
||||
import { initAesEncrypt } from '@/utils/crypto'
|
||||
|
||||
// 在 main.ts 中初始化
|
||||
const AES_SECRET_KEY = '1234567890qwer' // 与后端配置保持一致
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
```
|
||||
|
||||
#### 2. 使用加密
|
||||
|
||||
```typescript
|
||||
import { getAesInstance } from '@/utils/crypto'
|
||||
|
||||
// 获取加密实例
|
||||
const aes = getAesInstance()
|
||||
|
||||
// 加密密码
|
||||
const encryptedPassword = await aes.encryptPassword('myPassword123')
|
||||
|
||||
// 加密手机号
|
||||
const encryptedPhone = await aes.encryptPhone('13812345678')
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
#### 登录时加密密码
|
||||
|
||||
```typescript
|
||||
import { authAPI } from '@/api/auth'
|
||||
import { getAesInstance } from '@/utils/crypto'
|
||||
|
||||
async function login(username: string, password: string) {
|
||||
try {
|
||||
// 加密密码
|
||||
const aes = getAesInstance()
|
||||
const encryptedPassword = await aes.encryptPassword(password)
|
||||
|
||||
// 发送登录请求
|
||||
const response = await authAPI.login({
|
||||
username,
|
||||
password: encryptedPassword,
|
||||
loginType: 'password'
|
||||
})
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 手机号注册
|
||||
|
||||
```typescript
|
||||
import { authAPI } from '@/api/auth'
|
||||
import { getAesInstance } from '@/utils/crypto'
|
||||
|
||||
async function register(phone: string, password: string, smsCode: string) {
|
||||
try {
|
||||
const aes = getAesInstance()
|
||||
|
||||
// 加密密码
|
||||
const encryptedPassword = await aes.encryptPassword(password)
|
||||
|
||||
// 加密手机号
|
||||
const encryptedPhone = await aes.encryptPhone(phone)
|
||||
|
||||
// 发送注册请求
|
||||
const response = await authAPI.register({
|
||||
registerType: 'phone',
|
||||
phone: encryptedPhone,
|
||||
password: encryptedPassword,
|
||||
confirmPassword: encryptedPassword,
|
||||
smsCode,
|
||||
smsSessionId: 'session-id-from-captcha'
|
||||
})
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 工具函数
|
||||
|
||||
#### 数据脱敏
|
||||
|
||||
```typescript
|
||||
import { AesUtils } from '@/utils/crypto'
|
||||
|
||||
// 脱敏手机号
|
||||
const masked = AesUtils.maskPhone('13812345678')
|
||||
// 输出:138****5678
|
||||
|
||||
// 脱敏身份证号
|
||||
const maskedId = AesUtils.maskIdCard('110101199001011234')
|
||||
// 输出:110101********1234
|
||||
|
||||
// 脱敏邮箱
|
||||
const maskedEmail = AesUtils.maskEmail('test@example.com')
|
||||
// 输出:t***@example.com
|
||||
```
|
||||
|
||||
### API 参考
|
||||
|
||||
#### AesEncryptUtil 类
|
||||
|
||||
| 方法 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `init()` | - | `Promise<void>` | 初始化密钥(必须先调用) |
|
||||
| `encrypt(plaintext)` | `string` | `Promise<string>` | 加密字符串 |
|
||||
| `decrypt(ciphertext)` | `string` | `Promise<string>` | 解密字符串 |
|
||||
| `encryptPassword(password)` | `string` | `Promise<string>` | 加密密码 |
|
||||
| `encryptPhone(phone)` | `string` | `Promise<string>` | 加密手机号 |
|
||||
| `decryptPhone(encrypted)` | `string` | `Promise<string>` | 解密手机号 |
|
||||
| `encryptIdCard(idCard)` | `string` | `Promise<string>` | 加密身份证号 |
|
||||
| `decryptIdCard(encrypted)` | `string` | `Promise<string>` | 解密身份证号 |
|
||||
|
||||
#### AesUtils 静态方法
|
||||
|
||||
| 方法 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `maskPhone(phone)` | `string` | `string` | 脱敏手机号 |
|
||||
| `maskIdCard(idCard)` | `string` | `string` | 脱敏身份证号 |
|
||||
| `maskEmail(email)` | `string` | `string` | 脱敏邮箱 |
|
||||
|
||||
#### 全局函数
|
||||
|
||||
| 函数 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `initAesEncrypt(secretKey)` | `string` | `Promise<void>` | 初始化 AES 加密(应用启动时调用) |
|
||||
| `getAesInstance()` | - | `AesEncryptUtil` | 获取 AES 加密实例 |
|
||||
| `createAesEncrypt(secretKey)` | `string` | `Promise<AesEncryptUtil>` | 创建新的 AES 实例 |
|
||||
|
||||
### 配置说明
|
||||
|
||||
#### 密钥配置
|
||||
|
||||
前端密钥必须与后端配置保持一致:
|
||||
|
||||
**后端配置(application.yml)**:
|
||||
```yaml
|
||||
security:
|
||||
aes:
|
||||
secret-key: 1234567890qwer
|
||||
```
|
||||
|
||||
**前端配置**:
|
||||
```typescript
|
||||
const AES_SECRET_KEY = '1234567890qwer' // 与后端保持一致
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
```
|
||||
|
||||
#### 算法参数
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| 算法 | AES-256-GCM | 高强度加密算法 |
|
||||
| 密钥长度 | 256 bits | AES-256 |
|
||||
| IV 长度 | 12 bytes | GCM 推荐长度 |
|
||||
| Tag 长度 | 128 bits | GCM 认证标签 |
|
||||
| 编码 | Base64 | 密文编码格式 |
|
||||
|
||||
### 安全注意事项
|
||||
|
||||
1. **密钥管理**
|
||||
- 密钥不要硬编码在代码中
|
||||
- 生产环境从配置中心或环境变量获取
|
||||
- 定期轮换密钥
|
||||
|
||||
2. **HTTPS 传输**
|
||||
- 生产环境必须使用 HTTPS
|
||||
- 加密只是额外保障,不能替代 HTTPS
|
||||
|
||||
3. **密码安全**
|
||||
- 密码传输前加密
|
||||
- 后端再次使用 BCrypt 等算法加密存储
|
||||
- 前端加密防止明文传输被截获
|
||||
|
||||
4. **错误处理**
|
||||
- 捕获加密失败异常
|
||||
- 不要在错误信息中暴露敏感信息
|
||||
|
||||
### 浏览器兼容性
|
||||
|
||||
使用 Web Crypto API,支持以下浏览器:
|
||||
|
||||
- Chrome 37+
|
||||
- Firefox 34+
|
||||
- Safari 11+
|
||||
- Edge 79+
|
||||
|
||||
不支持 IE 浏览器。
|
||||
|
||||
### 与后端对接
|
||||
|
||||
#### 数据流程
|
||||
|
||||
```
|
||||
前端 ----[加密数据]----> Gateway -----> Auth Service
|
||||
(AES-256-GCM) (AES-256-GCM)
|
||||
[解密] → [BCrypt] → 数据库
|
||||
```
|
||||
|
||||
#### 示例对比
|
||||
|
||||
**前端加密**:
|
||||
```typescript
|
||||
const encrypted = await aes.encrypt('13812345678')
|
||||
// 输出:Base64([IV(12字节)][密文])
|
||||
```
|
||||
|
||||
**后端解密**:
|
||||
```java
|
||||
String decrypted = aesEncryptUtil.decrypt(encrypted)
|
||||
// 输出:'13812345678'
|
||||
```
|
||||
|
||||
### 故障排查
|
||||
|
||||
#### 1. "AES 密钥未初始化"
|
||||
|
||||
**原因**:未调用 `initAesEncrypt()`
|
||||
|
||||
**解决**:在 `main.ts` 中初始化
|
||||
```typescript
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
```
|
||||
|
||||
#### 2. "AES 解密失败"
|
||||
|
||||
**原因**:前后端密钥不一致
|
||||
|
||||
**解决**:检查前后端密钥配置是否相同
|
||||
|
||||
#### 3. 类型错误
|
||||
|
||||
**原因**:TypeScript 类型问题
|
||||
|
||||
**解决**:确保使用最新版本的工具类
|
||||
|
||||
### 性能优化
|
||||
|
||||
1. **密钥复用**:使用单例模式,避免重复初始化
|
||||
2. **异步处理**:加密操作是异步的,注意使用 `await`
|
||||
3. **批量加密**:如需加密多个字段,可并行处理
|
||||
|
||||
```typescript
|
||||
// 并行加密
|
||||
const [encryptedPhone, encryptedPassword] = await Promise.all([
|
||||
aes.encryptPhone(phone),
|
||||
aes.encryptPassword(password)
|
||||
])
|
||||
```
|
||||
322
urbanLifelineWeb/packages/shared/src/utils/crypto/aes.ts
Normal file
322
urbanLifelineWeb/packages/shared/src/utils/crypto/aes.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* AES-256-GCM 加密工具(兼容 H5/小程序/App)
|
||||
* 与后端 AesEncryptUtil 保持一致
|
||||
*
|
||||
* 使用场景:
|
||||
* - 密码传输前加密
|
||||
* - 敏感信息(手机号、身份证号)加密
|
||||
*
|
||||
* @author yslg
|
||||
* @since 2025-12-10
|
||||
*/
|
||||
|
||||
/**
|
||||
* AES 加密配置
|
||||
*/
|
||||
interface AesConfig {
|
||||
algorithm: string
|
||||
keySize: number
|
||||
ivLength: number
|
||||
tagLength: number
|
||||
}
|
||||
|
||||
const AES_CONFIG: AesConfig = {
|
||||
algorithm: 'AES-GCM',
|
||||
keySize: 256,
|
||||
ivLength: 12, // GCM 推荐 IV 长度
|
||||
tagLength: 128 // GCM 认证标签长度
|
||||
}
|
||||
|
||||
const getCrypto = (): Crypto => {
|
||||
return window.crypto
|
||||
}
|
||||
|
||||
/**
|
||||
* AES 加密工具类
|
||||
*/
|
||||
export class AesEncryptUtil {
|
||||
private secretKey: CryptoKey | null = null
|
||||
private secretKeyString: string
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param secretKeyString Base64 编码的密钥(32字节,AES-256)
|
||||
*/
|
||||
constructor(secretKeyString: string) {
|
||||
this.secretKeyString = secretKeyString
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化密钥(异步)
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
if (!this.secretKeyString) {
|
||||
throw new Error('AES 密钥未配置')
|
||||
}
|
||||
|
||||
try {
|
||||
// Base64 解码密钥
|
||||
const keyData = this.base64ToArrayBuffer(this.secretKeyString)
|
||||
|
||||
// 校验密钥长度(AES-256 必须是 32 字节)
|
||||
if (keyData.byteLength !== 32) {
|
||||
throw new Error(`AES 密钥长度错误:需32字节,实际${keyData.byteLength}字节`)
|
||||
}
|
||||
|
||||
// 导入密钥(跨端兼容)
|
||||
this.secretKey = await getCrypto().subtle.importKey(
|
||||
'raw',
|
||||
keyData,
|
||||
{ name: AES_CONFIG.algorithm },
|
||||
false,
|
||||
['encrypt', 'decrypt']
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(`AES 密钥初始化失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密字符串
|
||||
* @param plaintext 明文
|
||||
* @returns Base64 编码的密文(包含 IV)
|
||||
*/
|
||||
async encrypt(plaintext: string): Promise<string> {
|
||||
if (!plaintext) {
|
||||
return plaintext
|
||||
}
|
||||
|
||||
if (!this.secretKey) {
|
||||
throw new Error('AES 密钥未初始化,请先调用 init()')
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成随机 IV(跨端兼容)
|
||||
const iv = getCrypto().getRandomValues(new Uint8Array(AES_CONFIG.ivLength))
|
||||
|
||||
// 将明文转为 ArrayBuffer
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(plaintext)
|
||||
|
||||
// 加密
|
||||
const ciphertext = await getCrypto().subtle.encrypt(
|
||||
{
|
||||
name: AES_CONFIG.algorithm,
|
||||
iv: iv,
|
||||
tagLength: AES_CONFIG.tagLength
|
||||
},
|
||||
this.secretKey,
|
||||
data
|
||||
)
|
||||
|
||||
// 将 IV 和密文组合:[IV(12字节)][密文]
|
||||
const combined = new Uint8Array(iv.length + ciphertext.byteLength)
|
||||
combined.set(iv, 0)
|
||||
combined.set(new Uint8Array(ciphertext), iv.length)
|
||||
|
||||
// Base64 编码
|
||||
return this.arrayBufferToBase64(combined)
|
||||
} catch (error) {
|
||||
throw new Error(`AES 加密失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密字符串
|
||||
* @param ciphertext Base64 编码的密文(包含 IV)
|
||||
* @returns 明文
|
||||
*/
|
||||
async decrypt(ciphertext: string): Promise<string> {
|
||||
if (!ciphertext) {
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
if (!this.secretKey) {
|
||||
throw new Error('AES 密钥未初始化,请先调用 init()')
|
||||
}
|
||||
|
||||
try {
|
||||
// Base64 解码
|
||||
const combinedBuffer = this.base64ToArrayBuffer(ciphertext)
|
||||
const combined = new Uint8Array(combinedBuffer)
|
||||
|
||||
// 校验 IV 长度
|
||||
if (combined.length < AES_CONFIG.ivLength) {
|
||||
throw new Error('密文格式错误:IV 长度不足')
|
||||
}
|
||||
|
||||
// 提取 IV 和密文
|
||||
const iv = combined.slice(0, AES_CONFIG.ivLength)
|
||||
const data = combined.slice(AES_CONFIG.ivLength)
|
||||
|
||||
// 解密
|
||||
const plaintext = await getCrypto().subtle.decrypt(
|
||||
{
|
||||
name: AES_CONFIG.algorithm,
|
||||
iv: iv,
|
||||
tagLength: AES_CONFIG.tagLength
|
||||
},
|
||||
this.secretKey,
|
||||
data
|
||||
)
|
||||
|
||||
// 将 ArrayBuffer 转为字符串
|
||||
const decoder = new TextDecoder()
|
||||
return decoder.decode(plaintext)
|
||||
} catch (error) {
|
||||
throw new Error(`AES 解密失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密密码(用于登录等场景)
|
||||
*/
|
||||
async encryptPassword(password: string): Promise<string> {
|
||||
return this.encrypt(password)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密手机号
|
||||
*/
|
||||
async encryptPhone(phone: string): Promise<string> {
|
||||
return this.encrypt(phone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密手机号
|
||||
*/
|
||||
async decryptPhone(encryptedPhone: string): Promise<string> {
|
||||
return this.decrypt(encryptedPhone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密身份证号
|
||||
*/
|
||||
async encryptIdCard(idCard: string): Promise<string> {
|
||||
return this.encrypt(idCard)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密身份证号
|
||||
*/
|
||||
async decryptIdCard(encryptedIdCard: string): Promise<string> {
|
||||
return this.decrypt(encryptedIdCard)
|
||||
}
|
||||
|
||||
// ============ 工具方法 ============
|
||||
|
||||
/**
|
||||
* Base64 转 ArrayBuffer
|
||||
*/
|
||||
private base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||
// 处理 Base64 填充字符
|
||||
base64 = base64.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const padLength = (4 - (base64.length % 4)) % 4
|
||||
base64 += '='.repeat(padLength)
|
||||
|
||||
const binaryString = atob(base64)
|
||||
const bytes = new Uint8Array(binaryString.length)
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i)
|
||||
}
|
||||
return bytes.buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* ArrayBuffer 转 Base64
|
||||
*/
|
||||
private arrayBufferToBase64(buffer: Uint8Array): string {
|
||||
let binary = ''
|
||||
const len = buffer.byteLength
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(buffer[i])
|
||||
}
|
||||
return btoa(binary)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态工具方法
|
||||
*/
|
||||
export class AesUtils {
|
||||
/**
|
||||
* 脱敏显示手机号
|
||||
* 例如:13812345678 -> 138****5678
|
||||
*/
|
||||
static maskPhone(phone: string): string {
|
||||
if (!phone || phone.length < 11) {
|
||||
return phone
|
||||
}
|
||||
return phone.substring(0, 3) + '****' + phone.substring(7)
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏显示身份证号
|
||||
* 例如:110101199001011234 -> 110101********1234
|
||||
*/
|
||||
static maskIdCard(idCard: string): string {
|
||||
if (!idCard || idCard.length < 18) {
|
||||
return idCard
|
||||
}
|
||||
return idCard.substring(0, 6) + '********' + idCard.substring(14)
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏显示邮箱
|
||||
* 例如:test@example.com -> t***@example.com
|
||||
*/
|
||||
static maskEmail(email: string): string {
|
||||
if (!email || !email.includes('@')) {
|
||||
return email
|
||||
}
|
||||
const [username, domain] = email.split('@')
|
||||
if (username.length <= 1) {
|
||||
return email
|
||||
}
|
||||
return username[0] + '***@' + domain
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 AES-256 Base64 密钥(工具方法,用于后端/测试)
|
||||
*/
|
||||
static generateAes256KeyBase64(): string {
|
||||
const crypto = getCrypto()
|
||||
const keyBytes = crypto.getRandomValues(new Uint8Array(32)) // 32字节=256位
|
||||
let binary = ''
|
||||
for (let i = 0; i < keyBytes.length; i++) {
|
||||
binary += String.fromCharCode(keyBytes[i])
|
||||
}
|
||||
return btoa(binary)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 AES 加密实例的工厂函数
|
||||
* @param secretKey Base64 编码的密钥(32字节)
|
||||
*/
|
||||
export async function createAesEncrypt(secretKey: string): Promise<AesEncryptUtil> {
|
||||
const aes = new AesEncryptUtil(secretKey)
|
||||
await aes.init()
|
||||
return aes
|
||||
}
|
||||
|
||||
// 导出单例(需要在应用启动时初始化)
|
||||
let aesInstance: AesEncryptUtil | null = null
|
||||
|
||||
/**
|
||||
* 获取 AES 加密实例
|
||||
*/
|
||||
export function getAesInstance(): AesEncryptUtil {
|
||||
if (!aesInstance) {
|
||||
throw new Error('AES 加密工具未初始化,请先调用 initAesEncrypt()')
|
||||
}
|
||||
return aesInstance
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 AES 加密工具(在应用启动时调用)
|
||||
* @param secretKey Base64 编码的密钥(32字节,与后端配置保持一致)
|
||||
*/
|
||||
export async function initAesEncrypt(secretKey: string): Promise<void> {
|
||||
aesInstance = await createAesEncrypt(secretKey)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './aes'
|
||||
@@ -3,3 +3,4 @@
|
||||
*/
|
||||
|
||||
export * from './file'
|
||||
export * from './crypto'
|
||||
|
||||
Reference in New Issue
Block a user