feat: first commit
This commit is contained in:
45
server/src/app.module.ts
Normal file
45
server/src/app.module.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import configuration from './config/configuration';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { AuthGuard } from './common/guard/auth.guard';
|
||||
import { PermissionGuard } from '@/common/guard/permission.guard';
|
||||
import { SystemModule } from './system/system.module';
|
||||
import { PrismaModule } from '@/prisma/prisma.module';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { redisStore } from 'cache-manager-redis-yet';
|
||||
import { MinioService } from './minio/minio.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [configuration],
|
||||
}),
|
||||
PrismaModule,
|
||||
// 系统模块
|
||||
SystemModule,
|
||||
// https://github.com/dabroek/node-cache-manager-redis-store/issues/40
|
||||
CacheModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
isGlobal: true,
|
||||
useFactory: async (config: ConfigService) => ({
|
||||
store: await redisStore({
|
||||
ttl: config.get('redis.ttl'),
|
||||
socket: {
|
||||
host: config.get('redis.host'),
|
||||
port: config.get('redis.port'),
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
controllers: [],
|
||||
providers: [
|
||||
{ provide: APP_GUARD, useClass: AuthGuard },
|
||||
{ provide: APP_GUARD, useClass: PermissionGuard },
|
||||
MinioService,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
5
server/src/common/decorators/roles.decorator.ts
Normal file
5
server/src/common/decorators/roles.decorator.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { Role } from '../enum';
|
||||
|
||||
export const ROLES_KEY = 'roles';
|
||||
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
|
||||
5
server/src/common/decorators/skip-auth.decorator.ts
Normal file
5
server/src/common/decorators/skip-auth.decorator.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const SKIP_AUTH = 'SKIP_AUTH';
|
||||
|
||||
export const SkipAuth = () => SetMetadata(SKIP_AUTH, true);
|
||||
13
server/src/common/dto/pagination.dto.ts
Normal file
13
server/src/common/dto/pagination.dto.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IsOptional, Min } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class PaginationDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@Min(1)
|
||||
public readonly page?: number = 1;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
public readonly pageSize?: number = 10;
|
||||
}
|
||||
13
server/src/common/enum/index.ts
Normal file
13
server/src/common/enum/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export enum Role {
|
||||
User = 'user',
|
||||
Admin = 'admin',
|
||||
}
|
||||
|
||||
export enum RedisGroup {
|
||||
Config = 'config',
|
||||
JwtJti = 'jwt_jti',
|
||||
}
|
||||
|
||||
export enum PermissionType {
|
||||
Menu,
|
||||
}
|
||||
65
server/src/common/guard/auth.guard.ts
Normal file
65
server/src/common/guard/auth.guard.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Request } from 'express';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { SKIP_AUTH } from '@/common/decorators/skip-auth.decorator';
|
||||
import { RedisGroup } from '@/common/enum';
|
||||
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(
|
||||
private jwtService: JwtService,
|
||||
private readonly configService: ConfigService,
|
||||
private reflector: Reflector,
|
||||
@Inject(CACHE_MANAGER) private cacheService: Cache,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const isSkipAuth = this.reflector.getAllAndOverride<boolean>(SKIP_AUTH, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (isSkipAuth) {
|
||||
request.isSkipAuth = SKIP_AUTH;
|
||||
return true;
|
||||
}
|
||||
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('用户未登录!');
|
||||
}
|
||||
try {
|
||||
const user = await this.jwtService.verifyAsync(token, {
|
||||
secret: this.configService.get('jwt.secret'),
|
||||
});
|
||||
|
||||
const isTokenPresent = await this.cacheService.get(
|
||||
`${RedisGroup.JwtJti}:${user.sub}:${user.jti}`,
|
||||
);
|
||||
|
||||
if (!isTokenPresent) {
|
||||
throw new UnauthorizedException('登录已失效,请重新登录!');
|
||||
}
|
||||
|
||||
request['user'] = user;
|
||||
} catch {
|
||||
throw new UnauthorizedException('登录已失效,请重新登录!');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
}
|
||||
33
server/src/common/guard/permission.guard.ts
Normal file
33
server/src/common/guard/permission.guard.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
|
||||
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { SKIP_AUTH } from '@/common/decorators/skip-auth.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class PermissionGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private reflector: Reflector,
|
||||
@Inject(CACHE_MANAGER) private cacheService: Cache,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
request['isAdmin'] = request?.user?.account === 'admin';
|
||||
return true;
|
||||
// if (request.isSkipAuth === SKIP_AUTH || request.user.account === 'admin') {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// throw new ForbiddenException('无权访问!');
|
||||
}
|
||||
}
|
||||
43
server/src/common/http-exception.filter.ts
Normal file
43
server/src/common/http-exception.filter.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
Logger,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { inspect } from 'util';
|
||||
import { HttpExceptionBody } from '@nestjs/common/interfaces/http/http-exception-body.interface';
|
||||
import maskSensitiveData from '@/common/utils/maskSensitiveData';
|
||||
|
||||
@Catch(HttpException)
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
private logger = new Logger('HttpError');
|
||||
|
||||
catch(exception: HttpException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
|
||||
const response = ctx.getResponse<Response>();
|
||||
const { method, url, body, query, headers, socket } =
|
||||
ctx.getRequest<Request>();
|
||||
const exceptionResponse = exception.getResponse() as HttpExceptionBody;
|
||||
const message = exceptionResponse?.message;
|
||||
|
||||
const status = exception.getStatus();
|
||||
const reason = {
|
||||
method,
|
||||
code: status,
|
||||
url,
|
||||
message,
|
||||
};
|
||||
const log = {
|
||||
...reason,
|
||||
body: maskSensitiveData(body),
|
||||
query,
|
||||
clientIp: headers['x-forwarded-for'] || socket.remoteAddress,
|
||||
};
|
||||
this.logger.error(inspect(log, { breakLength: 9999 }));
|
||||
response.status(status).json(reason);
|
||||
}
|
||||
}
|
||||
44
server/src/common/http.interceptor.ts
Normal file
44
server/src/common/http.interceptor.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { inspect } from 'util';
|
||||
import { Request } from 'express';
|
||||
import maskSensitiveData from '@/common/utils/maskSensitiveData';
|
||||
|
||||
@Injectable()
|
||||
export class HttpInterceptor implements NestInterceptor {
|
||||
private logger = new Logger('HttpSuccess');
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const ctx = context.switchToHttp();
|
||||
const { method, body, query, url, headers, socket } =
|
||||
ctx.getRequest<Request>();
|
||||
const { statusCode } = ctx.getResponse();
|
||||
const clientIp = headers['x-forwarded-for'] || socket.remoteAddress;
|
||||
const log = {
|
||||
method,
|
||||
code: statusCode,
|
||||
url,
|
||||
body: maskSensitiveData(body),
|
||||
query,
|
||||
clientIp,
|
||||
};
|
||||
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
this.logger.log(inspect(log, { breakLength: 9999 }));
|
||||
return {
|
||||
code: statusCode,
|
||||
message: '请求成功!',
|
||||
data: data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
server/src/common/utils/maskSensitiveData.ts
Normal file
43
server/src/common/utils/maskSensitiveData.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { cloneDeep, has } from 'lodash';
|
||||
|
||||
// 需要脱敏的字段
|
||||
const fieldList = [
|
||||
{
|
||||
field: 'account',
|
||||
pattern: /^(.).+(.)$/,
|
||||
replacement: '$1***$2',
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
pattern: /.*/,
|
||||
replacement: '******',
|
||||
},
|
||||
{
|
||||
field: 'newPassword',
|
||||
pattern: /.*/,
|
||||
replacement: '******',
|
||||
},
|
||||
];
|
||||
|
||||
export default function maskSensitiveData(body: any) {
|
||||
const obj = cloneDeep(body);
|
||||
|
||||
if (typeof obj !== 'object' && obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => maskSensitiveData(item));
|
||||
} else {
|
||||
for (const { field, pattern, replacement } of fieldList) {
|
||||
if (obj[field]) {
|
||||
if (typeof obj[field] === 'string') {
|
||||
obj[field] = obj[field].replace(pattern, replacement);
|
||||
} else {
|
||||
obj[field] = maskSensitiveData(obj[field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
36
server/src/common/validator/custom-starts-with.ts
Normal file
36
server/src/common/validator/custom-starts-with.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
ValidationArguments,
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
} from 'class-validator';
|
||||
|
||||
@ValidatorConstraint({ name: 'customerStartsWith', async: false })
|
||||
export class CustomStartsWith implements ValidatorConstraintInterface {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [prefix] = args.constraints;
|
||||
if (typeof value !== 'string') return false;
|
||||
return value.startsWith(prefix);
|
||||
}
|
||||
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
const [prefix] = args.constraints;
|
||||
return `Field $property must start with "${prefix}"`;
|
||||
}
|
||||
}
|
||||
|
||||
export function StartsWithPrefix(
|
||||
prefix: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: { constructor: any }, propertyName: string) {
|
||||
registerDecorator({
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
constraints: [prefix],
|
||||
validator: CustomStartsWith,
|
||||
});
|
||||
};
|
||||
}
|
||||
21
server/src/config/configuration.ts
Normal file
21
server/src/config/configuration.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default () => ({
|
||||
jwt: {
|
||||
secret: 'ultimate',
|
||||
expiresIn: '7d',
|
||||
redisExpiresIn: 7 * 60 * 60 * 24 * 1000,
|
||||
},
|
||||
redis: {
|
||||
host: '81.70.149.52',
|
||||
port: 8002,
|
||||
// 默认过期时间 5s
|
||||
ttl: 5000,
|
||||
},
|
||||
minio: {
|
||||
endPoint: 's3.ulti42.com',
|
||||
port: 443,
|
||||
useSSL: true,
|
||||
accessKey: 'vjr5fW3Rk6W0WydqOpCt',
|
||||
secretKey: '3sNkhS5zVf9tTXb3doSN2iJ3NO6WG8alJPoj1Pzd',
|
||||
bucketName: 'static',
|
||||
},
|
||||
});
|
||||
79
server/src/main.ts
Normal file
79
server/src/main.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma/prisma.service';
|
||||
import { ConfigService } from './system/config/config.service';
|
||||
import { HttpExceptionFilter } from 'src/common/http-exception.filter';
|
||||
import { HttpInterceptor } from 'src/common/http.interceptor';
|
||||
import { WinstonModule } from 'nest-winston';
|
||||
import { transports, format } from 'winston';
|
||||
import 'winston-daily-rotate-file';
|
||||
|
||||
async function bootstrap() {
|
||||
const logFormat = format.combine(
|
||||
format.timestamp(),
|
||||
format.printf((info) => {
|
||||
return `${info.timestamp} ${info.level} ${info.context} ${info.message}`;
|
||||
}),
|
||||
);
|
||||
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: WinstonModule.createLogger({
|
||||
transports: [
|
||||
// 记录错误日志
|
||||
new transports.DailyRotateFile({
|
||||
filename: `logs/%DATE%/error.log`,
|
||||
level: 'error',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
maxFiles: '30d',
|
||||
format: logFormat,
|
||||
}),
|
||||
// logging all level
|
||||
new transports.DailyRotateFile({
|
||||
filename: `logs/%DATE%/combined.log`,
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
maxFiles: '30d',
|
||||
format: logFormat,
|
||||
}),
|
||||
// 控制台输出
|
||||
new transports.Console({
|
||||
format: format.combine(
|
||||
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
format.colorize({ all: true }),
|
||||
logFormat,
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
}),
|
||||
);
|
||||
// 全局异常过滤器
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
app.useGlobalInterceptors(new HttpInterceptor());
|
||||
|
||||
// prisma
|
||||
const prismaService = app.get(PrismaService);
|
||||
await prismaService.enableShutdownHooks(app);
|
||||
|
||||
// 同步配置到redis
|
||||
const configService = app.get(ConfigService);
|
||||
await configService.syncConfig();
|
||||
|
||||
// 跨域
|
||||
app.enableCors();
|
||||
// 设置全局前缀
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
const logger = new Logger('Server Running');
|
||||
await app.listen(3000, '0.0.0.0', async () => {
|
||||
logger.log(`Application is running on: ${await app.getUrl()}`);
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
41
server/src/minio/minio.service.ts
Normal file
41
server/src/minio/minio.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as Minio from 'minio';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class MinioService {
|
||||
private readonly minioClient: Minio.Client;
|
||||
private readonly minioConfig: string;
|
||||
private readonly bucketName: string;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
const { bucketName, ...rest } = this.configService.get('minio');
|
||||
this.minioConfig = rest;
|
||||
this.bucketName = bucketName;
|
||||
this.minioClient = new Minio.Client({
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
|
||||
async createBucketIfNotExists() {
|
||||
const bucketExists = await this.minioClient.bucketExists(this.bucketName);
|
||||
if (!bucketExists) {
|
||||
await this.minioClient.makeBucket(this.bucketName, 'cn-north-1');
|
||||
}
|
||||
}
|
||||
|
||||
async uploadFile(file: Express.Multer.File, fileName: string) {
|
||||
await this.minioClient.putObject(
|
||||
this.bucketName,
|
||||
fileName,
|
||||
file.buffer,
|
||||
file.size,
|
||||
{ 'Content-Type': file.mimetype },
|
||||
);
|
||||
return `https://${this.minioConfig['endPoint']}/${this.bucketName}/${fileName}`;
|
||||
}
|
||||
|
||||
async deleteFile(fileName: string) {
|
||||
await this.minioClient.removeObject(this.bucketName, fileName);
|
||||
}
|
||||
}
|
||||
9
server/src/prisma/prisma.module.ts
Normal file
9
server/src/prisma/prisma.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module, Global } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
25
server/src/prisma/prisma.service.ts
Normal file
25
server/src/prisma/prisma.service.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import process from 'process';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
constructor(private reflector: Reflector) {
|
||||
// super({
|
||||
// log: ['query', 'info', 'warn', 'error'],
|
||||
// errorFormat: 'colorless',
|
||||
// });
|
||||
super();
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
process.on('beforeExit', async () => {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
30
server/src/system/auth/auth.controller.ts
Normal file
30
server/src/system/auth/auth.controller.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Controller, Post, Body, Param } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthDto } from './dto/auth.dto';
|
||||
import { SkipAuth } from '@/common/decorators/skip-auth.decorator';
|
||||
import { UserRoleDto } from './dto/user-role.dto';
|
||||
import { RolePermissionDto } from './dto/role-permission.dto';
|
||||
|
||||
@Controller()
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
// 用户登录
|
||||
@Post('login')
|
||||
@SkipAuth()
|
||||
signIn(@Body() authDto: AuthDto) {
|
||||
return this.authService.signIn(authDto);
|
||||
}
|
||||
|
||||
// 授权用户角色
|
||||
@Post('userRole/:id')
|
||||
grantUserRole(@Param('id') id: string, @Body() dto: UserRoleDto) {
|
||||
return this.authService.grantUserRole(+id, dto);
|
||||
}
|
||||
|
||||
// 授权角色权限
|
||||
@Post('rolePermission/:id')
|
||||
grantRolePermission(@Param('id') id: string, @Body() dto: RolePermissionDto) {
|
||||
return this.authService.grantRolePermission(+id, dto);
|
||||
}
|
||||
}
|
||||
26
server/src/system/auth/auth.module.ts
Normal file
26
server/src/system/auth/auth.module.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { UserModule } from '../user/user.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UserModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
global: true,
|
||||
useFactory: async (config: ConfigService) => {
|
||||
return {
|
||||
secret: config.get('jwt.secret'),
|
||||
signOptions: { expiresIn: config.get('jwt.expiresIn') },
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
136
server/src/system/auth/auth.service.ts
Normal file
136
server/src/system/auth/auth.service.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
ForbiddenException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { AuthDto } from './dto/auth.dto';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { PrismaService } from '@/prisma/prisma.service';
|
||||
import { UserRoleDto } from './dto/user-role.dto';
|
||||
import { RolePermissionDto } from './dto/role-permission.dto';
|
||||
import { RedisGroup } from '@/common/enum';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly configService: ConfigService,
|
||||
@Inject(CACHE_MANAGER) private cacheService: Cache,
|
||||
) {}
|
||||
|
||||
// 登录
|
||||
async signIn({ account, password }: AuthDto) {
|
||||
const user = await this.prisma.sysUser.findUnique({
|
||||
where: {
|
||||
account,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('账号或密码错误!');
|
||||
}
|
||||
|
||||
const pass = await bcrypt.hash(password, user.passwordSalt);
|
||||
if (user?.password !== pass) {
|
||||
throw new UnauthorizedException('账号或密码错误!');
|
||||
}
|
||||
|
||||
if (user.status === 0) {
|
||||
throw new ForbiddenException('此账户已被禁用!');
|
||||
}
|
||||
|
||||
const jti = nanoid();
|
||||
|
||||
const payload = { sub: user.id, account: user.account, jti };
|
||||
|
||||
const accessToken = await this.jwtService.signAsync(payload);
|
||||
|
||||
await this.cacheService.set(
|
||||
`${RedisGroup.JwtJti}:${user.id}:${jti}`,
|
||||
user.id,
|
||||
this.configService.get('jwt.redisExpiresIn'),
|
||||
);
|
||||
|
||||
return { accessToken };
|
||||
}
|
||||
|
||||
async grantUserRole(id: number, { roleIds }: UserRoleDto) {
|
||||
const user = await this.prisma.sysUser.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (!user) throw new HttpException('用户不存在!', HttpStatus.NOT_FOUND);
|
||||
|
||||
// 查询所有角色id
|
||||
const allRoles = await this.prisma.sysRole.findMany({
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
const allRoleIds = allRoles.map((item) => item.id);
|
||||
|
||||
const isContain = roleIds.every((item) => allRoleIds.includes(item));
|
||||
|
||||
if (!isContain)
|
||||
throw new HttpException(
|
||||
'出现不存在的角色id,请检查传入的roleIds!',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
|
||||
const data = roleIds.map((item) => ({
|
||||
userId: id,
|
||||
roleId: item,
|
||||
}));
|
||||
|
||||
await this.prisma.$transaction(async (prisma) => {
|
||||
await prisma.sysUserRole.deleteMany({
|
||||
where: { userId: id },
|
||||
});
|
||||
await prisma.sysUserRole.createMany({ data });
|
||||
});
|
||||
}
|
||||
|
||||
async grantRolePermission(id: number, { permissionIds }: RolePermissionDto) {
|
||||
const role = await this.prisma.sysRole.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (!role)
|
||||
throw new HttpException('角色信息不存在!', HttpStatus.NOT_FOUND);
|
||||
|
||||
// 查询所有权限
|
||||
const allPermission = await this.prisma.sysPermission.findMany({
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
const allPermissionIds = allPermission.map((item) => item.id);
|
||||
|
||||
// 判断传入的id是否都存在于数据库中
|
||||
const isContain = permissionIds.every((item) =>
|
||||
allPermissionIds.includes(item),
|
||||
);
|
||||
|
||||
if (!isContain)
|
||||
throw new HttpException(
|
||||
'出现不存在的权限id,请检查传入的permissionIds!',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
|
||||
const data = permissionIds.map((permissionId) => ({
|
||||
roleId: id,
|
||||
permissionId,
|
||||
}));
|
||||
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.sysRolePermission.deleteMany({
|
||||
where: { permissionId: { in: permissionIds } },
|
||||
}),
|
||||
this.prisma.sysRolePermission.createMany({ data }),
|
||||
]);
|
||||
}
|
||||
}
|
||||
7
server/src/system/auth/dto/auth.dto.ts
Normal file
7
server/src/system/auth/dto/auth.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { CreateUserDto } from 'src/system/user/dto/create-user.dto';
|
||||
import { PickType } from '@nestjs/mapped-types';
|
||||
|
||||
export class AuthDto extends PickType(CreateUserDto, [
|
||||
'account',
|
||||
'password',
|
||||
] as const) {}
|
||||
8
server/src/system/auth/dto/role-permission.dto.ts
Normal file
8
server/src/system/auth/dto/role-permission.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ArrayUnique, IsInt, IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class RolePermissionDto {
|
||||
@IsNotEmpty()
|
||||
@IsInt({ each: true })
|
||||
@ArrayUnique()
|
||||
permissionIds: number[];
|
||||
}
|
||||
8
server/src/system/auth/dto/user-role.dto.ts
Normal file
8
server/src/system/auth/dto/user-role.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ArrayUnique, IsInt, IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class UserRoleDto {
|
||||
@IsNotEmpty()
|
||||
@IsInt({ each: true })
|
||||
@ArrayUnique()
|
||||
roleIds: number[];
|
||||
}
|
||||
22
server/src/system/auth/roles.guard.ts
Normal file
22
server/src/system/auth/roles.guard.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Role } from '@/common/enum';
|
||||
import { ROLES_KEY } from '@/common/decorators/roles.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (!requiredRoles) {
|
||||
return true;
|
||||
}
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
return requiredRoles.some((role) => user.roles?.includes(role));
|
||||
}
|
||||
}
|
||||
49
server/src/system/config/config.controller.ts
Normal file
49
server/src/system/config/config.controller.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from './config.service';
|
||||
import { CreateConfigDto } from './dto/create-config.dto';
|
||||
import { UpdateConfigDto } from './dto/update-config.dto';
|
||||
import { QueryListDto } from './dto/query-list.dto';
|
||||
|
||||
@Controller()
|
||||
export class ConfigController {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateConfigDto) {
|
||||
return this.configService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Query() dto: QueryListDto) {
|
||||
return this.configService.findAll(dto);
|
||||
}
|
||||
|
||||
@Get(':key')
|
||||
findOne(@Param('key') key: string) {
|
||||
return this.configService.findOne(key);
|
||||
}
|
||||
|
||||
@Patch(':key')
|
||||
update(@Param('key') key: string, @Body() updateConfigDto: UpdateConfigDto) {
|
||||
return this.configService.update(key, updateConfigDto);
|
||||
}
|
||||
|
||||
@Delete(':key')
|
||||
remove(@Param('key') key: string) {
|
||||
return this.configService.remove(key);
|
||||
}
|
||||
|
||||
@Post('sync')
|
||||
syncConfig() {
|
||||
return this.configService.syncConfig();
|
||||
}
|
||||
}
|
||||
9
server/src/system/config/config.module.ts
Normal file
9
server/src/system/config/config.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigService } from './config.service';
|
||||
import { ConfigController } from './config.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ConfigController],
|
||||
providers: [ConfigService],
|
||||
})
|
||||
export class ConfigModule {}
|
||||
99
server/src/system/config/config.service.ts
Normal file
99
server/src/system/config/config.service.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Injectable, HttpException, HttpStatus, Inject } from '@nestjs/common';
|
||||
import { CreateConfigDto } from './dto/create-config.dto';
|
||||
import { UpdateConfigDto } from './dto/update-config.dto';
|
||||
import { PrismaService } from '@/prisma/prisma.service';
|
||||
import { QueryListDto } from './dto/query-list.dto';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { RedisGroup } from '@/common/enum';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(CACHE_MANAGER) private cacheService: Cache,
|
||||
) {}
|
||||
|
||||
findOneByUnique(key: string) {
|
||||
return this.prisma.sysConfig.findUnique({
|
||||
where: {
|
||||
key,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: CreateConfigDto) {
|
||||
const doesExists = await this.findOneByUnique(data.key);
|
||||
|
||||
if (doesExists) {
|
||||
throw new HttpException('key值不可重复!', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
await this.prisma.sysConfig.create({ data });
|
||||
}
|
||||
|
||||
async findAll({ page, pageSize }: QueryListDto): Promise<QueryListResult> {
|
||||
const query: Prisma.SysConfigFindManyArgs = {
|
||||
select: {
|
||||
key: true,
|
||||
value: true,
|
||||
remark: true,
|
||||
},
|
||||
skip: pageSize * (page - 1),
|
||||
take: pageSize,
|
||||
};
|
||||
|
||||
const [list, count] = await this.prisma.$transaction([
|
||||
this.prisma.sysConfig.findMany(query),
|
||||
this.prisma.sysConfig.count({ where: query.where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
count,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
findOne(key: string) {
|
||||
return this.findOneByUnique(key);
|
||||
}
|
||||
|
||||
async update(key: string, updateConfigDto: UpdateConfigDto) {
|
||||
const doesExists = await this.findOneByUnique(key);
|
||||
|
||||
if (!doesExists) {
|
||||
throw new HttpException('修改失败!key值不存在!', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.prisma.sysConfig.update({
|
||||
data: updateConfigDto,
|
||||
where: { key },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(key: string) {
|
||||
const doesExists = await this.findOneByUnique(key);
|
||||
|
||||
if (!doesExists) {
|
||||
throw new HttpException('删除失败!key值不存在!', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.prisma.sysConfig.delete({ where: { key } });
|
||||
}
|
||||
|
||||
async syncConfig() {
|
||||
const allConfigs = await this.prisma.sysConfig.findMany();
|
||||
|
||||
const keys = await this.cacheService.store.keys(`${RedisGroup.Config}:*`);
|
||||
if (keys.length) await this.cacheService.store.mdel(...keys);
|
||||
|
||||
const config: [string, unknown][] = allConfigs.map((item) => [
|
||||
item.key,
|
||||
item.value,
|
||||
]);
|
||||
|
||||
if (config.length) await this.cacheService.store.mset(config, 0);
|
||||
}
|
||||
}
|
||||
14
server/src/system/config/dto/create-config.dto.ts
Normal file
14
server/src/system/config/dto/create-config.dto.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateConfigDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
key: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
value: string;
|
||||
|
||||
@IsOptional()
|
||||
remark: string;
|
||||
}
|
||||
3
server/src/system/config/dto/query-list.dto.ts
Normal file
3
server/src/system/config/dto/query-list.dto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { PaginationDto } from 'src/common/dto/pagination.dto';
|
||||
|
||||
export class QueryListDto extends PaginationDto {}
|
||||
6
server/src/system/config/dto/update-config.dto.ts
Normal file
6
server/src/system/config/dto/update-config.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { OmitType, PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateConfigDto } from './create-config.dto';
|
||||
|
||||
export class UpdateConfigDto extends PartialType(
|
||||
OmitType(CreateConfigDto, ['key'] as const),
|
||||
) {}
|
||||
23
server/src/system/files/files.controller.ts
Normal file
23
server/src/system/files/files.controller.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
ParseFilePipe,
|
||||
} from '@nestjs/common';
|
||||
import { FilesService } from './files.service';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
|
||||
@Controller()
|
||||
export class FilesController {
|
||||
constructor(private readonly filesService: FilesService) {}
|
||||
|
||||
@Post()
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
upload(
|
||||
@UploadedFile(new ParseFilePipe({ validators: [] }))
|
||||
file: Express.Multer.File,
|
||||
) {
|
||||
return this.filesService.upload(file);
|
||||
}
|
||||
}
|
||||
10
server/src/system/files/files.module.ts
Normal file
10
server/src/system/files/files.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FilesService } from './files.service';
|
||||
import { FilesController } from './files.controller';
|
||||
import { MinioService } from '@/minio/minio.service';
|
||||
|
||||
@Module({
|
||||
controllers: [FilesController],
|
||||
providers: [FilesService, MinioService],
|
||||
})
|
||||
export class FilesModule {}
|
||||
31
server/src/system/files/files.service.ts
Normal file
31
server/src/system/files/files.service.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '@/prisma/prisma.service';
|
||||
|
||||
import path from 'path';
|
||||
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { MinioService } from '@/minio/minio.service';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
@Injectable()
|
||||
export class FilesService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(CACHE_MANAGER) private cacheService: Cache,
|
||||
private readonly minioService: MinioService,
|
||||
) {}
|
||||
|
||||
async upload(file: Express.Multer.File) {
|
||||
const fileName = `${nanoid()}${path.extname(file.originalname)}`;
|
||||
|
||||
const filePath = await this.minioService.uploadFile(file, fileName);
|
||||
|
||||
const res = await this.prisma.files.create({
|
||||
data: { name: fileName, path: filePath },
|
||||
});
|
||||
|
||||
return {
|
||||
id: res.id,
|
||||
path: res.path,
|
||||
};
|
||||
}
|
||||
}
|
||||
15
server/src/system/menu/dto/create-menu.dto.ts
Normal file
15
server/src/system/menu/dto/create-menu.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateMenuDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
url: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
parentId?: number;
|
||||
}
|
||||
4
server/src/system/menu/dto/update-menu.dto.ts
Normal file
4
server/src/system/menu/dto/update-menu.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateMenuDto } from './create-menu.dto';
|
||||
|
||||
export class UpdateMenuDto extends PartialType(CreateMenuDto) {}
|
||||
42
server/src/system/menu/menu.controller.ts
Normal file
42
server/src/system/menu/menu.controller.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { MenuService } from './menu.service';
|
||||
import { CreateMenuDto } from './dto/create-menu.dto';
|
||||
import { UpdateMenuDto } from './dto/update-menu.dto';
|
||||
|
||||
@Controller()
|
||||
export class MenuController {
|
||||
constructor(private readonly menuService: MenuService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateMenuDto) {
|
||||
return this.menuService.create(dto);
|
||||
}
|
||||
|
||||
@Get('tree')
|
||||
getTreeMenu() {
|
||||
return this.menuService.getTreeMenu();
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.menuService.findAll();
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateMenuDto: UpdateMenuDto) {
|
||||
return this.menuService.update(+id, updateMenuDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: number) {
|
||||
return this.menuService.remove(id);
|
||||
}
|
||||
}
|
||||
9
server/src/system/menu/menu.module.ts
Normal file
9
server/src/system/menu/menu.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MenuService } from './menu.service';
|
||||
import { MenuController } from './menu.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [MenuController],
|
||||
providers: [MenuService],
|
||||
})
|
||||
export class MenuModule {}
|
||||
126
server/src/system/menu/menu.service.ts
Normal file
126
server/src/system/menu/menu.service.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { CreateMenuDto } from './dto/create-menu.dto';
|
||||
import { UpdateMenuDto } from './dto/update-menu.dto';
|
||||
import { PrismaService } from '@/prisma/prisma.service';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PermissionType } from '@/common/enum';
|
||||
|
||||
@Injectable()
|
||||
export class MenuService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
findOne(params: { id?: number; excludeId?: number }) {
|
||||
const { id, excludeId } = params;
|
||||
return this.prisma.sysMenu.findFirst({
|
||||
where: {
|
||||
...(id ? { id } : {}),
|
||||
...(excludeId ? { NOT: { id: excludeId } } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: number) {
|
||||
return this.prisma.sysMenu.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
async create(data: CreateMenuDto) {
|
||||
await this.prisma.$transaction(async (prisma) => {
|
||||
const { id: menuId } = await prisma.sysMenu.create({ data });
|
||||
// 创建菜单权限
|
||||
const { id: permissionId } = await prisma.sysPermission.create({
|
||||
data: { type: PermissionType.Menu },
|
||||
});
|
||||
// 关联权限和菜单
|
||||
await prisma.sysMenuPermission.create({ data: { menuId, permissionId } });
|
||||
});
|
||||
}
|
||||
|
||||
generatorMenu(data, parentId = 0) {
|
||||
const menu = [];
|
||||
for (const item of data) {
|
||||
if (item.parentId === parentId) {
|
||||
const children = this.generatorMenu(data, item.id);
|
||||
if (children.length) {
|
||||
item.children = children;
|
||||
}
|
||||
menu.push(item);
|
||||
}
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
async getTreeMenu() {
|
||||
const query: Prisma.SysMenuFindManyArgs = {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
parentId: true,
|
||||
},
|
||||
};
|
||||
const list = await this.prisma.sysMenu.findMany(query);
|
||||
return this.generatorMenu(list);
|
||||
}
|
||||
|
||||
findAll() {
|
||||
const query: Prisma.SysMenuFindManyArgs = {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
parentId: true,
|
||||
},
|
||||
};
|
||||
return this.prisma.sysMenu.findMany(query);
|
||||
}
|
||||
|
||||
async update(id: number, data: UpdateMenuDto) {
|
||||
const doesExists = await this.findOne({ id });
|
||||
if (!doesExists) {
|
||||
throw new HttpException('菜单不存在!', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
if (data.parentId === id) {
|
||||
throw new HttpException(
|
||||
'父级id不可与自身id相同!',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
await this.prisma.sysMenu.update({ data, where: { id } });
|
||||
}
|
||||
|
||||
async remove(menuId: number) {
|
||||
const doesExists = await this.findOneById(menuId);
|
||||
if (!doesExists) {
|
||||
throw new HttpException('菜单不存在!', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// 判断是否存在子菜单
|
||||
const doesExistsChildren = await this.prisma.sysMenu.findFirst({
|
||||
where: { parentId: menuId },
|
||||
});
|
||||
|
||||
if (doesExistsChildren) {
|
||||
throw new HttpException(
|
||||
'删除失败:请确保当前菜单下不存在子菜单!',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
|
||||
await this.prisma.$transaction(async (prisma) => {
|
||||
// step1 通过关联表删除权限表数据 这一步必须在前
|
||||
// 因为schema文件中设置了onDelete: Cascade 删除菜单会删除关联表数据 则无法找出对应权限
|
||||
await prisma.sysPermission.deleteMany({
|
||||
where: {
|
||||
sysMenuPermission: {
|
||||
some: { menuId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//step2 删除菜单数据
|
||||
await prisma.sysMenu.delete({ where: { id: menuId } });
|
||||
});
|
||||
}
|
||||
}
|
||||
6
server/src/system/permission/dto/grant-menu.dto.ts
Normal file
6
server/src/system/permission/dto/grant-menu.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsNumber } from 'class-validator';
|
||||
|
||||
export class GrantMenuDto {
|
||||
@IsNumber({}, { each: true })
|
||||
menuIds: number[];
|
||||
}
|
||||
19
server/src/system/permission/permission.controller.ts
Normal file
19
server/src/system/permission/permission.controller.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Controller, Body, Patch, Param, Get } from '@nestjs/common';
|
||||
import { PermissionService } from './permission.service';
|
||||
|
||||
import { GrantMenuDto } from '@/system/permission/dto/grant-menu.dto';
|
||||
|
||||
@Controller()
|
||||
export class PermissionController {
|
||||
constructor(private readonly permissionService: PermissionService) {}
|
||||
|
||||
@Get(':id/menu')
|
||||
getMenu(@Param('id') id: number) {
|
||||
return this.permissionService.getMenu(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
grantMenu(@Param('id') id: number, @Body() dto: GrantMenuDto) {
|
||||
return this.permissionService.grantMenu(id, dto);
|
||||
}
|
||||
}
|
||||
9
server/src/system/permission/permission.module.ts
Normal file
9
server/src/system/permission/permission.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PermissionService } from './permission.service';
|
||||
import { PermissionController } from './permission.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [PermissionController],
|
||||
providers: [PermissionService],
|
||||
})
|
||||
export class PermissionModule {}
|
||||
45
server/src/system/permission/permission.service.ts
Normal file
45
server/src/system/permission/permission.service.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GrantMenuDto } from '@/system/permission/dto/grant-menu.dto';
|
||||
import { PrismaService } from '@/prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class PermissionService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async getMenu(id: number) {
|
||||
const menuIds = await this.prisma.sysMenuPermission.findMany({
|
||||
select: {
|
||||
menuId: true,
|
||||
},
|
||||
where: {
|
||||
permission: {
|
||||
sysRolePermission: {
|
||||
some: { roleId: id },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return menuIds.map((item) => item.menuId);
|
||||
}
|
||||
|
||||
async grantMenu(id: number, { menuIds }: GrantMenuDto) {
|
||||
await this.prisma.$transaction(async (prisma) => {
|
||||
// 删除角色对应的权限
|
||||
await prisma.sysRolePermission.deleteMany({
|
||||
where: { roleId: id },
|
||||
});
|
||||
// 通过菜单id 找出所有权限id
|
||||
const rolePermIds = await prisma.sysMenuPermission.findMany({
|
||||
select: { permissionId: true },
|
||||
where: { menuId: { in: menuIds } },
|
||||
});
|
||||
// 根据权限id 关联菜单和权限
|
||||
if (rolePermIds.length > 0) {
|
||||
await prisma.sysRolePermission.createMany({
|
||||
data: rolePermIds.map((item) => ({ ...item, roleId: id })),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
12
server/src/system/role/dto/create-role.dto.ts
Normal file
12
server/src/system/role/dto/create-role.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator';
|
||||
|
||||
export class CreateRoleDto {
|
||||
@IsOptional()
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
code: string;
|
||||
|
||||
@IsNumber()
|
||||
status: number;
|
||||
}
|
||||
3
server/src/system/role/dto/query-list.dto.ts
Normal file
3
server/src/system/role/dto/query-list.dto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { PaginationDto } from 'src/common/dto/pagination.dto';
|
||||
|
||||
export class QueryListDto extends PaginationDto {}
|
||||
6
server/src/system/role/dto/update-role.dto.ts
Normal file
6
server/src/system/role/dto/update-role.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { OmitType, PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateRoleDto } from './create-role.dto';
|
||||
|
||||
export class UpdateRoleDto extends PartialType(
|
||||
OmitType(CreateRoleDto, ['code']),
|
||||
) {}
|
||||
44
server/src/system/role/role.controller.ts
Normal file
44
server/src/system/role/role.controller.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { RoleService } from './role.service';
|
||||
import { CreateRoleDto } from './dto/create-role.dto';
|
||||
import { UpdateRoleDto } from './dto/update-role.dto';
|
||||
import { QueryListDto } from './dto/query-list.dto';
|
||||
|
||||
@Controller()
|
||||
export class RoleController {
|
||||
constructor(private readonly roleService: RoleService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateRoleDto) {
|
||||
return this.roleService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findPage(@Query() dto: QueryListDto): Promise<QueryListResult> {
|
||||
return this.roleService.findPage(dto);
|
||||
}
|
||||
|
||||
@Get('/all')
|
||||
findAll() {
|
||||
return this.roleService.findAll();
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateRoleDto) {
|
||||
return this.roleService.update(+id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.roleService.remove(+id);
|
||||
}
|
||||
}
|
||||
9
server/src/system/role/role.module.ts
Normal file
9
server/src/system/role/role.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RoleService } from './role.service';
|
||||
import { RoleController } from './role.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [RoleController],
|
||||
providers: [RoleService],
|
||||
})
|
||||
export class RoleModule {}
|
||||
81
server/src/system/role/role.service.ts
Normal file
81
server/src/system/role/role.service.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { CreateRoleDto } from './dto/create-role.dto';
|
||||
import { UpdateRoleDto } from './dto/update-role.dto';
|
||||
import { PrismaService } from '@/prisma/prisma.service';
|
||||
import { Prisma, SysRole } from '@prisma/client';
|
||||
import { QueryListDto } from './dto/query-list.dto';
|
||||
|
||||
@Injectable()
|
||||
export class RoleService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findOne(
|
||||
params: Prisma.SysRoleWhereUniqueInput,
|
||||
): Promise<SysRole | null> {
|
||||
return this.prisma.sysRole.findUnique({
|
||||
where: params,
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: CreateRoleDto) {
|
||||
const doesExists = await this.findOne({ code: data.code });
|
||||
if (doesExists)
|
||||
throw new HttpException('角色编码不可重复!', HttpStatus.NOT_FOUND);
|
||||
|
||||
await this.prisma.sysRole.create({ data });
|
||||
}
|
||||
|
||||
async findPage({ page, pageSize }: QueryListDto): Promise<QueryListResult> {
|
||||
const query: Prisma.SysRoleFindManyArgs = {
|
||||
skip: pageSize * (page - 1),
|
||||
take: pageSize,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
code: true,
|
||||
status: true,
|
||||
},
|
||||
};
|
||||
const [list, count] = await this.prisma.$transaction([
|
||||
this.prisma.sysRole.findMany(query),
|
||||
this.prisma.sysRole.count({ where: query.where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
count,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.prisma.sysRole.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
code: true,
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: number, data: UpdateRoleDto) {
|
||||
const role = await this.findOne({ id });
|
||||
if (!role)
|
||||
throw new HttpException('角色信息不存在!', HttpStatus.NOT_FOUND);
|
||||
|
||||
await this.prisma.sysRole.update({
|
||||
data,
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const role = await this.findOne({ id });
|
||||
if (!role)
|
||||
throw new HttpException('角色信息不存在!', HttpStatus.NOT_FOUND);
|
||||
|
||||
await this.prisma.sysRole.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
58
server/src/system/system.module.ts
Normal file
58
server/src/system/system.module.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RouterModule } from '@nestjs/core';
|
||||
import { UserModule } from './user/user.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { RoleModule } from './role/role.module';
|
||||
import { FilesModule } from './files/files.module';
|
||||
import { MenuModule } from './menu/menu.module';
|
||||
import { ConfigModule } from './config/config.module';
|
||||
import { PermissionModule } from './permission/permission.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
RouterModule.register([
|
||||
{
|
||||
path: 'admin',
|
||||
module: SystemModule,
|
||||
children: [
|
||||
{
|
||||
path: 'user',
|
||||
module: UserModule,
|
||||
},
|
||||
{
|
||||
path: 'auth',
|
||||
module: AuthModule,
|
||||
},
|
||||
{
|
||||
path: 'role',
|
||||
module: RoleModule,
|
||||
},
|
||||
{
|
||||
path: 'files',
|
||||
module: FilesModule,
|
||||
},
|
||||
{
|
||||
path: 'menu',
|
||||
module: MenuModule,
|
||||
},
|
||||
{
|
||||
path: 'config',
|
||||
module: ConfigModule,
|
||||
},
|
||||
{
|
||||
path: 'permission',
|
||||
module: PermissionModule,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
UserModule,
|
||||
AuthModule,
|
||||
RoleModule,
|
||||
FilesModule,
|
||||
MenuModule,
|
||||
ConfigModule,
|
||||
PermissionModule,
|
||||
],
|
||||
})
|
||||
export class SystemModule {}
|
||||
35
server/src/system/user/dto/create-user.dto.ts
Normal file
35
server/src/system/user/dto/create-user.dto.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsOptional()
|
||||
@MaxLength(20, { message: '用户名不要超过20个字符' })
|
||||
name?: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@MinLength(5, { message: '账户名不要低于6位字符' })
|
||||
@MaxLength(50, { message: '账户名不要超过50个字符' })
|
||||
account: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
avatarId: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
gender: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@MinLength(6, { message: '密码不要低于6位字符' })
|
||||
@MaxLength(20, { message: '密码不要超过50个字符' })
|
||||
password: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
status?: number;
|
||||
}
|
||||
3
server/src/system/user/dto/query-list.dto.ts
Normal file
3
server/src/system/user/dto/query-list.dto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { PaginationDto } from 'src/common/dto/pagination.dto';
|
||||
|
||||
export class QueryListDto extends PaginationDto {}
|
||||
10
server/src/system/user/dto/update-password.dto.ts
Normal file
10
server/src/system/user/dto/update-password.dto.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { IsOptional, MaxLength, MinLength } from 'class-validator';
|
||||
import { PickType } from '@nestjs/mapped-types';
|
||||
import { CreateUserDto } from './create-user.dto';
|
||||
|
||||
export class UpdatePasswordDto extends PickType(CreateUserDto, ['password']) {
|
||||
@IsOptional()
|
||||
@MinLength(5)
|
||||
@MaxLength(50)
|
||||
newPassword?: string;
|
||||
}
|
||||
6
server/src/system/user/dto/update-user.dto.ts
Normal file
6
server/src/system/user/dto/update-user.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { OmitType, PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateUserDto } from './create-user.dto';
|
||||
|
||||
export class UpdateUserDto extends PartialType(
|
||||
OmitType(CreateUserDto, ['account', 'password'] as const),
|
||||
) {}
|
||||
58
server/src/system/user/user.controller.ts
Normal file
58
server/src/system/user/user.controller.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { QueryListDto } from './dto/query-list.dto';
|
||||
import { UpdatePasswordDto } from './dto/update-password.dto';
|
||||
|
||||
@Controller()
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() data: CreateUserDto) {
|
||||
return this.userService.create(data);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Query() dto: QueryListDto): Promise<QueryListResult> {
|
||||
return this.userService.findAll(dto);
|
||||
}
|
||||
|
||||
@Get('/info')
|
||||
getUserInfo(@Request() req) {
|
||||
const { sub } = req['user'];
|
||||
const { isAdmin } = req;
|
||||
return this.userService.getUserInfo(sub, isAdmin);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOneById(@Param('id') id: number) {
|
||||
return this.userService.findOneById(id);
|
||||
}
|
||||
|
||||
@Patch(':id/password')
|
||||
updatePassword(@Param('id') id: string, @Body() dto: UpdatePasswordDto) {
|
||||
return this.userService.updatePassword(+id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
|
||||
return this.userService.update(+id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.userService.remove(+id);
|
||||
}
|
||||
}
|
||||
9
server/src/system/user/user.module.ts
Normal file
9
server/src/system/user/user.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { UserController } from './user.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [UserController],
|
||||
providers: [UserService],
|
||||
})
|
||||
export class UserModule {}
|
||||
208
server/src/system/user/user.service.ts
Normal file
208
server/src/system/user/user.service.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import {
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { Prisma, SysUser } from '@prisma/client';
|
||||
import { PrismaService } from '@/prisma/prisma.service';
|
||||
import { QueryListDto } from './dto/query-list.dto';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { UpdatePasswordDto } from './dto/update-password.dto';
|
||||
import { SkipAuth } from '@/common/decorators/skip-auth.decorator';
|
||||
import { RedisGroup } from '@/common/enum';
|
||||
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
|
||||
@Injectable()
|
||||
@SkipAuth()
|
||||
export class UserService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||
) {}
|
||||
|
||||
async findOne(
|
||||
params: Prisma.SysUserWhereUniqueInput,
|
||||
): Promise<SysUser | null> {
|
||||
return this.prisma.sysUser.findUnique({
|
||||
where: params,
|
||||
});
|
||||
}
|
||||
|
||||
async create(dto: CreateUserDto) {
|
||||
const userExists = await this.findOne({ account: dto.account });
|
||||
if (userExists)
|
||||
throw new HttpException('账号已存在!', HttpStatus.UNPROCESSABLE_ENTITY);
|
||||
|
||||
const passwordSalt = await bcrypt.genSalt();
|
||||
const password = await bcrypt.hash(dto.password, passwordSalt);
|
||||
const data = {
|
||||
...dto,
|
||||
passwordSalt,
|
||||
password,
|
||||
};
|
||||
|
||||
await this.prisma.sysUser.create({ data });
|
||||
}
|
||||
|
||||
async findAll({ page, pageSize }: QueryListDto): Promise<QueryListResult> {
|
||||
const query: Prisma.SysUserFindManyArgs = {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
account: true,
|
||||
status: true,
|
||||
gender: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
avatar: {
|
||||
select: {
|
||||
id: true,
|
||||
path: true,
|
||||
},
|
||||
},
|
||||
userRole: {
|
||||
select: { roleId: true },
|
||||
},
|
||||
},
|
||||
skip: pageSize * (page - 1),
|
||||
take: pageSize,
|
||||
};
|
||||
|
||||
const [list, count] = await this.prisma.$transaction([
|
||||
this.prisma.sysUser.findMany(query),
|
||||
this.prisma.sysUser.count({ where: query.where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
count,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
async findOneById(id: number) {
|
||||
return this.prisma.sysUser.findUnique({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
account: true,
|
||||
status: true,
|
||||
gender: true,
|
||||
avatar: {
|
||||
select: {
|
||||
id: true,
|
||||
path: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getUserInfo(id: number, isAdmin: boolean) {
|
||||
const user = await this.prisma.sysUser.findUnique({
|
||||
select: {
|
||||
name: true,
|
||||
gender: true,
|
||||
account: true,
|
||||
avatar: {
|
||||
select: {
|
||||
path: true,
|
||||
},
|
||||
},
|
||||
createdAt: true,
|
||||
},
|
||||
where: {
|
||||
id,
|
||||
status: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const query: Prisma.SysMenuFindManyArgs = { where: {} };
|
||||
|
||||
if (!isAdmin) {
|
||||
query.where = {
|
||||
sysMenuPermission: {
|
||||
some: {
|
||||
permission: {
|
||||
sysRolePermission: {
|
||||
some: { role: { sysUserRole: { some: { userId: id } } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const menus = await this.prisma.sysMenu.findMany(query);
|
||||
return {
|
||||
...user,
|
||||
menus,
|
||||
};
|
||||
}
|
||||
|
||||
async update(id: number, data: UpdateUserDto) {
|
||||
const user = await this.findOne({ id });
|
||||
if (!user)
|
||||
throw new HttpException('修改失败,用户不存在!', HttpStatus.NOT_FOUND);
|
||||
|
||||
await this.prisma.$transaction(async (prisma) => {
|
||||
await prisma.sysUser.update({
|
||||
data,
|
||||
where: { id },
|
||||
});
|
||||
|
||||
// 禁用用户 删除token
|
||||
if (data.status === 0) {
|
||||
if (data.status === 0) {
|
||||
const keys = await this.cacheManager.store.keys(
|
||||
`${RedisGroup.JwtJti}:${id}:*`,
|
||||
);
|
||||
await this.cacheManager.store.mdel(...keys);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async updatePassword(
|
||||
id: number,
|
||||
{ password, newPassword }: UpdatePasswordDto,
|
||||
) {
|
||||
const user = await this.findOne({ id });
|
||||
if (!user)
|
||||
throw new HttpException('修改失败,用户不存在!', HttpStatus.NOT_FOUND);
|
||||
// 同时存在密码和新密码
|
||||
const data: Partial<SysUser> = {};
|
||||
|
||||
const comparePasswordSalt = user.passwordSalt;
|
||||
const comparePassword = await bcrypt.hash(password, comparePasswordSalt);
|
||||
|
||||
if (comparePassword !== user.password) {
|
||||
throw new UnauthorizedException('密码错误,修改失败!');
|
||||
} else {
|
||||
data.passwordSalt = await bcrypt.genSalt();
|
||||
data.password = await bcrypt.hash(newPassword, data.passwordSalt);
|
||||
}
|
||||
|
||||
await this.prisma.sysUser.update({
|
||||
data,
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const user = await this.findOne({ id });
|
||||
|
||||
if (!user)
|
||||
throw new HttpException('删除失败,用户不存在!', HttpStatus.NOT_FOUND);
|
||||
|
||||
await this.prisma.sysUser.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
0
server/src/utils/index.ts
Normal file
0
server/src/utils/index.ts
Normal file
Reference in New Issue
Block a user