import { Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ConfigService } from '@nestjs/config'; import * as bcrypt from 'bcrypt'; import { AuthToken } from '../entities/AuthToken'; import { LoginDto, RefreshTokenDto, LogoutDto } from '../dto/AuthDto'; // 导入Admin和Member服务 import { CoreAdminService } from '../../admin/services/core/CoreAdminService'; import { CoreMemberService } from '../../member/services/core/CoreMemberService'; @Injectable() export class AuthService { constructor( @InjectRepository(AuthToken) private readonly authTokenRepository: Repository, private readonly jwtService: JwtService, private readonly configService: ConfigService, private readonly adminService: CoreAdminService, private readonly memberService: CoreMemberService, ) {} /** * 管理员登录 */ async adminLogin(loginDto: LoginDto, ipAddress: string, userAgent: string) { const { username, password, siteId = 0 } = loginDto; // 调用AdminService验证用户名密码 const adminUser = await this.validateAdminUser(username, password, siteId); if (!adminUser) { throw new UnauthorizedException('用户名或密码错误'); } // 生成JWT Token const tokenPayload = { userId: adminUser.uid, username: adminUser.username, userType: 'admin', siteId, }; const accessToken = this.jwtService.sign(tokenPayload, { expiresIn: this.configService.get('JWT_EXPIRES_IN', '7d'), }); const refreshToken = this.jwtService.sign(tokenPayload, { expiresIn: this.configService.get('JWT_REFRESH_EXPIRES_IN', '30d'), }); // 计算过期时间 const expiresIn = this.configService.get('JWT_EXPIRES_IN', '7d'); const refreshExpiresIn = this.configService.get('JWT_REFRESH_EXPIRES_IN', '30d'); const expiresAt = this.calculateExpiryDate(expiresIn); const refreshExpiresAt = this.calculateExpiryDate(refreshExpiresIn); // 保存Token到数据库 const authToken = this.authTokenRepository.create({ token: accessToken, userId: adminUser.uid, userType: 'admin', siteId, expiresAt, refreshToken, refreshExpiresAt, ipAddress: ipAddress, userAgent: userAgent, deviceType: this.detectDeviceType(userAgent), isRevoked: 0, }); await this.authTokenRepository.save(authToken); // 更新管理员登录信息 await this.adminService.updateLoginInfo(adminUser.uid, ipAddress); return { accessToken, refreshToken, expiresIn, user: { userId: adminUser.uid, username: adminUser.username, realname: adminUser.real_name, userType: 'admin', siteId, }, }; } /** * 会员登录 */ async memberLogin(loginDto: LoginDto, ipAddress: string, userAgent: string) { const { username, password, siteId = 0 } = loginDto; // 调用MemberService验证用户名密码 const memberUser = await this.validateMemberUser(username, password, siteId); if (!memberUser) { throw new UnauthorizedException('用户名或密码错误'); } // 生成JWT Token const tokenPayload = { userId: memberUser.member_id, username: memberUser.username, userType: 'member', siteId, }; const accessToken = this.jwtService.sign(tokenPayload, { expiresIn: this.configService.get('JWT_EXPIRES_IN', '7d'), }); const refreshToken = this.jwtService.sign(tokenPayload, { expiresIn: this.configService.get('JWT_REFRESH_EXPIRES_IN', '30d'), }); // 计算过期时间 const expiresIn = this.configService.get('JWT_EXPIRES_IN', '7d'); const refreshExpiresIn = this.configService.get('JWT_REFRESH_EXPIRES_IN', '30d'); const expiresAt = this.calculateExpiryDate(expiresIn); const refreshExpiresAt = this.calculateExpiryDate(refreshExpiresIn); // 保存Token到数据库 const authToken = this.authTokenRepository.create({ token: accessToken, userId: memberUser.member_id, userType: 'member', siteId, expiresAt, refreshToken, refreshExpiresAt, ipAddress: ipAddress, userAgent: userAgent, deviceType: this.detectDeviceType(userAgent), isRevoked: 0, }); await this.authTokenRepository.save(authToken); // 更新会员登录信息 await this.memberService.updateLastLogin(memberUser.member_id, { ip: ipAddress, address: ipAddress, // 这里可以调用IP地址解析服务 device: this.detectDeviceType(userAgent), }); return { accessToken, refreshToken, expiresIn, user: { userId: memberUser.member_id, username: memberUser.username, nickname: memberUser.nickname, userType: 'member', siteId, }, }; } /** * 刷新Token */ async refreshToken(refreshTokenDto: RefreshTokenDto) { const { refreshToken } = refreshTokenDto; try { // 验证刷新Token const payload = this.jwtService.verify(refreshToken); // 检查数据库中的Token记录 const tokenRecord = await this.authTokenRepository.findOne({ where: { refreshToken, isRevoked: 0 }, }); if (!tokenRecord || tokenRecord.isRefreshExpired()) { throw new UnauthorizedException('刷新Token无效或已过期'); } // 生成新的访问Token const newTokenPayload = { userId: payload.userId, username: payload.username, userType: payload.userType, siteId: payload.siteId, }; const newAccessToken = this.jwtService.sign(newTokenPayload, { expiresIn: this.configService.get('JWT_EXPIRES_IN', '7d'), }); // 更新数据库中的Token tokenRecord.token = newAccessToken; tokenRecord.expiresAt = this.calculateExpiryDate(this.configService.get('JWT_EXPIRES_IN', '7d')); await this.authTokenRepository.save(tokenRecord); return { accessToken: newAccessToken, expiresIn: this.configService.get('JWT_EXPIRES_IN', '7d'), }; } catch (error) { throw new UnauthorizedException('刷新Token无效'); } } /** * 登出 */ async logout(logoutDto: LogoutDto) { const { token } = logoutDto; // 撤销Token const tokenRecord = await this.authTokenRepository.findOne({ where: { token, isRevoked: 0 }, }); if (tokenRecord) { tokenRecord.isRevoked = 1; tokenRecord.revokedAt = new Date(); tokenRecord.revokedReason = '用户主动登出'; await this.authTokenRepository.save(tokenRecord); } return { message: '登出成功' }; } /** * 验证Token */ async validateToken(token: string): Promise { try { // 验证JWT Token const payload = this.jwtService.verify(token); // 检查数据库中的Token记录 const tokenRecord = await this.authTokenRepository.findOne({ where: { token, isRevoked: 0 }, }); if (!tokenRecord || tokenRecord.isExpired()) { return null; } return payload; } catch (error) { return null; } } /** * 获取用户Token信息 */ async getUserTokens(userId: number, userType: string, siteId: number = 0) { return await this.authTokenRepository.find({ where: { userId, userType, siteId, isRevoked: 0 }, order: { createdAt: 'DESC' }, }); } /** * 撤销用户所有Token */ async revokeUserTokens(userId: number, userType: string, siteId: number = 0, reason: string = '管理员撤销') { const tokens = await this.authTokenRepository.find({ where: { userId, userType, siteId, isRevoked: 0 }, }); for (const token of tokens) { token.isRevoked = 1; token.revokedAt = new Date(); token.revokedReason = reason; } await this.authTokenRepository.save(tokens); return { message: 'Token撤销成功', count: tokens.length }; } /** * 清理过期Token */ async cleanupExpiredTokens() { const expiredTokens = await this.authTokenRepository .createQueryBuilder('token') .where('token.expires_at < :now', { now: new Date() }) .andWhere('token.is_revoked = :revoked', { revoked: 0 }) .getMany(); for (const token of expiredTokens) { token.isRevoked = 1; token.revokedAt = new Date(); token.revokedReason = 'Token过期自动清理'; } if (expiredTokens.length > 0) { await this.authTokenRepository.save(expiredTokens); } return { message: '过期Token清理完成', count: expiredTokens.length }; } /** * 计算过期时间 */ private calculateExpiryDate(expiresIn: string): Date { const now = new Date(); const unit = expiresIn.slice(-1); const value = parseInt(expiresIn.slice(0, -1)); switch (unit) { case 's': return new Date(now.getTime() + value * 1000); case 'm': return new Date(now.getTime() + value * 60 * 1000); case 'h': return new Date(now.getTime() + value * 60 * 60 * 1000); case 'd': return new Date(now.getTime() + value * 24 * 60 * 60 * 1000); default: return new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); // 默认7天 } } /** * 检测设备类型 */ private detectDeviceType(userAgent: string): string { if (/mobile|android|iphone|ipad|phone/i.test(userAgent)) { return 'mobile'; } else if (/app|application/i.test(userAgent)) { return 'app'; } else { return 'web'; } } /** * 验证管理员用户 */ private async validateAdminUser(username: string, password: string, siteId: number): Promise { try { // 根据用户名查找管理员 const admin = await this.adminService.getAdminByUsername(username); if (!admin) { return null; } // 验证密码 const isValidPassword = await this.adminService.validatePassword(admin.uid, password); if (!isValidPassword) { return null; } // 检查状态 if (admin.status !== 1 || admin.is_del !== 0) { return null; } return admin; } catch (error) { return null; } } /** * 验证会员用户 */ private async validateMemberUser(username: string, password: string, siteId: number): Promise { try { // 根据用户名查找会员 let member = await this.memberService.findByUsername(username); // 如果用户名没找到,尝试用手机号或邮箱查找 if (!member) { member = await this.memberService.findByMobile(username); } if (!member) { member = await this.memberService.findByEmail(username); } if (!member) { return null; } // 验证密码 const isValidPassword = await bcrypt.compare(password, member.password); if (!isValidPassword) { return null; } // 检查状态 if (member.status !== 1 || member.is_del !== 0) { return null; } return member; } catch (error) { return null; } } }