import { DepartmentService } from '@server/models/department/department.service'; import { UserProfile, db, JwtPayload, RolePerms, ObjectType, } from '@nicestack/common'; import { JwtService } from '@nestjs/jwt'; import { env } from '@server/env'; import { redis } from '@server/utils/redis/redis.service'; import EventBus from '@server/utils/event-bus'; import { RoleMapService } from '@server/models/rbac/rolemap.service'; import { Request } from "express" interface ProfileResult { staff: UserProfile | undefined; error?: string; } interface TokenVerifyResult { id?: string; error?: string; } export function extractTokenFromHeader(request: Request): string | undefined { const [type, token] = extractTokenFromAuthorization(request.headers.authorization) return type === 'Bearer' ? token : undefined; } export function extractTokenFromAuthorization(authorization: string): string | undefined { const [type, token] = authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } export class UserProfileService { public static readonly instance = new UserProfileService(); private readonly CACHE_TTL = 3600; // 缓存时间1小时 private readonly jwtService: JwtService; private readonly departmentService: DepartmentService; private readonly roleMapService: RoleMapService; private constructor() { this.jwtService = new JwtService(); this.departmentService = new DepartmentService(); this.roleMapService = new RoleMapService(this.departmentService); EventBus.on("dataChanged", ({ type, data }) => { if (type === ObjectType.STAFF) { // 确保 data 是数组,如果不是则转换为数组 const dataArray = Array.isArray(data) ? data : [data]; for (const item of dataArray) { if (item.id) { redis.del(this.getProfileCacheKey(item.id)); } } } }); } public getProfileCacheKey(id: string) { return `user-profile-${id}`; } /** * 验证并解析token */ public async verifyToken(token?: string): Promise { if (!token) { return {}; } try { const { sub: id } = await this.jwtService.verifyAsync(token, { secret: env.JWT_SECRET, }); return { id }; } catch (error) { return { error: error instanceof Error ? error.message : 'Token verification failed', }; } } /** * 通过Token获取用户信息 */ public async getUserProfileByToken(token?: string): Promise { const { id, error } = await this.verifyToken(token); if (error) { return { staff: undefined, error, }; } return await this.getUserProfileById(id); } /** * 通过用户ID获取用户信息 */ public async getUserProfileById(id?: string): Promise { if (!id) { return { staff: undefined }; } try { const cachedProfile = await this.getCachedProfile(id); if (cachedProfile) { return { staff: cachedProfile }; } const staff = await this.getBaseProfile(id); if (!staff) { throw new Error(`User with id ${id} does not exist`); } await this.populateStaffExtras(staff); await this.cacheProfile(id, staff); return { staff }; } catch (error) { return { staff: undefined, error: error instanceof Error ? error.message : 'Failed to get user profile', }; } } /** * 从缓存获取用户信息 */ private async getCachedProfile(id: string): Promise { const cachedData = await redis.get(this.getProfileCacheKey(id)); if (!cachedData) return null; try { const profile = JSON.parse(cachedData) as UserProfile; return profile.id === id ? profile : null; } catch { return null; } } /** * 缓存用户信息 */ private async cacheProfile(id: string, profile: UserProfile): Promise { await redis.set( this.getProfileCacheKey(id), JSON.stringify(profile), 'EX', this.CACHE_TTL, ); } /** * 获取基础用户信息 */ private async getBaseProfile(id: string): Promise { return (await db.staff.findUnique({ where: { id }, select: { id: true, deptId: true, department: true, domainId: true, domain: true, showname: true, username: true, phoneNumber: true, }, })) as unknown as UserProfile; } /** * 填充用户权限信息 */ private async populateStaffExtras(staff: UserProfile): Promise { const [deptIds, parentDeptIds, permissions] = await Promise.all([ staff.deptId ? this.departmentService.getDescendantIdsInDomain(staff.deptId) : [], staff.deptId ? this.departmentService.getAncestorIds([staff.deptId]) : [], this.roleMapService.getPermsForObject({ domainId: staff.domainId, staffId: staff.id, deptId: staff.deptId, }) as Promise, ]); Object.assign(staff, { deptIds, parentDeptIds, permissions, }); } }