122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
// src/service/user.service.ts
|
||
import { Body, httpError, Inject, Provide } from '@midwayjs/core';
|
||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||
import { Repository } from 'typeorm';
|
||
import * as bcrypt from 'bcryptjs';
|
||
import { JwtService } from '@midwayjs/jwt';
|
||
import { User } from '../entity/user.entity';
|
||
import { LoginResDTO } from '../dto/user.dto';
|
||
import { plainToInstance } from 'class-transformer';
|
||
import { MailService } from './mail.service';
|
||
import { AuthCodeService } from './authCode.service';
|
||
import { DeviceWhitelistService } from './deviceWhitelist.service';
|
||
|
||
@Provide()
|
||
export class UserService {
|
||
@InjectEntityModel(User)
|
||
userModel: Repository<User>;
|
||
|
||
@Inject()
|
||
jwtService: JwtService;
|
||
|
||
@Inject()
|
||
mailService: MailService;
|
||
|
||
@Inject()
|
||
authCodeService: AuthCodeService;
|
||
|
||
@Inject()
|
||
deviceWhitelistService: DeviceWhitelistService;
|
||
|
||
async requestAuthCode(deviceId: string) {
|
||
const code = await this.authCodeService.generateCode(deviceId);
|
||
|
||
await this.mailService.sendMail(
|
||
'info@canpouches.com',
|
||
'Your Login Authorization Code',
|
||
`Your authorization code is: ${code}, valid for 10 minutes.`
|
||
);
|
||
}
|
||
|
||
async login(@Body() body): Promise<LoginResDTO> {
|
||
const { username, password, deviceId, authCode } = body;
|
||
|
||
const user = await this.userModel.findOne({
|
||
where: { username, isActive: true },
|
||
});
|
||
if (!user || !(await bcrypt.compare(password, user.password))) {
|
||
throw new Error('用户名或者密码错误');
|
||
}
|
||
const isWhite = await this.deviceWhitelistService.isWhitelisted(deviceId);
|
||
if (!isWhite) {
|
||
if (!authCode) {
|
||
await this.requestAuthCode(deviceId);
|
||
const err = new httpError.BadRequestError('非白名单设备请填写验证码');
|
||
(err as any).code = 10001; // 添加业务错误码
|
||
throw err;
|
||
}
|
||
|
||
const valid = await this.authCodeService.verifyCode(deviceId, authCode);
|
||
if (!valid) {
|
||
throw new Error('验证码错误');
|
||
}
|
||
|
||
// 校验通过后,将设备加入白名单
|
||
await this.deviceWhitelistService.addToWhitelist(deviceId);
|
||
}
|
||
|
||
// 生成 JWT,包含角色和权限信息
|
||
const token = await this.jwtService.sign({
|
||
id: user.id,
|
||
deviceId,
|
||
username: user.username,
|
||
});
|
||
|
||
return {
|
||
token, //role: user.role,
|
||
username: user.username,
|
||
userId: user.id,
|
||
permissions: user.permissions,
|
||
};
|
||
}
|
||
|
||
async addUser(username: string, password: string) {
|
||
const existingUser = await this.userModel.findOne({
|
||
where: { username },
|
||
});
|
||
if (existingUser) {
|
||
throw new Error('用户已存在');
|
||
}
|
||
const hashedPassword = await bcrypt.hash(password, 10);
|
||
const user = this.userModel.create({
|
||
username,
|
||
password: hashedPassword,
|
||
});
|
||
return this.userModel.save(user);
|
||
}
|
||
|
||
async listUsers(current: number, pageSize: number) {
|
||
const [items, total] = await this.userModel.findAndCount({
|
||
skip: (current - 1) * pageSize,
|
||
take: pageSize,
|
||
});
|
||
return { items, total, current, pageSize };
|
||
}
|
||
|
||
async toggleUserActive(userId: number, isActive: boolean) {
|
||
const user = await this.userModel.findOne({ where: { id: userId } });
|
||
if (!user) {
|
||
throw new Error('User not found');
|
||
}
|
||
user.isActive = isActive;
|
||
return this.userModel.save(user);
|
||
}
|
||
|
||
async getUser(userId: number) {
|
||
return plainToInstance(
|
||
User,
|
||
await this.userModel.findOne({ where: { id: userId } })
|
||
);
|
||
}
|
||
}
|