@bigworm/bigworm-framework
v8.7.5
Published
BigWorm Bootstrap 前端框架
Downloads
230
Readme
@bigworm/bigworm-framework
前端 HTTP 通信框架。提供 EnvelopeAxios(Envelope 协议)和 OpenAxios(常规 JSON)双客户端,内置数字签名(HMAC/RSA/SM2)、JWT 认证、requestId/timestamp 防重放校验、文件上传/下载、HTTP 调试日志等功能。
安装
npm install @bigworm/bigworm-framework对等依赖(按需安装):
npm install crypto-js@^4.2.0 # HMAC 签名必需
npm install jsencrypt@^3.5.4 # RSA 签名可选
npm install sm-crypto@^0.3.13 # SM2 国密可选模块结构
src/
├── core/
│ ├── EnvelopeAxios.ts # Envelope 协议 HTTP 客户端
│ └── OpenAxios.ts # 常规 JSON HTTP 客户端
├── config/
│ ├── SecurityConfig.ts # 统一安全配置接口及默认值
│ ├── EnvelopeAxiosConfig.ts
│ ├── OpenAxiosConfig.ts
│ ├── RequestConfig.ts
│ ├── UploadConfig.ts
│ ├── DownloadConfig.ts
│ └── StreamConfig.ts
├── crypto/
│ ├── CryptoAdapterFactory.ts
│ ├── HmacAdapter.ts
│ ├── RSAAdapter.ts
│ └── SM2Adapter.ts
├── tools/
│ ├── JwtUtil.ts # JWT Token 管理
│ ├── HmacUtil.ts # HMAC 用户专属密钥管理
│ ├── StorageUtil.ts # 凭证存储公共层(存储位置/键名/只读可配)
│ ├── SignatureUtil.ts
│ ├── SecurityUtil.ts
│ └── DebugUtil.ts # HTTP 调试日志
└── error/
└── CheckedError.ts核心:EnvelopeAxios
用于与 bigworm 后端 Envelope 协议接口通信。
初始化
import { EnvelopeAxios } from '@bigworm/bigworm-framework'
const http = EnvelopeAxios.getInstance({
baseURL: 'http://localhost:8080',
timeout: 30000,
onError: (error) => ElMessage.error(error.message), // 全局错误回调
security: {
// 所有选项均有默认值,下方为默认值说明
envelopeEnabled: true, // 启用 Envelope 报文验证
validateRequestId: true, // 校验响应 requestId 与请求一致
validateTimestamp: true, // 校验响应时间戳防重放
maxTimeDiff: 300000, // 最大时间差(毫秒,默认 5 分钟)
signatureEnabled: true, // 启用数字签名
requestAlgorithm: 'hmac', // 请求签名算法:'hmac' | 'rsa' | 'sm2'
responseAlgorithm: 'rsa', // 响应验签算法:'rsa' | 'sm2' | 'hmac'
signRequest: true, // 对请求签名
verifyResponse: true, // 验证响应签名
useUserHmacKey: true, // 启用用户专属 HMAC 密钥
publicKey: undefined, // 后端公钥(未配置则用内置默认公钥)
hmacKey: undefined, // 自定义默认 HMAC 密钥
authType: 'jwt', // 认证类型:'jwt' | 'session'
jwtHeaderName: 'Authorization',
jwtTokenPrefix: 'Bearer ',
// 凭证存储(对齐上层应用的存储位置与键名,详见「凭证存储」一节)
jwtStorageType: 'localStorage',
jwtStorageKey: 'jwt_token',
jwtStorageReadOnly: false, // true 时只读,保护宿主会话不被误删
jwtTokenProvider: undefined, // 自定义读取器,Token 不在 storage 时使用
hmacStorageType: 'localStorage',
hmacStorageKey: 'hmac_user_key',
hmacStorageReadOnly: false
}
})配置优先级:
getInstance(config)>globalThis.CONFIG.security> 默认值
请求方法
// POST
const response = await http.post('/api/user/login', requestEnvelope)
// GET(params 可以是 Record 或 Envelope)
const response = await http.get('/api/user/info', { userId: '123' })
// 文件上传
const response = await http.upload('/api/file/upload', formData, {
onUploadProgress: (e) => console.log(`${Math.round(e.loaded * 100 / e.total!)}%`)
})
// 文件下载
const result = await http.download('/api/file/download', requestEnvelope, {
onDownloadProgress: (e) => console.log(`${Math.round(e.loaded * 100 / e.total!)}%`)
})
// result.blob: Blob, result.filename: string
const url = URL.createObjectURL(result.blob)拦截器
// 请求拦截(添加业务字段)
const reqId = http.addRequestInterceptor((envelope) => {
envelope.header().entity().data().setStringItem('sessionId', getSessionId())
return envelope
})
// 响应拦截(统一错误处理)
const resId = http.addResponseInterceptor((envelope) => {
const success = envelope.header().entity().data().getStringItem('success')
if (success === 'false') {
const msg = envelope.header().entity().data().getStringItem('message')
if (msg?.includes('未登录')) window.location.href = '/login'
}
return envelope
})
// 移除拦截器
http.removeRequestInterceptor(reqId)
http.removeResponseInterceptor(resId)核心:OpenAxios
与 EnvelopeAxios API 完全一致,区别是不强制 Envelope 协议,接收和返回任意 JSON。
import { OpenAxios } from '@bigworm/bigworm-framework'
const http = OpenAxios.getInstance({
baseURL: 'https://api.example.com',
security: { signRequest: true, verifyResponse: true, requestAlgorithm: 'rsa' }
})
const data = await http.post('/api/login', { username: 'admin', password: '123' })
const info = await http.get('/api/user/info', { userId: '123' })安全配置详解
签名算法
| 方向 | 配置项 | 可选值 | 说明 |
|------|--------|--------|------|
| 请求签名(前端→后端) | requestAlgorithm | hmac(默认)/ rsa / sm2 | HMAC 性能最优,推荐 |
| 响应验签(后端→前端) | responseAlgorithm | rsa(默认)/ sm2 / hmac | 需与后端 response-algorithm 一致 |
签名内容放入 HTTP Header X-Signature;requestId 和 timestamp 同时放入 X-Request-ID、X-Timestamp Header 以及 envelope.header 双通道传输(兼容防火墙过滤 Header 的场景)。
用户专属 HMAC(useUserHmacKey)
登录后通过 HmacUtil.deriveKey(password, account) 派生用户专属密钥,再通过 HmacUtil.saveKey(key) 保存。框架在初始化时自动从 storage 恢复并注入签名器,实现每个用户使用独立的 HMAC 密钥。需与后端 server.security.signature.use-user-hmac-key 保持一致。
全局配置(globalThis.CONFIG)
框架从 globalThis.CONFIG.security 读取配置,支持在运行时注入(Vite 环境变量、Webpack process.env、或运行时配置文件均可):
// 在 main.ts / index.ts 中提前设置
;(globalThis as any).CONFIG = {
security: {
publicKey: import.meta.env.VITE_PUBLIC_KEY,
requestAlgorithm: 'hmac',
responseAlgorithm: 'rsa'
}
}JwtUtil — JWT Token 管理
import { JwtUtil } from '@bigworm/bigworm-framework'
// 登录成功后保存 token
JwtUtil.saveToken('eyJhbGci...')
// 获取 token
const token = JwtUtil.getToken() // 返回 string | null
// 解析 payload(仅客户端解码,不验证签名)
const payload = JwtUtil.parseToken(token)
console.log(payload?.sub, payload?.exp)
// 检查是否过期
const expired = JwtUtil.isTokenExpired() // true | false | null
// 获取剩余有效时间(毫秒)
const remaining = JwtUtil.getTokenRemainingTime()
// 登出时清除
JwtUtil.clearToken()
// 切换存储类型 / 键名(更推荐用 security 配置项声明,见下节)
JwtUtil.setStorageType('sessionStorage')
JwtUtil.setStorageKey('session_id')凭证存储
以 npm 包形式寄宿进宿主工程时,两边 JWT 的存储位置与键名往往不一致。配置三个参数即可让框架直读宿主凭证,无需任何镜像同步。
「寄宿」指本库被打包进上层应用(两侧后端各自独立进程),与后端 hosted-mode(已于 8.7.0 移除)无关。
| 配置项 | 环境变量 | 默认值 |
|---|---|---|
| jwtStorageType | VITE_SECURITY_JWT_STORAGE_TYPE | 'localStorage' |
| jwtStorageKey | VITE_SECURITY_JWT_STORAGE_KEY | 'jwt_token' |
| jwtStorageReadOnly | VITE_SECURITY_JWT_STORAGE_READ_ONLY | false |
| jwtTokenProvider | —(函数,仅代码配置) | undefined |
| hmacStorageType | VITE_SECURITY_HMAC_STORAGE_TYPE | 'localStorage' |
| hmacStorageKey | VITE_SECURITY_HMAC_STORAGE_KEY | 'hmac_user_key' |
| hmacStorageReadOnly | VITE_SECURITY_HMAC_STORAGE_READ_ONLY | false |
默认值与历史行为逐字节一致,独立部署的应用无需配置任何一项。
配置在哪一侧?
答案几乎总是「宿主的 .env」。 存储位置是宿主环境的既成事实,理应由宿主声明;客居方不传该项时,框架的三级合并会自动兜到全局配置。
# 宿主工程的 .env.development —— 宿主把 JWT 存在 sessionStorage['session_id']
VITE_SECURITY_JWT_STORAGE_TYPE=sessionStorage
VITE_SECURITY_JWT_STORAGE_KEY=session_id
VITE_SECURITY_JWT_STORAGE_READ_ONLY=true宿主只需保证 applyMetaEnvToGlobalConfig(import.meta.env) 在任何客居方模块求值之前执行(通常放在入口文件的第一个 import)。客居方什么都不用改:
// 客居方代码保持原样,不传 storage 相关配置
EnvelopeAxios.getInstance('my-app', {
baseURL: 'http://localhost:5001',
security: { signRequest: true, verifyResponse: true }
})只有当同一宿主下不同客居方需要读不同凭证时,才在实例级覆盖。此时它是 INSTANCE 级(高于 GLOBAL),会盖过宿主的全局配置:
EnvelopeAxios.getInstance('special', {
security: { jwtStorageKey: 'another_token' }
})⚠️ 注意存储槽位是全局单例:两个实例配了不同的键名时,同级 first-wins(先创建者胜出)并打 warn,后者不会生效。详见设计文档的「设计边界」一节。
⚠️ 只读模式的责任转移
jwtStorageReadOnly: true 时框架放弃「凭证失效时自我清理」的能力——saveToken / clearToken 均被跳过。宿主必须接管未认证处理(注入 onError 并执行登出跳转),否则会出现「请求一直失败但不跳登录页」的现象。
判断标准是谁负责登录:应用自己登录 → false;宿主登录、本应用只是复用凭证 → true。
还有一处容易被忽略:客居方的业务代码若把 security-00040(HMAC 密钥失效,属纯签名问题)也判为「强制重新登录」,会把宿主的登录态一起踢掉——框架层 readOnly 的克制就被业务层完全抵消了。该码由框架自动回退默认 HMAC 签名器处理,下次请求往往即恢复。
排错
| 现象 | 排查方向 |
|---|---|
| 请求头没有 Authorization | 宿主的 applyMetaEnvToGlobalConfig 是否早于客居方模块求值(入口第一个 import);改了 .env 后 dev server 是否重启 |
| 配置改了但不生效 | Vite 依赖预构建缓存陈旧,rm -rf node_modules/.vite 后重启;升级 npm 包后尤其容易踩 |
| 存储位置没切过去 | 控制台搜 [StorageUtil] 的 warn:同级配置冲突时保留先到者 |
| 一直失败但不跳登录页 | readOnly 下框架不再自我清理,检查宿主是否注入了 onError / onUnauthorized |
详见 docs/凭证存储与寄宿模式设计.md,其中包含优先级仲裁表、失效链路时序与设计边界说明。
HmacUtil — 用户专属 HMAC 密钥管理
import { HmacUtil } from '@bigworm/bigworm-framework'
// 登录成功后:派生 + 保存用户专属密钥(PBKDF2,与后端算法参数一致)
const key = HmacUtil.deriveKey(password, account)
HmacUtil.saveKey(key)
// 获取当前用户密钥
const key = HmacUtil.getKey() // string | null
// 登出时清除
HmacUtil.clearKey()
// 切换存储类型(默认 localStorage)
HmacUtil.setStorageType('sessionStorage')HTTP 调试日志
开发环境(DEBUG 日志级别)下自动输出请求/响应详情,生产环境零开销:
═══════════════════════════════════════════
📥 HTTP 请求信息
═══════════════════════════════════════════
📍 请求路径: POST /api/user/login
📋 HTTP 请求头:
X-Request-ID: req-abc123...
X-Timestamp: 1736604123456
X-Signature: MIIBIjAN...
📦 请求体:
{
"header": { "default": { "requestid": "req-abc123...", "timestamp": "..." } },
"body": { "default": { "username": "admin" } }
}
═══════════════════════════════════════════构建
npm run build # ESM + CJS + UMD
npm run build:esm # ES Module
npm run build:cjs # CommonJS
npm run build:umd # UMD(浏览器直接使用)
npm run build:all # 构建并压缩(发布前使用)输出格式:
dist/esm/— ES Module(含.d.ts类型声明)dist/cjs/— CommonJSdist/umd/— UMD
测试
npm test # 运行所有测试
npm run test:watch # 监听模式
npm run test:coverage # 覆盖率报告