feat: 完成 NestJS 后端核心底座开发 (M1-M6) 和 Ant Design Vue 前端迁移

主要更新:
1. 后端核心底座完成 (M1-M6):
   - 健康检查、指标监控、分布式锁
   - 事件总线、队列系统、事务管理
   - 安全守卫、多租户隔离、存储适配器
   - 审计日志、配置管理、多语言支持

2. 前端迁移到 Ant Design Vue:
   - 从 Element Plus 迁移到 Ant Design Vue
   - 完善 system 模块 (role/menu/dept)
   - 修复依赖和配置问题

3. 文档完善:
   - AI 开发工作流文档
   - 架构约束和开发规范
   - 项目进度跟踪

4. 其他改进:
   - 修复编译错误和类型问题
   - 完善测试用例
   - 优化项目结构
This commit is contained in:
万物街
2025-08-27 11:24:22 +08:00
parent be07b9ffec
commit 1cd5d3bdef
696 changed files with 36708 additions and 16868 deletions

View File

@@ -1,46 +1,30 @@
import { Module, forwardRef } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { Module, forwardRef, Global } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ConfigModule } from '@nestjs/config';
import { AuthToken } from './entities/AuthToken';
import { AuthService } from './services/AuthService';
import { AuthController } from './controllers/AuthController';
import { JwtAuthGuard } from './guards/JwtAuthGuard';
import { RolesGuard } from './guards/RolesGuard';
import { JwtGlobalModule } from './jwt.module';
// 导入Admin和Member模块
import { AdminModule } from '../admin/admin.module';
import { MemberModule } from '../member/member.module';
@Global()
@Module({
imports: [
PassportModule,
TypeOrmModule.forFeature([AuthToken]),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET', 'change_me'),
signOptions: {
expiresIn: configService.get('JWT_EXPIRES_IN', '7d'),
},
}),
inject: [ConfigService],
}),
JwtGlobalModule,
// 导入Admin和Member模块以使用其服务
forwardRef(() => AdminModule),
forwardRef(() => MemberModule),
],
providers: [
AuthService,
JwtAuthGuard,
RolesGuard,
],
providers: [AuthService, JwtAuthGuard, RolesGuard],
controllers: [AuthController],
exports: [
AuthService,
JwtAuthGuard,
RolesGuard,
],
exports: [AuthService, JwtAuthGuard, RolesGuard],
})
export class AuthModule {}
export class AuthModule {}

View File

@@ -1,14 +1,19 @@
import {
Controller,
Post,
Body,
Req,
HttpCode,
import {
Controller,
Post,
Body,
Req,
HttpCode,
HttpStatus,
UseGuards,
Get
Get,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
} from '@nestjs/swagger';
import type { Request } from 'express';
import { AuthService } from '../services/AuthService';
import { LoginDto, RefreshTokenDto, LogoutDto } from '../dto/AuthDto';
@@ -25,13 +30,10 @@ export class AuthController {
@ApiResponse({ status: 200, description: '登录成功' })
@ApiResponse({ status: 401, description: '用户名或密码错误' })
@HttpCode(HttpStatus.OK)
async adminLogin(
@Body() loginDto: LoginDto,
@Req() req: Request
) {
async adminLogin(@Body() loginDto: LoginDto, @Req() req: Request) {
const ipAddress = req.ip || req.connection.remoteAddress || 'unknown';
const userAgent = req.headers['user-agent'] || 'unknown';
return await this.authService.adminLogin(loginDto, ipAddress, userAgent);
}
@@ -40,13 +42,10 @@ export class AuthController {
@ApiResponse({ status: 200, description: '登录成功' })
@ApiResponse({ status: 401, description: '用户名或密码错误' })
@HttpCode(HttpStatus.OK)
async memberLogin(
@Body() loginDto: LoginDto,
@Req() req: Request
) {
async memberLogin(@Body() loginDto: LoginDto, @Req() req: Request) {
const ipAddress = req.ip || req.connection.remoteAddress || 'unknown';
const userAgent = req.headers['user-agent'] || 'unknown';
return await this.authService.memberLogin(loginDto, ipAddress, userAgent);
}
@@ -112,4 +111,4 @@ export class AuthController {
}
return { message: '登出成功' };
}
}
}

View File

@@ -1,4 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

View File

@@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

View File

@@ -1,5 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsOptional, MinLength, MaxLength } from 'class-validator';
import {
IsString,
IsNumber,
IsOptional,
MinLength,
MaxLength,
} from 'class-validator';
export class LoginDto {
@ApiProperty({ description: '用户名', example: 'admin' })
@@ -21,13 +27,19 @@ export class LoginDto {
}
export class RefreshTokenDto {
@ApiProperty({ description: '刷新Token', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' })
@ApiProperty({
description: '刷新Token',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
})
@IsString()
refreshToken: string;
}
export class LogoutDto {
@ApiProperty({ description: '访问Token', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' })
@ApiProperty({
description: '访问Token',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
})
@IsString()
token: string;
}
}

View File

@@ -1,4 +1,11 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
} from 'typeorm';
@Entity('auth_token')
@Index(['token'], { unique: true })
@@ -22,7 +29,12 @@ export class AuthToken {
@Column({ name: 'expires_at', type: 'datetime' })
expiresAt: Date;
@Column({ name: 'refresh_token', type: 'varchar', length: 500, nullable: true })
@Column({
name: 'refresh_token',
type: 'varchar',
length: 500,
nullable: true,
})
refreshToken?: string;
@Column({ name: 'refresh_expires_at', type: 'datetime', nullable: true })
@@ -43,7 +55,12 @@ export class AuthToken {
@Column({ name: 'revoked_at', type: 'datetime', nullable: true })
revokedAt?: Date;
@Column({ name: 'revoked_reason', type: 'varchar', length: 200, nullable: true })
@Column({
name: 'revoked_reason',
type: 'varchar',
length: 200,
nullable: true,
})
revokedReason?: string;
@CreateDateColumn({ name: 'created_at' })
@@ -56,10 +73,10 @@ export class AuthToken {
getDeviceTypeText(): string {
if (this.deviceType === undefined || this.deviceType === '') return '';
const typeMap: { [key: string]: string } = {
'web': '网页',
'mobile': '手机',
'app': '应用',
'wechat': '微信'
web: '网页',
mobile: '手机',
app: '应用',
wechat: '微信',
};
return typeMap[this.deviceType] || '未知';
}
@@ -80,4 +97,4 @@ export class AuthToken {
isValid(): boolean {
return !this.isRevoked && !this.isExpired();
}
}
}

View File

@@ -1,6 +1,7 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtAuthGuard } from './JwtAuthGuard';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class GlobalAuthGuard implements CanActivate {
@@ -11,7 +12,7 @@ export class GlobalAuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
// 检查是否有 @Public() 装饰器
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
@@ -22,12 +23,12 @@ export class GlobalAuthGuard implements CanActivate {
// 对于需要认证的接口,使用 JWT 认证
const result = this.jwtAuthGuard.canActivate(context);
// 处理 Promise 类型
if (result instanceof Promise) {
return await result;
}
return result as boolean;
}
}
}

View File

@@ -1,4 +1,9 @@
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { AuthService } from '../services/AuthService';
@@ -13,7 +18,7 @@ export class JwtAuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('未提供访问令牌');
}
@@ -21,7 +26,7 @@ export class JwtAuthGuard implements CanActivate {
try {
// 验证Token
const payload = await this.authService.validateToken(token);
if (!payload) {
throw new UnauthorizedException('访问令牌无效或已过期');
}
@@ -38,4 +43,4 @@ export class JwtAuthGuard implements CanActivate {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
}

View File

@@ -1,4 +1,9 @@
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
@@ -29,10 +34,10 @@ export class RolesGuard implements CanActivate {
}
// 检查具体角色权限
if (user.roles && requiredRoles.some(role => user.roles.includes(role))) {
if (user.roles && requiredRoles.some((role) => user.roles.includes(role))) {
return true;
}
throw new ForbiddenException('权限不足');
}
}
}

View File

@@ -7,4 +7,4 @@ export interface User {
export interface RequestWithUser extends Request {
user: User;
}
}

View File

@@ -0,0 +1,21 @@
import { Module, Global } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Global()
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET', 'change_me'),
signOptions: {
expiresIn: configService.get('JWT_EXPIRES_IN', '7d'),
},
}),
inject: [ConfigService],
}),
],
exports: [JwtModule],
})
export class JwtGlobalModule {}

View File

@@ -30,7 +30,7 @@ export class AuthService {
// 调用AdminService验证用户名密码
const adminUser = await this.validateAdminUser(username, password, siteId);
if (!adminUser) {
throw new UnauthorizedException('用户名或密码错误');
}
@@ -53,8 +53,11 @@ export class AuthService {
// 计算过期时间
const expiresIn = this.configService.get('JWT_EXPIRES_IN', '7d');
const refreshExpiresIn = this.configService.get('JWT_REFRESH_EXPIRES_IN', '30d');
const refreshExpiresIn = this.configService.get(
'JWT_REFRESH_EXPIRES_IN',
'30d',
);
const expiresAt = this.calculateExpiryDate(expiresIn);
const refreshExpiresAt = this.calculateExpiryDate(refreshExpiresIn);
@@ -99,8 +102,12 @@ export class AuthService {
const { username, password, siteId = 0 } = loginDto;
// 调用MemberService验证用户名密码
const memberUser = await this.validateMemberUser(username, password, siteId);
const memberUser = await this.validateMemberUser(
username,
password,
siteId,
);
if (!memberUser) {
throw new UnauthorizedException('用户名或密码错误');
}
@@ -123,8 +130,11 @@ export class AuthService {
// 计算过期时间
const expiresIn = this.configService.get('JWT_EXPIRES_IN', '7d');
const refreshExpiresIn = this.configService.get('JWT_REFRESH_EXPIRES_IN', '30d');
const refreshExpiresIn = this.configService.get(
'JWT_REFRESH_EXPIRES_IN',
'30d',
);
const expiresAt = this.calculateExpiryDate(expiresIn);
const refreshExpiresAt = this.calculateExpiryDate(refreshExpiresIn);
@@ -175,7 +185,7 @@ export class AuthService {
try {
// 验证刷新Token
const payload = this.jwtService.verify(refreshToken);
// 检查数据库中的Token记录
const tokenRecord = await this.authTokenRepository.findOne({
where: { refreshToken, isRevoked: 0 },
@@ -199,7 +209,9 @@ export class AuthService {
// 更新数据库中的Token
tokenRecord.token = newAccessToken;
tokenRecord.expiresAt = this.calculateExpiryDate(this.configService.get('JWT_EXPIRES_IN', '7d'));
tokenRecord.expiresAt = this.calculateExpiryDate(
this.configService.get('JWT_EXPIRES_IN', '7d'),
);
await this.authTokenRepository.save(tokenRecord);
return {
@@ -239,7 +251,7 @@ export class AuthService {
try {
// 验证JWT Token
const payload = this.jwtService.verify(token);
// 检查数据库中的Token记录
const tokenRecord = await this.authTokenRepository.findOne({
where: { token, isRevoked: 0 },
@@ -268,7 +280,12 @@ export class AuthService {
/**
* 撤销用户所有Token
*/
async revokeUserTokens(userId: number, userType: string, siteId: number = 0, reason: string = '管理员撤销') {
async revokeUserTokens(
userId: number,
userType: string,
siteId: number = 0,
reason: string = '管理员撤销',
) {
const tokens = await this.authTokenRepository.find({
where: { userId, userType, siteId, isRevoked: 0 },
});
@@ -344,7 +361,11 @@ export class AuthService {
/**
* 验证管理员用户
*/
private async validateAdminUser(username: string, password: string, siteId: number): Promise<any> {
private async validateAdminUser(
username: string,
password: string,
siteId: number,
): Promise<any> {
try {
// 根据用户名查找管理员
const admin = await this.adminService.getAdminByUsername(username);
@@ -353,7 +374,10 @@ export class AuthService {
}
// 验证密码
const isValidPassword = await this.adminService.validatePassword(admin.uid, password);
const isValidPassword = await this.adminService.validatePassword(
admin.uid,
password,
);
if (!isValidPassword) {
return null;
}
@@ -372,11 +396,15 @@ export class AuthService {
/**
* 验证会员用户
*/
private async validateMemberUser(username: string, password: string, siteId: number): Promise<any> {
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);
@@ -405,4 +433,4 @@ export class AuthService {
return null;
}
}
}
}