538de50bb1
- Prisma User 模型新增 username 字段(唯一索引) - 注册先查重复再创建,返回友好中文提示(非 500) - 登录支持用户名/手机号/邮箱三种方式 - 前端注册表单增加用户名输入框,预校验 2-20 位格式 - 新增 scripts/deploy.sh:一键构建并部署主站+镜像站+重启后端+重载 Nginx - 镜像站 www.yuzhiran.com.cn Nginx 配置与主站同步 - AI 助手架构升级:用户端/管理后台均采用完整 Tool Calling 架构 - 新增 UserAiAssistantService(18 工具)+ AiAssistantController - admin 助手新增 search + mark-all-notifications-read 工具 - 修复注册 500 错误:catch Prisma P2002 → BadRequestException - Baidu Analytics Script 注入 root layout
60 lines
1.9 KiB
TypeScript
Executable File
60 lines
1.9 KiB
TypeScript
Executable File
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import * as bcrypt from 'bcryptjs';
|
|
import { PrismaService } from '../../prisma/prisma.service';
|
|
|
|
@Injectable()
|
|
export class AdminService {
|
|
constructor(
|
|
private prisma: PrismaService,
|
|
private jwtService: JwtService,
|
|
) {}
|
|
|
|
async login(username: string, password: string) {
|
|
const admin = await this.prisma.adminUser.findUnique({
|
|
where: { username },
|
|
include: { role: true },
|
|
});
|
|
if (!admin || admin.status !== 'ACTIVE') {
|
|
throw new UnauthorizedException('管理员账号不可用');
|
|
}
|
|
|
|
const isValid = await bcrypt.compare(password, admin.passwordHash);
|
|
if (!isValid) {
|
|
throw new UnauthorizedException('密码错误');
|
|
}
|
|
|
|
await this.prisma.adminUser.update({
|
|
where: { id: admin.id },
|
|
data: { lastLoginAt: new Date() },
|
|
});
|
|
|
|
const token = this.jwtService.sign(
|
|
{ sub: admin.id, type: 'admin' },
|
|
{ expiresIn: '7d' },
|
|
);
|
|
|
|
return { token, id: admin.id, username: admin.username, role: admin.role?.name };
|
|
}
|
|
|
|
async getDashboard() {
|
|
const [userCount, courseCount, contentCount, promptCount, orderCount] = await Promise.all([
|
|
this.prisma.user.count({ where: { deletedAt: null } }),
|
|
this.prisma.course.count({ where: { deletedAt: null } }),
|
|
this.prisma.content.count({ where: { deletedAt: null, status: 'PUBLISHED' } }),
|
|
this.prisma.prompt.count({ where: { deletedAt: null, status: 'PUBLISHED' } }),
|
|
this.prisma.order.count(),
|
|
]);
|
|
|
|
return {
|
|
stats: { userCount, courseCount, contentCount, promptCount, orderCount },
|
|
};
|
|
}
|
|
|
|
async logAction(adminId: number, action: string, target?: string, detail?: string, ip?: string) {
|
|
return this.prisma.adminLog.create({
|
|
data: { adminId, action, target, detail, ip },
|
|
});
|
|
}
|
|
}
|