docs: 基本完成初版数据库设计

This commit is contained in:
R524809
2026-10-15 10:09:34 +08:00
parent 63a6993276
commit e6ded00a4c
41 changed files with 2597 additions and 180 deletions
+78
View File
@@ -0,0 +1,78 @@
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { SERVER_CONFIG, type ServerConfig } from '../config/server-config';
/** 最小身份载荷;authVersion 用于与账户当前版本比较,不在 token 中固化角色。 */
export interface AccessTokenClaims {
sub: string;
authVersion: number;
iat: number;
exp: number;
}
@Injectable()
/** 统一签发和验证访问凭证,固定算法、签发方、受众和有效期。 */
export class JwtTokenService {
constructor(
private readonly jwt: JwtService,
@Inject(SERVER_CONFIG) private readonly config: ServerConfig,
) {}
/** 仅供可信登录业务调用;调用方须先验证微信身份并取得真实账户。 */
async signAccessToken(
userId: string,
authVersion: number,
): Promise<string> {
if (
!userId ||
userId.length > 128 ||
!Number.isSafeInteger(authVersion) ||
authVersion < 0
) {
throw new Error('Invalid access token identity');
}
return this.jwt.signAsync(
{ sub: userId, authVersion },
{
secret: this.config.jwtSecret,
algorithm: 'HS256',
issuer: this.config.jwtIssuer,
audience: this.config.jwtAudience,
expiresIn: this.config.jwtExpiresInSeconds,
},
);
}
/** 验签及校验载荷;当前账户状态与权限仍由全局守卫继续核对。 */
async verifyAccessToken(token: string): Promise<AccessTokenClaims> {
try {
const claims = await this.jwt.verifyAsync<AccessTokenClaims>(
token,
{
secret: this.config.jwtSecret,
// 不根据 token 头自由选择算法,并限制凭证用途和签发方。
algorithms: ['HS256'],
issuer: this.config.jwtIssuer,
audience: this.config.jwtAudience,
},
);
// 签名正确仍需检查字段类型及必填时间,拒绝无期限或畸形身份载荷。
if (
typeof claims.sub !== 'string' ||
!claims.sub ||
claims.sub.length > 128 ||
!Number.isSafeInteger(claims.authVersion) ||
claims.authVersion < 0 ||
!Number.isSafeInteger(claims.iat) ||
!Number.isSafeInteger(claims.exp) ||
claims.exp <= claims.iat
) {
throw new Error('Invalid access token claims');
}
return claims;
} catch {
// 过期、伪造或载荷错误统一返回 401,不向客户端暴露验签细节。
throw new UnauthorizedException();
}
}
}