核心保证
✅ 1. 异步链起点包裹 - 已保证
在 RequestContextMiddleware 中,所有请求都被 RequestContext.run() 包裹:
// middleware 自动执行(AppModule 全局注册)
RequestContext.run(contextData, () => {
next(); // 后续所有操作都在此上下文中
});
你可以放心:在任何 Service 中访问 getCurrentUserId() 都是安全的。
✅ 2. Promise.all 并发 - 安全可用
// ✅ 这样是安全的
async findAll() {
const userId = this.userContextUtil.getCurrentUserId();
// Promise.all 在同一个请求的 RequestContext 中
const rows = await Promise.all(
items.map(async (item) => {
const customer = await this.prisma.customer.findUnique(...);
return { ...item, customer, createdBy: userId };
})
);
return rows;
}
你可以放心:所有 Promise.all 中的子任务都能正确访问上下文。
⚠️ 3. 数据存储 - 已优化
中间件现在只存储必要的简单字段:
// 存储的数据(轻量化)
{
userId: BigInt(25), // ✅ 用户 ID
username: "zhangsan", // ✅ 用户名
departmentId: BigInt(1), // ✅ 部门 ID
// ❌ 不再存储完整的用户对象或敏感数据
}
优点:
- 内存占用最小化
- 无敏感数据泄露风险
- 性能最优
如果需要更多用户信息,在 Service 中直接查询数据库:
async create(dto: CreateWorkOrderDto) {
const userId = this.userContextUtil.getCurrentUserId();
// 如果需要完整用户信息,直接查询
const user = await this.prisma.user.findUnique({
where: { id: userId }
});
// 使用 user 的各个字段
return { ...result, createdBy: user.name };
}
禁止场景
❌ 不要在 setTimeout 中使用
// ❌ 错误
setTimeout(() => {
const userId = this.userContextUtil.getCurrentUserId(); // undefined
}, 100);
// ✅ 正确:在 async 中使用
await this.delay(100); // 实现:return new Promise(resolve => setTimeout(resolve, ms))
const userId = this.userContextUtil.getCurrentUserId();
❌ 不要在 Worker Thread 中使用
// ❌ 错误:Worker 有独立的事件循环
const worker = new Worker('./worker.js');
// ✅ 正确:传递 userId 给 Worker
const userId = this.userContextUtil.getCurrentUserId();
worker.postMessage({ userId, data });
❌ 不要在 Queue 系统中直接依赖
// ❌ 错误:Queue worker 中 AsyncLocalStorage 为空
async handleJobCreated(dto) {
const userId = this.userContextUtil.getCurrentUserId(); // undefined
await this.queue.add('processOrder', { userId, dto });
}
// ✅ 正确:显式传递 userId
async handleJobCreated(dto) {
const userId = this.userContextUtil.getCurrentUserId();
await this.queue.add('processOrder', { userId, dto }); // 传入 userId
}
// Queue worker 中
async processOrder(job) {
const { userId, dto } = job.data; // 从 job.data 获取 userId
// 现在可以使用 userId
}
常见用法示例
Service 中最常见的用法
@Injectable()
export class WorkOrdersService {
constructor(
private readonly prisma: PrismaService,
private readonly userContextUtil: UserContextUtil
) {}
async create(dto: CreateWorkOrderDto) {
// 1. 获取当前用户 ID(从 AsyncLocalStorage)
const userId = this.userContextUtil.getCurrentUserId();
// 2. 验证(可选)
if (!userId) {
throw new UnauthorizedException('User not authenticated');
}
// 3. 业务逻辑 - 使用 userId
const workOrder = await this.prisma.workOrder.create({
data: {
orderNo: this.generateOrderNumber(),
customerId: BigInt(dto.customerId),
createBy: userId, // ✅ 使用 userId
updateBy: userId, // ✅ 使用 userId
currentHandlerId: userId, // ✅ 使用 userId
}
});
// 4. 返回结果
return workOrder;
}
async findAll(queryDto: QueryWorkOrderDto) {
const userId = this.userContextUtil.getCurrentUserId();
// Promise.all 在并发查询中
const rows = await Promise.all(
workOrders.map(async (order) => {
const customer = await this.prisma.customer.findUnique(...);
return { ...order, customer }; // userId 仍在上下文中可用
})
);
return rows;
}
}
Guard 中的使用(可选)
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private userContextUtil: UserContextUtil) {}
canActivate(context: ExecutionContext): boolean {
// 在 Guard 中也可以访问
const userId = this.userContextUtil.getCurrentUserId();
// 执行权限检查逻辑
return userId ? checkPermission(userId) : false;
}
}
Filter 中的使用(可选)
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
constructor(private userContextUtil: UserContextUtil) {}
catch(exception: unknown, host: ArgumentsHost) {
const userId = this.userContextUtil.getCurrentUserId();
// 记录错误时关联用户信息
this.logger.error('Error occurred', {
userId,
error: exception,
timestamp: new Date().toISOString()
});
}
}
逐步迁移指南
如果有旧代码仍在使用 @Inject(REQUEST) 方式,可以逐步迁移:
之前(旧方式)
@Injectable()
export class OldService {
constructor(@Inject(REQUEST) private request: Request) {}
async process() {
const userId = this.request.user?.id;
}
}
之后(新方式)
@Injectable()
export class NewService {
constructor(private userContextUtil: UserContextUtil) {}
async process() {
const userId = this.userContextUtil.getCurrentUserId();
}
}
迁移步骤
- 在 Service 中注入
UserContextUtil - 将
this.request.user?.id替换为this.userContextUtil.getCurrentUserId() - 移除
@Inject(REQUEST) private request: Request - 测试确认功能正常
性能建议
关于内存使用
// 当前使用(轻量化)
{
userId: BigInt(25), // ~30 bytes
username: "zhangsan", // ~20 bytes
departmentId: BigInt(1), // ~30 bytes
}
// 总计:~80 bytes 每个请求
// 之前存储整个用户对象(浪费内存)
{
id: 25,
username: "zhangsan",
email: "zhangsan@example.com",
password: "hashed_...",
departmentId: 1,
managerId: 2,
status: "1",
createTime: "2024-11-01T12:00:00Z",
updateTime: "2024-11-01T12:00:00Z",
createBy: 1,
updateBy: 2,
// ... 更多字段
}
// 总计:~500+ bytes 每个请求
改进:内存占用减少 85%!
监控和调试
打印当前上下文(调试)
// 在任何 Service 中
const context = RequestContext.get();
console.log('Current context:', context);
// 输出:{ userId: 25n, username: "zhangsan", departmentId: 1n }
验证上下文是否为空
async process() {
const userId = this.userContextUtil.getCurrentUserId();
if (!userId) {
this.logger.warn('User context not found - request may not have proper authentication');
// 决定是否抛出异常或使用默认值
return this.userContextUtil.getCurrentUserIdOrDefault();
}
// 继续业务逻辑
}
添加日志追踪
async create(dto: CreateWorkOrderDto) {
const userId = this.userContextUtil.getCurrentUserId();
this.logger.log(`User ${userId} creating work order`, {
customerId: dto.customerId,
timestamp: new Date().toISOString()
});
// ... 业务逻辑
}
总结检查清单
| 项目 | 状态 | 说明 |
|---|---|---|
| 异步链起点包裹 | ✅ | 在 RequestContextMiddleware 中已实现 |
| Promise.all 支持 | ✅ | 可安全使用在同一请求中 |
| 数据存储优化 | ✅ | 仅存储必要字段,已优化 |
| 敏感数据保护 | ✅ | 不存储 password、tokens 等 |
| 内存占用 | ✅ | 从 500+ bytes 降至 80 bytes/请求 |
| 编译验证 | ✅ | 零编译错误 |
| 向后兼容 | ✅ | 支持渐进迁移 |

837

被折叠的 条评论
为什么被折叠?



