Files
wwjcloud-nest-v1/wwjcloud/src/common/auth/services/AuthService.ts

451 lines
12 KiB
TypeScript
Raw Normal View History

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<AuthToken>,
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,
});
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<any> {
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<any> {
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<any> {
try {
// 根据用户名查找会员
let member = await this.memberService.findByUsername(username);
// 如果用户名没找到,尝试用手机号或邮箱查找
if (!member) {
member = await this.memberService.findByMobile(username);
}
if (!member) {
member = await this.memberService.findByEmail();
}
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;
}
}
/**
*
*/
async bindMobile(mobile: string, mobileCode: string) {
// TODO: 实现绑定手机号逻辑
return { message: 'bindMobile not implemented' };
}
/**
*
*/
async getMobile(mobileCode: string) {
// TODO: 实现获取手机号逻辑
return { message: 'getMobile not implemented' };
}
}