feat: 迁移最新代码
parent
6327960a60
commit
7e7b6152f7
36
.env
36
.env
|
|
@ -1,20 +1,26 @@
|
||||||
|
# ========================================================================================
|
||||||
|
# 应用配置
|
||||||
|
# ========================================================================================
|
||||||
|
# 应用名称
|
||||||
|
APP_TITLE = 绝弹应用
|
||||||
|
# 应用副标题
|
||||||
|
APP_SUBTITLE = 快速构建NestJS应用的模板工具
|
||||||
# 服务端口
|
# 服务端口
|
||||||
SERVER_PORT = 3030
|
SERVER_PORT = 3030
|
||||||
# 服务地址
|
# 服务地址
|
||||||
SERVER_HOST = 0.0.0.0
|
SERVER_HOST = 0.0.0.0
|
||||||
|
# 服务域名
|
||||||
SERVER_URL = http://127.0.0.1
|
SERVER_URL = http://127.0.0.1
|
||||||
|
# 接口地址
|
||||||
SERVER_OPENAPI_URL = /openapi
|
SERVER_OPENAPI_URL = /openapi
|
||||||
|
|
||||||
APP_TITLE = 绝弹应用
|
# ========================================================================================
|
||||||
|
# 数据库配置
|
||||||
APP_SUBTITLE = 快速构建NestJS应用的模板工具
|
# ========================================================================================
|
||||||
|
|
||||||
# 数据库类型
|
# 数据库类型
|
||||||
DB_TYPE = sqlite
|
DB_TYPE = sqlite
|
||||||
# sqlite数据库地址
|
# sqlite数据库地址
|
||||||
DB_SQLITE_PATH = content/database/database.sqlite
|
DB_SQLITE_PATH = ./database/db.sqlite
|
||||||
# mysql数据库地址
|
# mysql数据库地址
|
||||||
DB_MYSQL_HOST = 127.0.0.1
|
DB_MYSQL_HOST = 127.0.0.1
|
||||||
# mysql数据库端口
|
# mysql数据库端口
|
||||||
|
|
@ -26,4 +32,18 @@ DB_MYSQL_PASSWORD = test1
|
||||||
# mysql数据库名称
|
# mysql数据库名称
|
||||||
DB_MYSQL_DATABASE = test1
|
DB_MYSQL_DATABASE = test1
|
||||||
|
|
||||||
UPLOAD_FOLDER = content/upload
|
# ========================================================================================
|
||||||
|
# 上传和静态文件配置
|
||||||
|
# ========================================================================================
|
||||||
|
# 上传文件目录
|
||||||
|
UPLOAD_DIR = ./uploads
|
||||||
|
# 静态文件目录
|
||||||
|
STATIC_DIR = ./public
|
||||||
|
|
||||||
|
# ========================================================================================
|
||||||
|
# 分页配置
|
||||||
|
# ========================================================================================
|
||||||
|
# 默认页码
|
||||||
|
DEFAULT_PAGE = 1
|
||||||
|
# 默认每页数量
|
||||||
|
DEFAULT_SIZE = 10
|
||||||
|
|
@ -39,6 +39,15 @@ SwaggerModule.setup('openapi', app, document);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 权限认证
|
||||||
|
|
||||||
|
1. 依赖
|
||||||
|
```bash
|
||||||
|
pnpm i @nestjs/passport passport passport-local passport-jwt
|
||||||
|
```
|
||||||
|
|
||||||
|
路径:/auth/login - localguard - localService.validate - jwtService.sign
|
||||||
|
|
||||||
## 配置文件
|
## 配置文件
|
||||||
|
|
||||||
1. 安装依赖
|
1. 安装依赖
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -20,7 +20,7 @@
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
"orm": "typeorm-ts-node-esm -d ./src/features/typeorm/config/index.ts"
|
"orm": "typeorm-ts-node-esm -d ./src/features/typeorm/datasource/index.ts"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
|
|
|
||||||
6255
pnpm-lock.yaml
6255
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,23 +1,21 @@
|
||||||
import { ClassSerializerInterceptor, Global, Module } from '@nestjs/common';
|
import { ClassSerializerInterceptor, Global, Module } from '@nestjs/common';
|
||||||
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
||||||
import {
|
import { PostModule } from '@/modules/post';
|
||||||
AllExecptionFilter,
|
import { RoleModule } from '@/modules/role';
|
||||||
BaseModule,
|
import { UploadModule } from '@/modules/upload';
|
||||||
ConfigModule,
|
import { PermissionModule } from '@/modules/permission';
|
||||||
HttpExecptionFilter,
|
import { PermissionGuard } from '@/features/permission/permission.guard';
|
||||||
LoggerInterceptor,
|
import { ConfigModule } from '@/config';
|
||||||
LoggerModule,
|
import { LoggerInterceptor, LoggerModule } from '@/features/logger';
|
||||||
MulterModule,
|
import { ServeStaticModule } from '@/features/static';
|
||||||
ResponseInterceptor,
|
import { BaseModule } from '@/features/base';
|
||||||
ServeStaticModule,
|
import { AllExecptionFilter, HttpExecptionFilter } from '@/features/exception';
|
||||||
TypeormModule,
|
import { ResponseInterceptor } from '@/features/response';
|
||||||
ValidationExecptionFilter,
|
import { TypeormModule } from '@/features/typeorm';
|
||||||
validationPipeFactory,
|
import { validationPipeFactory, ValidationExecptionFilter } from '@/features/validation';
|
||||||
} from './features';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { AuthModule, UserModule } from './modules';
|
import { AuthModule, JwtGuard } from '@/modules/auth';
|
||||||
import { PostModule } from './modules/post/post.module';
|
import { UserModule } from '@/modules/user';
|
||||||
import { RoleModule } from './modules/role/role.module';
|
|
||||||
import { UploadModule } from './modules/upload/upload.module';
|
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
|
|
@ -38,10 +36,6 @@ import { UploadModule } from './modules/upload/upload.module';
|
||||||
* 基础模块(全局),提供基础服务
|
* 基础模块(全局),提供基础服务
|
||||||
*/
|
*/
|
||||||
BaseModule,
|
BaseModule,
|
||||||
/**
|
|
||||||
* 文件上传配置模块(全局)
|
|
||||||
*/
|
|
||||||
MulterModule,
|
|
||||||
/**
|
/**
|
||||||
* 数据库ORM
|
* 数据库ORM
|
||||||
*/
|
*/
|
||||||
|
|
@ -54,6 +48,10 @@ import { UploadModule } from './modules/upload/upload.module';
|
||||||
* 账户模块
|
* 账户模块
|
||||||
*/
|
*/
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
/**
|
||||||
|
* JWT模块
|
||||||
|
*/
|
||||||
|
JwtModule,
|
||||||
/**
|
/**
|
||||||
* 角色模块
|
* 角色模块
|
||||||
*/
|
*/
|
||||||
|
|
@ -66,6 +64,10 @@ import { UploadModule } from './modules/upload/upload.module';
|
||||||
* 文章模块
|
* 文章模块
|
||||||
*/
|
*/
|
||||||
PostModule,
|
PostModule,
|
||||||
|
/**
|
||||||
|
* 权限模块
|
||||||
|
*/
|
||||||
|
PermissionModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
/**
|
/**
|
||||||
|
|
@ -124,6 +126,20 @@ import { UploadModule } from './modules/upload/upload.module';
|
||||||
provide: APP_FILTER,
|
provide: APP_FILTER,
|
||||||
useClass: ValidationExecptionFilter,
|
useClass: ValidationExecptionFilter,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 全局JWT守卫(校验是否登陆)
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: JwtGuard,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局权限守卫(校验是否有权限)
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: PermissionGuard,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
export interface Config {
|
||||||
|
/**
|
||||||
|
* 服务器端口
|
||||||
|
*/
|
||||||
|
SERVER_PORT: number;
|
||||||
|
/**
|
||||||
|
* 服务器地址
|
||||||
|
*/
|
||||||
|
SERVER_HOST: string;
|
||||||
|
/**
|
||||||
|
* OPENAPI 地址
|
||||||
|
*/
|
||||||
|
SERVER_OPENAPI_URL: string;
|
||||||
|
/**
|
||||||
|
* 应用名称
|
||||||
|
*/
|
||||||
|
APP_TITLE: string;
|
||||||
|
/**
|
||||||
|
* 应用副标题
|
||||||
|
*/
|
||||||
|
APP_SUBTITLE: string;
|
||||||
|
/**
|
||||||
|
* 数据库类型
|
||||||
|
*/
|
||||||
|
DB_TYPE: string;
|
||||||
|
/**
|
||||||
|
* SQLite 数据库文件路径
|
||||||
|
*/
|
||||||
|
DB_SQLITE_PATH: string;
|
||||||
|
/**
|
||||||
|
* 上传文件目录
|
||||||
|
*/
|
||||||
|
UPLOAD_DIR: string;
|
||||||
|
/**
|
||||||
|
* 上传文件 URL
|
||||||
|
*/
|
||||||
|
UPLOAD_URL: string;
|
||||||
|
/**
|
||||||
|
* 静态文件目录
|
||||||
|
*/
|
||||||
|
STATIC_DIR: string;
|
||||||
|
/**
|
||||||
|
* 默认分页
|
||||||
|
*/
|
||||||
|
DEFAULT_PAGE: number;
|
||||||
|
/**
|
||||||
|
* 默认分页大小
|
||||||
|
*/
|
||||||
|
DEFAULT_SIZE: number;
|
||||||
|
/**
|
||||||
|
* JWT 密钥
|
||||||
|
*/
|
||||||
|
JWT_SECRET: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ConfigModule as _ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
|
export const ConfigModule = _ConfigModule.forRoot({
|
||||||
|
envFilePath: ['.env.development', '.env.local', '.env'],
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './config.module';
|
||||||
|
export * from './config.interface';
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
|
/**
|
||||||
|
* 环境变量枚举
|
||||||
|
*/
|
||||||
export enum envKeys {
|
export enum envKeys {
|
||||||
|
/**
|
||||||
|
* 服务器地址
|
||||||
|
*/
|
||||||
SERVER_HOST = 'SERVER_HOST',
|
SERVER_HOST = 'SERVER_HOST',
|
||||||
|
/**
|
||||||
|
* 服务器端口
|
||||||
|
*/
|
||||||
SERVER_PORT = 'SERVER_PORT',
|
SERVER_PORT = 'SERVER_PORT',
|
||||||
UPLOAD_FOLDER = 'UPLOAD_FOLDER',
|
/**
|
||||||
|
* 上传文件夹
|
||||||
|
*/
|
||||||
|
UPLOAD_DIR = 'UPLOAD_DIR',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,20 @@
|
||||||
import { LoggerService } from '../logger';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { Config } from '@/config';
|
||||||
|
|
||||||
export class BaseService {
|
export class BaseService {
|
||||||
constructor(protected readonly loogerService: LoggerService) {}
|
@Inject(ConfigService)
|
||||||
|
protected readonly configService: ConfigService<Config>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化分页参数
|
||||||
|
*/
|
||||||
|
formatPagination(page?: number, size?: number) {
|
||||||
|
const _page = page || this.configService.get('DEFAULT_PAGE', 1);
|
||||||
|
const _size = size || this.configService.get('DEFAULT_SIZE', 10);
|
||||||
|
return {
|
||||||
|
skip: (_page - 1) * _size,
|
||||||
|
take: _size,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export enum configEnum {
|
|
||||||
SERVER_HOST = 'SERVER_HOST',
|
|
||||||
SERVER_PORT = 'SERVER_PORT',
|
|
||||||
UPLOAD_FOLDER = 'UPLOAD_FOLDER',
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { ConfigModule as configModule } from '@nestjs/config';
|
|
||||||
|
|
||||||
export const ConfigModule = configModule.forRoot({
|
|
||||||
envFilePath: ['.env.local', '.env'],
|
|
||||||
isGlobal: true,
|
|
||||||
});
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { ConfigModule as configModule } from '@nestjs/config';
|
|
||||||
|
|
||||||
export const ConfigModule = configModule.forRoot({
|
|
||||||
envFilePath: ['.env.local', '.env'],
|
|
||||||
isGlobal: true,
|
|
||||||
});
|
|
||||||
|
|
@ -9,7 +9,7 @@ export class AllExecptionFilter implements ExceptionFilter {
|
||||||
const response = ctx.getResponse<_Response>();
|
const response = ctx.getResponse<_Response>();
|
||||||
const message = exception.message;
|
const message = exception.message;
|
||||||
const code = ResponseCode.UNKNOWN_ERROR;
|
const code = ResponseCode.UNKNOWN_ERROR;
|
||||||
|
console.log(exception);
|
||||||
response.status(HttpStatus.INTERNAL_SERVER_ERROR).json(Response.create({ code, message, data: null }));
|
response.status(HttpStatus.INTERNAL_SERVER_ERROR).json(Response.create({ code, message, data: null }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ export class HttpExecptionFilter implements ExceptionFilter {
|
||||||
const response = ctx.getResponse<_Response>();
|
const response = ctx.getResponse<_Response>();
|
||||||
const code = exception.getStatus();
|
const code = exception.getStatus();
|
||||||
const message = exception.message;
|
const message = exception.message;
|
||||||
|
|
||||||
response.status(code).json(Response.error(null, message));
|
response.status(code).json(Response.error(null, message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
export * from './base';
|
|
||||||
export * from './config';
|
|
||||||
export * from './exception';
|
|
||||||
export * from './logger';
|
|
||||||
export * from './multer';
|
|
||||||
export * from './pagination';
|
|
||||||
export * from './response';
|
|
||||||
export * from './static';
|
|
||||||
export * from './swagger';
|
|
||||||
export * from './typeorm';
|
|
||||||
export * from './validation';
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { CallHandler, ExecutionContext, Inject, NestInterceptor } from '@nestjs/common';
|
import { CallHandler, ExecutionContext, Inject, NestInterceptor } from '@nestjs/common';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, tap } from 'rxjs';
|
||||||
import { LoggerService } from './logger.service';
|
import { LoggerService } from './logger.service';
|
||||||
|
|
||||||
export class LoggerInterceptor implements NestInterceptor {
|
export class LoggerInterceptor implements NestInterceptor {
|
||||||
|
|
@ -8,10 +8,14 @@ export class LoggerInterceptor implements NestInterceptor {
|
||||||
logger: LoggerService;
|
logger: LoggerService;
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
|
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
|
||||||
const controller = context.getClass();
|
|
||||||
const handler = context.getHandler();
|
|
||||||
const { method, url } = context.switchToHttp().getRequest<Request>();
|
const { method, url } = context.switchToHttp().getRequest<Request>();
|
||||||
this.logger.log(`${method} ${url} +1`, `${controller.name}.${handler.name}`);
|
const now = Date.now();
|
||||||
return next.handle();
|
return next.handle().pipe(
|
||||||
|
tap(() => {
|
||||||
|
const ms = Date.now() - now;
|
||||||
|
const scope = [context.getClass().name, context.getHandler().name].join('.');
|
||||||
|
this.logger.log(`${method} ${url}(${ms} ms) +1`, scope);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ConsoleLogger, Injectable } from '@nestjs/common';
|
import { ConsoleLogger, Injectable } from '@nestjs/common';
|
||||||
import { dayjs } from 'src/common';
|
import { dayjs } from '@/libs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoggerService extends ConsoleLogger {
|
export class LoggerService extends ConsoleLogger {
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import { Global, Module } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { MulterModule as _MulterModule } from '@nestjs/platform-express';
|
|
||||||
import { diskStorage } from 'multer';
|
|
||||||
import { join, parse } from 'path';
|
|
||||||
import { dayjs } from 'src/common';
|
|
||||||
|
|
||||||
@Global()
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
_MulterModule.registerAsync({
|
|
||||||
useFactory: async (configService) => {
|
|
||||||
const dest = configService.get('UPLOAD_FOLDER', './content/upload');
|
|
||||||
return {
|
|
||||||
storage: diskStorage({
|
|
||||||
destination: join(dest),
|
|
||||||
filename: (req, file, cb) => {
|
|
||||||
const yearMonth = dayjs().format('YYYY-MM');
|
|
||||||
const { name, ext } = parse(file.originalname);
|
|
||||||
const randomName = Array(32)
|
|
||||||
.fill(null)
|
|
||||||
.map(() => Math.round(Math.random() * 16).toString(16))
|
|
||||||
.join('');
|
|
||||||
cb(null, `${yearMonth}/${name}-${randomName}${ext}`);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
inject: [ConfigService],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
exports: [_MulterModule],
|
|
||||||
})
|
|
||||||
export class MulterModule {}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { IsNumber, IsOptional, Min } from 'class-validator';
|
import { IsNumber, IsOptional, Min } from 'class-validator';
|
||||||
|
|
||||||
export class paginationDto {
|
export class PaginationDto {
|
||||||
/**
|
/**
|
||||||
* 页码
|
* 页码
|
||||||
*/
|
*/
|
||||||
|
|
@ -11,6 +11,7 @@ export class paginationDto {
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@Transform(({ value }) => Number(value))
|
@Transform(({ value }) => Number(value))
|
||||||
page: number;
|
page: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每页条数
|
* 每页条数
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const PERMISSION_KEY = 'permission';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限枚举
|
||||||
|
*/
|
||||||
|
export const enum PermissionEnum {
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
CREATE = 'create',
|
||||||
|
/**
|
||||||
|
* 查询
|
||||||
|
*/
|
||||||
|
READ = 'read',
|
||||||
|
/**
|
||||||
|
* 更新
|
||||||
|
*/
|
||||||
|
UPDATE = 'update',
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
DELETE = 'delete',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限装饰器
|
||||||
|
* @param permissions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function Permission(...permissions: PermissionEnum[]) {
|
||||||
|
return SetMetadata(PERMISSION_KEY, permissions);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { PERMISSION_KEY } from './permission.decorator';
|
||||||
|
import { UserService } from '@/modules/user';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PermissionGuard implements CanActivate {
|
||||||
|
constructor(private reflector: Reflector, private userService: UserService) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const controller = context.getClass();
|
||||||
|
const handler = context.getHandler();
|
||||||
|
const permissions = this.reflector.getAllAndMerge(PERMISSION_KEY, [controller, handler]);
|
||||||
|
if (!permissions || !permissions.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const user = context.switchToHttp().getRequest().user;
|
||||||
|
if (!user) {
|
||||||
|
throw new UnauthorizedException('用户未登录');
|
||||||
|
}
|
||||||
|
const userPermissions = await this.userService.findUserPermissions(user.id);
|
||||||
|
const hasPermission = permissions.every((permission) => userPermissions.includes(permission));
|
||||||
|
if (!hasPermission) {
|
||||||
|
console.log(userPermissions, permissions);
|
||||||
|
throw new UnauthorizedException('权限不足');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* 响应码枚举,开头与HTTP状态码保持一致
|
* 响应码枚举,首位与HTTP状态码保持一致
|
||||||
*/
|
*/
|
||||||
export enum ResponseCode {
|
export enum ResponseCode {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -3,41 +3,33 @@ import { SetMetadata } from '@nestjs/common';
|
||||||
/**
|
/**
|
||||||
* 元数据的KEY
|
* 元数据的KEY
|
||||||
*/
|
*/
|
||||||
export const RESPONSE_KEY = 'resultor';
|
export const RESPONSE_KEY = 'APP:RESPONSE';
|
||||||
|
|
||||||
/**
|
type RespondFn = {
|
||||||
* 响应结果的类型
|
(type: 'raw' | 'wrap' | 'pagination'): any;
|
||||||
*/
|
|
||||||
export enum ResponseType {
|
|
||||||
/**
|
/**
|
||||||
* 包装类型,返回的数据会被包装成统一的格式
|
* 原始,返回的数据不会被包装
|
||||||
*/
|
*/
|
||||||
WRAP = 'wrap',
|
RAW: 'raw';
|
||||||
/**
|
/**
|
||||||
* 原始类型,返回的数据不会被包装
|
* 包装,返回的数据会被包装成统一的格式
|
||||||
*/
|
*/
|
||||||
RAW = 'raw',
|
WRAP: 'wrap';
|
||||||
/**
|
/**
|
||||||
* 分页类型,返回的数据会被包装成统一的格式,并且会包含分页信息
|
* 分页,需返回 `[data, total]` 格式的数据
|
||||||
*/
|
*/
|
||||||
PAGINATION = 'pagination',
|
PAGINATION: 'pagination';
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 响应结果装饰器的参数
|
|
||||||
*/
|
|
||||||
export class ResponseOptions {
|
|
||||||
/**
|
|
||||||
* 类型,默认为wrap
|
|
||||||
*/
|
|
||||||
type?: ResponseType = ResponseType.WRAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应结果装饰器
|
* 响应结果装饰器
|
||||||
* @param options 参数
|
* @param type 类型
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const Responsing = (options: ResponseOptions) => {
|
export const Respond = <RespondFn>((type = 'wrap') => {
|
||||||
return SetMetadata(RESPONSE_KEY, { ...ResponseOptions, ...options });
|
return SetMetadata(RESPONSE_KEY, type);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
Respond.RAW = 'raw';
|
||||||
|
Respond.WRAP = 'wrap';
|
||||||
|
Respond.PAGINATION = 'pagination';
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,46 @@ import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nes
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { Observable, map } from 'rxjs';
|
import { Observable, map } from 'rxjs';
|
||||||
import { Response } from './response';
|
import { Response } from './response';
|
||||||
import { RESPONSE_KEY, ResponseType } from './response.decorator';
|
import { RESPONSE_KEY, Respond } from './response.decorator';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { Config } from '@/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResponseInterceptor implements NestInterceptor {
|
export class ResponseInterceptor implements NestInterceptor {
|
||||||
constructor(private reflector: Reflector) {}
|
constructor(private reflector: Reflector, private configService: ConfigService<Config>) {}
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
const controller = context.getClass();
|
const controller = context.getClass();
|
||||||
const handler = context.getHandler();
|
const handler = context.getHandler();
|
||||||
const metadata = this.reflector.getAllAndOverride(RESPONSE_KEY, [controller, handler]);
|
const type = this.reflector.getAllAndOverride(RESPONSE_KEY, [controller, handler]);
|
||||||
|
return next.handle().pipe(
|
||||||
const maper = (data: any) => {
|
map((data: any) => {
|
||||||
if (metadata?.type === ResponseType.RAW) {
|
if (type === Respond.RAW) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
if (type === Respond.PAGINATION) {
|
||||||
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
const [list, total] = data;
|
||||||
|
if (request.query.meta) {
|
||||||
|
const page = Number(request.query.page || this.configService.get('DEFAULT_PAGE', 1));
|
||||||
|
const size = Number(request.query.size || this.configService.get('DEFAULT_SIZE', 10));
|
||||||
|
return Response.success({
|
||||||
|
data: list,
|
||||||
|
meta: {
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
total,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Response.success({ data: list, total });
|
||||||
|
}
|
||||||
if (data instanceof Response) {
|
if (data instanceof Response) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
return Response.success(data);
|
return Response.success({ data });
|
||||||
};
|
}),
|
||||||
|
);
|
||||||
return next.handle().pipe(map(maper));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export class Response<T = any> {
|
||||||
example: 2000,
|
example: 2000,
|
||||||
})
|
})
|
||||||
code?: ResponseCode;
|
code?: ResponseCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应消息
|
* 响应消息
|
||||||
* @example '请求成功'
|
* @example '请求成功'
|
||||||
|
|
@ -23,29 +24,39 @@ export class Response<T = any> {
|
||||||
example: '请求成功',
|
example: '请求成功',
|
||||||
})
|
})
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应数据
|
* 响应数据
|
||||||
* @example 1
|
* @example 1
|
||||||
*/
|
*/
|
||||||
@ApiProperty({})
|
@ApiProperty({})
|
||||||
data: T;
|
data: T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应元数据
|
* 响应元数据
|
||||||
* @example { total: 100 }
|
* @example { total: 100 }
|
||||||
*/
|
*/
|
||||||
meta?: any;
|
meta?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据总数
|
||||||
|
*/
|
||||||
|
total?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建成功响应结果
|
* 创建成功响应结果
|
||||||
*/
|
*/
|
||||||
static success(data: any, message = '请求成功') {
|
static success({ code = ResponseCode.SUCESS, message = '请求成功', ...rest }: Response = {} as any) {
|
||||||
return this.create({ code: ResponseCode.SUCESS, message, data });
|
return this.create({ code, message, ...rest });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建失败响应结果
|
* 创建失败响应结果
|
||||||
*/
|
*/
|
||||||
static error(data = null, message = '请求失败') {
|
static error(data = null, message = '请求失败') {
|
||||||
return this.create({ code: ResponseCode.ERROR, message, data });
|
return this.create({ code: ResponseCode.ERROR, message, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建响应结果
|
* 创建响应结果
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
import { ServeStaticModule as module } from '@nestjs/serve-static';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { join } from 'path';
|
import { ServeStaticModule as _ServeStaticModule } from '@nestjs/serve-static';
|
||||||
|
import { Config } from '@/config';
|
||||||
|
|
||||||
export const ServeStaticModule = module.forRoot(
|
export const ServeStaticModule = _ServeStaticModule.forRootAsync({
|
||||||
|
useFactory: (configService: ConfigService<Config>) => {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
rootPath: join(process.cwd(), 'content/upload'),
|
rootPath: configService.get<string>('UPLOAD_DIR', 'uploads'),
|
||||||
serveRoot: '/upload',
|
serveRoot: configService.get<string>('UPLOAD_URL', '/uploads'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rootPath: join(process.cwd(), 'public'),
|
rootPath: configService.get<string>('STATIC_DIR', 'public'),
|
||||||
},
|
},
|
||||||
);
|
];
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import { Config } from '@/config';
|
||||||
|
|
||||||
export const initSwagger = (app: INestApplication) => {
|
export const initSwagger = (app: INestApplication) => {
|
||||||
const configService = app.get(ConfigService);
|
const configService = app.get(ConfigService<Config>);
|
||||||
const openapiUrl = configService.get<string>('SERVER_OPENAPI_URL', 'openapi');
|
const openapiUrl = configService.get<string>('SERVER_OPENAPI_URL', 'openapi');
|
||||||
const appTitle = configService.get<string>('APP_TITLE', 'Apptify');
|
const appTitle = configService.get<string>('APP_TITLE', 'Apptify');
|
||||||
const appSubtitle = configService.get<string>('APP_SUBTITLE', 'Apptify');
|
const appSubtitle = configService.get<string>('APP_SUBTITLE', 'Apptify');
|
||||||
|
|
@ -13,6 +14,11 @@ export const initSwagger = (app: INestApplication) => {
|
||||||
.setDescription('Openapi 3.0文档')
|
.setDescription('Openapi 3.0文档')
|
||||||
.setExternalDoc('JSON数据', `${openapiUrl}.json`)
|
.setExternalDoc('JSON数据', `${openapiUrl}.json`)
|
||||||
.addTag('user', '用户管理')
|
.addTag('user', '用户管理')
|
||||||
|
.addTag('auth', '认证管理')
|
||||||
|
.addTag('role', '角色管理')
|
||||||
|
.addTag('permission', '权限管理')
|
||||||
|
.addTag('post', '文章管理')
|
||||||
|
.addTag('upload', '文件上传')
|
||||||
.build();
|
.build();
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup(openapiUrl, app, document, {
|
SwaggerModule.setup(openapiUrl, app, document, {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { MockPosts1685026010848 } from '../migrations/1685026010848-MockPosts';
|
||||||
*/
|
*/
|
||||||
export const baseConfig: DataSourceOptions = {
|
export const baseConfig: DataSourceOptions = {
|
||||||
type: 'sqlite',
|
type: 'sqlite',
|
||||||
database: 'content/database/database.sqlite',
|
database: 'database/db.sqlite',
|
||||||
logging: false,
|
logging: false,
|
||||||
namingStrategy: new SnakeNamingStrategy(),
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
};
|
};
|
||||||
|
|
@ -21,7 +21,7 @@ export const ormConfig: TypeOrmModuleOptions = {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
logging: true,
|
logging: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer';
|
||||||
import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础实体, 默认按照id倒序
|
* 基础实体, 查询时默认按照id倒序
|
||||||
*/
|
*/
|
||||||
@Entity({ orderBy: { id: 'DESC' } })
|
@Entity({ orderBy: { id: 'DESC' } })
|
||||||
export class BaseEntity {
|
export class BaseEntity {
|
||||||
|
|
@ -12,35 +12,30 @@ export class BaseEntity {
|
||||||
*/
|
*/
|
||||||
@PrimaryGeneratedColumn({ comment: '自增ID' })
|
@PrimaryGeneratedColumn({ comment: '自增ID' })
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
* @example "2022-01-01 10:10:10"
|
* @example "2022-01-01 10:10:10"
|
||||||
*/
|
*/
|
||||||
@CreateDateColumn({ comment: '创建时间' })
|
@CreateDateColumn({ comment: '创建时间' })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建人ID
|
* 创建人ID
|
||||||
* @example 1
|
* @example 1
|
||||||
*/
|
*/
|
||||||
@Column({ comment: '创建人' })
|
@Column({ comment: '创建人', nullable: true })
|
||||||
createdBy: number;
|
createdBy: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新时间
|
* 更新时间
|
||||||
* @example "2022-01-02 11:11:11"
|
* @example "2022-01-02 11:11:11"
|
||||||
*/
|
*/
|
||||||
@UpdateDateColumn({ comment: '更新时间' })
|
@UpdateDateColumn({ comment: '更新时间' })
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新人ID
|
* 更新人ID
|
||||||
* @example 1
|
* @example 1
|
||||||
*/
|
*/
|
||||||
@Column({ comment: '更新人' })
|
@Column({ comment: '更新人', nullable: true })
|
||||||
updatedBy: number;
|
updatedBy: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除时间
|
* 删除时间
|
||||||
* @example "2022-01-03 12:12:12"
|
* @example "2022-01-03 12:12:12"
|
||||||
|
|
@ -48,12 +43,11 @@ export class BaseEntity {
|
||||||
@Exclude()
|
@Exclude()
|
||||||
@DeleteDateColumn({ comment: '删除时间' })
|
@DeleteDateColumn({ comment: '删除时间' })
|
||||||
deleteddAt: Date;
|
deleteddAt: Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除人ID
|
* 删除人ID
|
||||||
* @example 1
|
* @example 1
|
||||||
*/
|
*/
|
||||||
@Exclude()
|
@Exclude()
|
||||||
@Column({ comment: '删除人' })
|
@Column({ comment: '删除人', nullable: true })
|
||||||
deletedBy: number;
|
deletedBy: number;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,30 @@
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { ormConfig } from './config';
|
import { ormConfig } from './datasource';
|
||||||
export * from './config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
|
import { Config } from '@/config';
|
||||||
|
export * from './datasource';
|
||||||
export * from './entities/base';
|
export * from './entities/base';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接数据库
|
* 连接数据库
|
||||||
*/
|
*/
|
||||||
export const TypeormModule = TypeOrmModule.forRoot(ormConfig);
|
export const TypeormModule = TypeOrmModule.forRootAsync({
|
||||||
|
useFactory: (configService: ConfigService<Config>) => {
|
||||||
|
const type = configService.get<string>('DB_TYPE', 'sqlite');
|
||||||
|
if (type === 'sqlite') {
|
||||||
|
const database = configService.get<string>('DB_SQLITE_PATH', 'database/db.sqlite');
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
database,
|
||||||
|
synchronize: true,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (type === 'mysql') {
|
||||||
|
}
|
||||||
|
return ormConfig;
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
import mock from 'mockjs';
|
import mock from 'mockjs';
|
||||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
export class CreateUsersTable1682693329275 implements MigrationInterface {
|
export class CreateUsersTable1682693329275 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
const numbers = Array(20).fill(0);
|
const numbers = Array(20).fill(0);
|
||||||
const users = numbers.map(() => {
|
const users = numbers.map(() => {
|
||||||
const guid = `"${v4()}"`;
|
|
||||||
const username = `"${mock.Random.name()}"`;
|
const username = `"${mock.Random.name()}"`;
|
||||||
const nickname = `"${mock.Random.cname()}"`;
|
const nickname = `"${mock.Random.cname()}"`;
|
||||||
const description = `"${mock.Random.csentence(100, 120)}"`;
|
const description = `"${mock.Random.csentence(100, 120)}"`;
|
||||||
const avatar = `"https://picsum.photos/400/300"`;
|
const avatar = `"https://picsum.photos/400/300"`;
|
||||||
const password = `"123456"`;
|
const password = `"123456"`;
|
||||||
return [guid, username, nickname, description, avatar, password];
|
const createdAt = `"${mock.Random.datetime()}"`;
|
||||||
|
return [username, nickname, description, avatar, password, createdAt];
|
||||||
});
|
});
|
||||||
const fields = ['guid', 'username', 'nickname', 'description', 'avatar', 'password'].join(',');
|
const fields = ['username', 'nickname', 'description', 'avatar', 'password', 'created_at'].join(',');
|
||||||
const values = users.map((user) => `(${user.join(',')})`).join(',');
|
const values = users.map((user) => `(${user.join(',')})`).join(',');
|
||||||
await queryRunner.query(`INSERT INTO user (${fields}) VALUES ${values}`);
|
await queryRunner.query(`INSERT INTO user (${fields}) VALUES ${values}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
import mock from 'mockjs';
|
import mock from 'mockjs';
|
||||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
export class MockPosts1685026010848 implements MigrationInterface {
|
export class MockPosts1685026010848 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
const numbers = Array(20).fill(0);
|
const numbers = Array(20).fill(0);
|
||||||
const users = numbers.map(() => {
|
const users = numbers.map(() => {
|
||||||
const guid = `"${v4()}"`;
|
|
||||||
const title = `"${mock.Random.csentence(10, 30)}"`;
|
const title = `"${mock.Random.csentence(10, 30)}"`;
|
||||||
const description = `"${mock.Random.csentence(100, 120)}"`;
|
const description = `"${mock.Random.csentence(100, 120)}"`;
|
||||||
const content = `"${mock.Random.csentence(200, 220)}"`;
|
const content = `"${mock.Random.csentence(200, 220)}"`;
|
||||||
return [guid, title, description, content];
|
return [title, description, content];
|
||||||
});
|
});
|
||||||
const fields = ['guid', 'title', 'description', 'content'].join(',');
|
const fields = ['title', 'description', 'content'].join(',');
|
||||||
const values = users.map((user) => `(${user.join(',')})`).join(',');
|
const values = users.map((user) => `(${user.join(',')})`).join(',');
|
||||||
await queryRunner.query(`INSERT INTO post (${fields}) VALUES ${values}`);
|
await queryRunner.query(`INSERT INTO post (${fields}) VALUES ${values}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,48 +2,42 @@ import dayjs from 'dayjs';
|
||||||
import 'dayjs/locale/zh-cn';
|
import 'dayjs/locale/zh-cn';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
|
||||||
export const DATETIME = 'YYYY-MM-DD HH:mm:ss';
|
|
||||||
|
|
||||||
export const DATE = 'YYYY-MM-DD';
|
|
||||||
|
|
||||||
export const TIME = 'HH:mm:ss';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中文语言包
|
* 使用中文语言包
|
||||||
*/
|
*/
|
||||||
dayjs.locale('zh-cn');
|
dayjs.locale('zh-cn');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 相对时间插件
|
* 使用相对时间插件
|
||||||
* @see https://dayjs.gitee.io/docs/zh-CN/plugin/relative-time
|
* @see https://dayjs.gitee.io/docs/zh-CN/plugin/relative-time
|
||||||
*/
|
*/
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* 默认时间格式
|
* 默认时间格式
|
||||||
*/
|
*/
|
||||||
dayjs.DATETIME = DATETIME;
|
dayjs.DATETIME = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认日期格式
|
* 默认日期格式
|
||||||
*/
|
*/
|
||||||
dayjs.DATE = DATE;
|
dayjs.DATE = 'YYYY-MM-DD';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认时间格式
|
* 默认时间格式
|
||||||
*/
|
*/
|
||||||
dayjs.TIME = TIME;
|
dayjs.TIME = 'HH:mm:ss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存原始的format方法
|
||||||
|
*/
|
||||||
|
dayjs.prototype._format = dayjs.prototype.format;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重写format方法,如果没有传入format参数,则使用默认的时间格式
|
* 重写format方法,如果没有传入format参数,则使用默认的时间格式
|
||||||
*/
|
*/
|
||||||
dayjs.prototype._format = dayjs.prototype.format;
|
dayjs.prototype.format = function (format = dayjs.DATETIME) {
|
||||||
dayjs.prototype.format = function (format?: string) {
|
|
||||||
if (format) {
|
|
||||||
return this._format(format);
|
return this._format(format);
|
||||||
}
|
|
||||||
return this._format(dayjs.DATETIME);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { dayjs };
|
export { dayjs };
|
||||||
|
|
@ -16,6 +16,9 @@ declare module 'dayjs' {
|
||||||
*/
|
*/
|
||||||
export let TIME: 'HH:mm:ss';
|
export let TIME: 'HH:mm:ss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存原始的format方法
|
||||||
|
*/
|
||||||
interface Dayjs {
|
interface Dayjs {
|
||||||
_format: (format?: string) => string;
|
_format: (format?: string) => string;
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { VersioningType } from '@nestjs/common';
|
import { VersioningType } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { initSwagger, LoggerService } from 'src/features';
|
import { initSwagger } from '@/features/swagger';
|
||||||
|
import { LoggerService } from '@/features/logger';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
import { Body, Controller, HttpStatus, Post, Request, UseGuards } from '@nestjs/common';
|
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { Public } from './jwt';
|
import { Public } from './jwt';
|
||||||
import { LocalAuthDto, LocalAuthGuard } from './local';
|
import { AuthUserDto } from './dto/auth-user.dto';
|
||||||
|
|
||||||
@ApiTags('auth')
|
@ApiTags('auth')
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private accountService: AuthService) {}
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@UseGuards(LocalAuthGuard)
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: '账号或密码错误' })
|
@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: '账号或密码错误' })
|
||||||
@ApiResponse({ status: HttpStatus.OK, description: '登录成功' })
|
@ApiResponse({ description: '登录成功' })
|
||||||
@ApiOperation({ summary: '账号登录', operationId: 'login' })
|
@ApiOperation({ summary: '账号登录', operationId: 'login' })
|
||||||
login(@Request() req: any, @Body() user: LocalAuthDto) {
|
login(@Body() user: AuthUserDto) {
|
||||||
this.accountService.sign(req.user);
|
return this.authService.signIn(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { Module } from '@nestjs/common';
|
||||||
import { UserModule } from '../user';
|
import { UserModule } from '../user';
|
||||||
import { AuthController } from './auth.controller';
|
import { AuthController } from './auth.controller';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { JwtAuthService, JwtModule } from './jwt';
|
import { JwtModule } from './jwt';
|
||||||
import { LocalAuthService } from './local';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UserModule, JwtModule],
|
|
||||||
providers: [AuthService, LocalAuthService, JwtAuthService],
|
|
||||||
exports: [AuthService],
|
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
|
imports: [UserModule, JwtModule],
|
||||||
|
providers: [AuthService],
|
||||||
|
exports: [AuthService],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,21 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { UserService } from '../user';
|
import { UserService } from '../user';
|
||||||
|
import { AuthUserDto } from './dto/auth-user.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(private userService: UserService, private jwtService: JwtService) {}
|
constructor(private userService: UserService, private jwtService: JwtService) {}
|
||||||
|
|
||||||
// 验证用户
|
async signIn(authUserDto: AuthUserDto) {
|
||||||
async auth(username: string, password: string): Promise<any> {
|
const user = await this.userService.findByUsername(authUserDto.username);
|
||||||
const user = await this.userService.findOne({ username });
|
const { password, ...result } = user;
|
||||||
if (!user) return null;
|
if (!user) {
|
||||||
if (user.password !== password) return null;
|
throw new UnauthorizedException('用户名不存在');
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
if (password !== authUserDto.password) {
|
||||||
// 令牌签名
|
throw new UnauthorizedException('密码错误');
|
||||||
async sign(user: any) {
|
}
|
||||||
const payload = { id: user.id, username: user.username };
|
return this.jwtService.signAsync(result);
|
||||||
return this.jwtService.sign(payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
export class LocalAuthDto {
|
export class AuthUserDto {
|
||||||
/**
|
/**
|
||||||
* 用户名
|
* 用户名
|
||||||
* @example admin
|
* @example admin
|
||||||
|
|
@ -11,5 +11,6 @@ export class LocalAuthDto {
|
||||||
* 用户密码
|
* 用户密码
|
||||||
* @example 123456
|
* @example 123456
|
||||||
*/
|
*/
|
||||||
|
@IsString()
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
@ -2,3 +2,4 @@ export * from './auth.controller';
|
||||||
export * from './auth.module';
|
export * from './auth.module';
|
||||||
export * from './auth.service';
|
export * from './auth.service';
|
||||||
export * from './jwt';
|
export * from './jwt';
|
||||||
|
export * from './dto/auth-user.dto';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
export * from './jwt-decorator';
|
export * from './jwt-decorator';
|
||||||
export * from './jwt-guard';
|
export * from './jwt-guard';
|
||||||
export * from './jwt-module';
|
export * from './jwt-module';
|
||||||
export * from './jwt-service';
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { SetMetadata } from '@nestjs/common';
|
||||||
export const PUBLICK_KEY = 'isPublic';
|
export const PUBLICK_KEY = 'isPublic';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公开当前控制器或路由
|
* 是否需要登陆才能访问
|
||||||
*/
|
*/
|
||||||
export const Public = (idPublic = true) => SetMetadata(PUBLICK_KEY, idPublic);
|
export function Public(isPublic = true) {
|
||||||
|
return SetMetadata(PUBLICK_KEY, isPublic);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,41 @@
|
||||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { APP_GUARD, Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { PUBLICK_KEY } from './jwt-decorator';
|
import { PUBLICK_KEY } from './jwt-decorator';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
export class JwtGuard implements CanActivate {
|
||||||
constructor(private reflector: Reflector) {
|
constructor(private reflector: Reflector, private jwtService: JwtService, private configService: ConfigService) {}
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
const token = this.extractTokenFromHeader(request);
|
||||||
|
if (token) {
|
||||||
|
const secret = this.configService.get('JWT_SECRET');
|
||||||
|
const user = await this.jwtService.verifyAsync(token, { secret });
|
||||||
|
request['user'] = user;
|
||||||
|
}
|
||||||
const metadata = [context.getClass(), context.getHandler()];
|
const metadata = [context.getClass(), context.getHandler()];
|
||||||
const isPublic = this.reflector.getAllAndOverride(PUBLICK_KEY, metadata);
|
const isPublic = this.reflector.getAllAndOverride(PUBLICK_KEY, metadata);
|
||||||
|
if (isPublic === undefined || isPublic) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isPublic !== false && request.method.toLowerCase() === 'GET') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
throw new UnauthorizedException('请先登录');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const routeMethod = context.switchToHttp().getRequest<Request>().method;
|
extractTokenFromHeader(request: Request): string {
|
||||||
|
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||||
if (routeMethod === 'GET' && isPublic !== false) return true;
|
if (type === 'Bearer') {
|
||||||
|
return token;
|
||||||
if (isPublic) return true;
|
}
|
||||||
return super.canActivate(context);
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppJwtGuard = {
|
|
||||||
provide: APP_GUARD,
|
|
||||||
useClass: JwtAuthGuard,
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
import { JwtModule as Jwt } from '@nestjs/jwt';
|
import { Config } from '@/config';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { JwtModule as _JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
export const JwtModule = Jwt.register({
|
export const JwtModule = _JwtModule.registerAsync({
|
||||||
secret: 'secret',
|
useFactory: (configService: ConfigService<Config>) => {
|
||||||
|
return {
|
||||||
|
secret: configService.get('JWT_SECRET', 'todo'),
|
||||||
signOptions: {
|
signOptions: {
|
||||||
expiresIn: '60000s',
|
expiresIn: '60000s',
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JwtAuthService extends PassportStrategy(Strategy) {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
||||||
ignoreExpiration: false,
|
|
||||||
secretOrKey: 'dsfsfs',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate({ id, username }) {
|
|
||||||
return { id, username };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export * from './local-guard';
|
|
||||||
export * from './local-service';
|
|
||||||
export * from './local.dto';
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class LocalAuthGuard extends AuthGuard('local') {}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
|
||||||
import { Strategy } from 'passport-local';
|
|
||||||
import { UserService } from 'src/modules/user/user.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class LocalAuthService extends PassportStrategy(Strategy) {
|
|
||||||
constructor(private readonly userService: UserService) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(username: string, password: string): Promise<any> {
|
|
||||||
const user = await this.userService.findOne({ username });
|
|
||||||
if (!user) {
|
|
||||||
throw new UnauthorizedException('用户名不存在');
|
|
||||||
}
|
|
||||||
if (user.password !== password) {
|
|
||||||
throw new UnauthorizedException('密码错误');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './auth';
|
|
||||||
export * from './user';
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreatePermissionDto {
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreatePermissionDto } from './create-permission.dto';
|
||||||
|
|
||||||
|
export class UpdatePermissionDto extends PartialType(CreatePermissionDto) {}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { BaseEntity } from '@/features/typeorm';
|
||||||
|
import { Role } from '@/modules/role/entities/role.entity';
|
||||||
|
import { Column, Entity, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Permission extends BaseEntity {
|
||||||
|
/**
|
||||||
|
* 权限名称
|
||||||
|
* @example '文章列表'
|
||||||
|
*/
|
||||||
|
@Column({ comment: '权限名称' })
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* 权限标识
|
||||||
|
* @example 'post:list'
|
||||||
|
*/
|
||||||
|
@Column({ comment: '权限标识' })
|
||||||
|
slug: string;
|
||||||
|
/**
|
||||||
|
* 权限描述
|
||||||
|
* @example '文章列表'
|
||||||
|
*/
|
||||||
|
@Column({ comment: '权限描述', nullable: true })
|
||||||
|
description: string;
|
||||||
|
/**
|
||||||
|
* 权限角色
|
||||||
|
* @example '文章列表'
|
||||||
|
*/
|
||||||
|
@ManyToMany(() => Role, (role) => role.permissions)
|
||||||
|
roles: Role[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export * from './dto/create-permission.dto';
|
||||||
|
export * from './dto/update-permission.dto';
|
||||||
|
export * from './entities/permission.entity';
|
||||||
|
export * from './permission.controller';
|
||||||
|
export * from './permission.module';
|
||||||
|
export * from './permission.service';
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||||
|
import { PermissionService } from './permission.service';
|
||||||
|
import { CreatePermissionDto } from './dto/create-permission.dto';
|
||||||
|
import { UpdatePermissionDto } from './dto/update-permission.dto';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { Respond } from '@/features/response';
|
||||||
|
|
||||||
|
@ApiTags('permission')
|
||||||
|
@Controller('permissions')
|
||||||
|
export class PermissionController {
|
||||||
|
constructor(private readonly permissionService: PermissionService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: '创建权限', operationId: 'addPermission' })
|
||||||
|
create(@Body() createPermissionDto: CreatePermissionDto) {
|
||||||
|
return this.permissionService.create(createPermissionDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Respond(Respond.PAGINATION)
|
||||||
|
@ApiOperation({ summary: '批量查询权限', operationId: 'getPermissions' })
|
||||||
|
findAll() {
|
||||||
|
return this.permissionService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: '查询权限', operationId: 'getPermission' })
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.permissionService.findOne(+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
@ApiOperation({ summary: '更新权限', operationId: 'updatePermission' })
|
||||||
|
update(@Param('id') id: string, @Body() updatePermissionDto: UpdatePermissionDto) {
|
||||||
|
return this.permissionService.update(+id, updatePermissionDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: '删除权限', operationId: 'delPermission' })
|
||||||
|
remove(@Param('id') id: string) {
|
||||||
|
return this.permissionService.remove(+id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PermissionService } from './permission.service';
|
||||||
|
import { PermissionController } from './permission.controller';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Permission } from './entities/permission.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Permission])],
|
||||||
|
controllers: [PermissionController],
|
||||||
|
providers: [PermissionService],
|
||||||
|
})
|
||||||
|
export class PermissionModule {}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreatePermissionDto } from './dto/create-permission.dto';
|
||||||
|
import { UpdatePermissionDto } from './dto/update-permission.dto';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Permission } from './entities/permission.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PermissionService {
|
||||||
|
constructor(@InjectRepository(Permission) private readonly permissionRepository: Repository<Permission>) {}
|
||||||
|
|
||||||
|
async create(createPermissionDto: CreatePermissionDto) {
|
||||||
|
const permission = this.permissionRepository.create(createPermissionDto);
|
||||||
|
await this.permissionRepository.save(permission);
|
||||||
|
return permission.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return this.permissionRepository.findAndCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
return `This action returns a #${id} permission`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: number, updatePermissionDto: UpdatePermissionDto) {
|
||||||
|
return `This action updates a #${id} permission`;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
return `This action removes a #${id} permission`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseEntity } from 'src/features';
|
import { BaseEntity } from '@/features/typeorm';
|
||||||
import { User } from 'src/modules/user';
|
import { User } from 'src/modules/user';
|
||||||
import { Column, Entity, ManyToMany } from 'typeorm';
|
import { Column, Entity, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export * from './dto/create-post.dto';
|
||||||
|
export * from './dto/update-post.dto';
|
||||||
|
export * from './entities/post.entity';
|
||||||
|
export * from './post.controller';
|
||||||
|
export * from './post.module';
|
||||||
|
export * from './post.service';
|
||||||
|
|
@ -2,32 +2,39 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
|
||||||
import { PostService } from './post.service';
|
import { PostService } from './post.service';
|
||||||
import { CreatePostDto } from './dto/create-post.dto';
|
import { CreatePostDto } from './dto/create-post.dto';
|
||||||
import { UpdatePostDto } from './dto/update-post.dto';
|
import { UpdatePostDto } from './dto/update-post.dto';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
@Controller('post')
|
@Controller('post')
|
||||||
|
@ApiTags('post')
|
||||||
export class PostController {
|
export class PostController {
|
||||||
constructor(private readonly postService: PostService) {}
|
constructor(private readonly postService: PostService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@ApiOperation({ summary: '创建文章', operationId: 'addPost' })
|
||||||
create(@Body() createPostDto: CreatePostDto) {
|
create(@Body() createPostDto: CreatePostDto) {
|
||||||
return this.postService.create(createPostDto);
|
return this.postService.create(createPostDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@ApiOperation({ summary: '批量查询文章', operationId: 'getPosts' })
|
||||||
findAll() {
|
findAll() {
|
||||||
return this.postService.findAll();
|
return this.postService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: '查询文章', operationId: 'getPost' })
|
||||||
findOne(@Param('id') id: string) {
|
findOne(@Param('id') id: string) {
|
||||||
return this.postService.findOne(+id);
|
return this.postService.findOne(+id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
|
@ApiOperation({ summary: '更新文章', operationId: 'updatePost' })
|
||||||
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
|
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
|
||||||
return this.postService.update(+id, updatePostDto);
|
return this.postService.update(+id, updatePostDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: '删除文章', operationId: 'delPost' })
|
||||||
remove(@Param('id') id: string) {
|
remove(@Param('id') id: string) {
|
||||||
return this.postService.remove(+id);
|
return this.postService.remove(+id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,18 @@
|
||||||
export class CreateRoleDto {}
|
import { Permission } from '@/modules/permission/entities/permission.entity';
|
||||||
|
import { IsInt, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateRoleDto {
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt({ each: true })
|
||||||
|
permissions?: Permission[];
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,39 @@
|
||||||
|
import { BaseEntity } from '@/features/typeorm';
|
||||||
|
import { Permission } from '@/modules/permission/entities/permission.entity';
|
||||||
import { User } from 'src/modules/user';
|
import { User } from 'src/modules/user';
|
||||||
import { Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Role {
|
export class Role extends BaseEntity {
|
||||||
@PrimaryGeneratedColumn()
|
/**
|
||||||
id: number;
|
* 角色名称
|
||||||
|
* @example '管理员'
|
||||||
|
*/
|
||||||
|
@Column({ comment: '角色名称' })
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* 角色标识
|
||||||
|
* @example 'admin'
|
||||||
|
*/
|
||||||
|
@Column({ comment: '角色标识' })
|
||||||
|
slug: string;
|
||||||
|
/**
|
||||||
|
* 角色描述
|
||||||
|
* @example '拥有所有权限'
|
||||||
|
*/
|
||||||
|
@Column({ comment: '角色描述', nullable: true })
|
||||||
|
description: string;
|
||||||
|
/**
|
||||||
|
* 角色权限
|
||||||
|
* @example [1, 2, 3]
|
||||||
|
*/
|
||||||
|
@JoinTable()
|
||||||
|
@ManyToMany(() => Permission, (permission) => permission.roles)
|
||||||
|
permissions: Permission[];
|
||||||
|
/**
|
||||||
|
* 角色用户
|
||||||
|
* @example [1, 2, 3]
|
||||||
|
*/
|
||||||
@ManyToMany(() => User, (user) => user.roles)
|
@ManyToMany(() => User, (user) => user.roles)
|
||||||
user: User;
|
user: User;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export * from './dto/create-role.dto';
|
||||||
|
export * from './dto/update-role.dto';
|
||||||
|
export * from './entities/role.entity';
|
||||||
|
export * from './role.controller';
|
||||||
|
export * from './role.module';
|
||||||
|
export * from './role.service';
|
||||||
|
|
@ -1,35 +1,42 @@
|
||||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { CreateRoleDto } from './dto/create-role.dto';
|
import { CreateRoleDto } from './dto/create-role.dto';
|
||||||
import { UpdateRoleDto } from './dto/update-role.dto';
|
import { UpdateRoleDto } from './dto/update-role.dto';
|
||||||
import { RoleService } from './role.service';
|
import { RoleService } from './role.service';
|
||||||
|
import { Respond } from '@/features/response';
|
||||||
|
|
||||||
@ApiTags('role')
|
@ApiTags('role')
|
||||||
@Controller('role')
|
@Controller('roles')
|
||||||
export class RoleController {
|
export class RoleController {
|
||||||
constructor(private readonly roleService: RoleService) {}
|
constructor(private readonly roleService: RoleService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@ApiOperation({ summary: '创建角色', operationId: 'addRole' })
|
||||||
create(@Body() createRoleDto: CreateRoleDto) {
|
create(@Body() createRoleDto: CreateRoleDto) {
|
||||||
return this.roleService.create(createRoleDto);
|
return this.roleService.create(createRoleDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@Respond(Respond.PAGINATION)
|
||||||
|
@ApiOperation({ summary: '批量查询角色', operationId: 'getRoles' })
|
||||||
findAll() {
|
findAll() {
|
||||||
return this.roleService.findAll();
|
return this.roleService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: '查询角色', operationId: 'getRole' })
|
||||||
findOne(@Param('id') id: string) {
|
findOne(@Param('id') id: string) {
|
||||||
return this.roleService.findOne(+id);
|
return this.roleService.findOne(+id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
|
@ApiOperation({ summary: '更新角色', operationId: 'updateRole' })
|
||||||
update(@Param('id') id: string, @Body() updateRoleDto: UpdateRoleDto) {
|
update(@Param('id') id: string, @Body() updateRoleDto: UpdateRoleDto) {
|
||||||
return this.roleService.update(+id, updateRoleDto);
|
return this.roleService.update(+id, updateRoleDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: '删除角色', operationId: 'delRole' })
|
||||||
remove(@Param('id') id: string) {
|
remove(@Param('id') id: string) {
|
||||||
return this.roleService.remove(+id);
|
return this.roleService.remove(+id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,27 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { CreateRoleDto } from './dto/create-role.dto';
|
import { CreateRoleDto } from './dto/create-role.dto';
|
||||||
import { UpdateRoleDto } from './dto/update-role.dto';
|
import { UpdateRoleDto } from './dto/update-role.dto';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { Role } from './entities/role.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RoleService {
|
export class RoleService {
|
||||||
create(createRoleDto: CreateRoleDto) {
|
constructor(@InjectRepository(Role) private readonly roleRepository: Repository<Role>) {}
|
||||||
return 'This action adds a new role';
|
|
||||||
|
async create(createRoleDto: CreateRoleDto) {
|
||||||
|
const role = this.roleRepository.create(createRoleDto);
|
||||||
|
if (createRoleDto.permissions) {
|
||||||
|
role.permissions = createRoleDto.permissions.map((id) => ({ id })) as any;
|
||||||
|
}
|
||||||
|
await this.roleRepository.save(role);
|
||||||
|
return role.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll() {
|
findAll() {
|
||||||
return `This action returns all role`;
|
return this.roleRepository.findAndCount({
|
||||||
|
relations: ['permissions'],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findOne(id: number) {
|
findOne(id: number) {
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,42 @@
|
||||||
import { BaseEntity, Column, Entity } from 'typeorm';
|
import { BaseEntity } from '@/features/typeorm';
|
||||||
|
import { Column, Entity } from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Upload extends BaseEntity {
|
export class Upload extends BaseEntity {
|
||||||
/**
|
|
||||||
* 文件大小
|
|
||||||
* @example 1024
|
|
||||||
*/
|
|
||||||
@Column({ comment: '文件大小' })
|
|
||||||
size: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件名
|
* 文件名
|
||||||
* @example "xxx.jpg"
|
* @example "xxx.jpg"
|
||||||
*/
|
*/
|
||||||
@Column({ comment: '文件名' })
|
@Column({ comment: '文件名' })
|
||||||
name: string;
|
name: string;
|
||||||
|
/**
|
||||||
|
* 文件大小
|
||||||
|
* @example 1024
|
||||||
|
*/
|
||||||
|
@Column({ comment: '文件大小' })
|
||||||
|
size: number;
|
||||||
/**
|
/**
|
||||||
* 文件类型
|
* 文件类型
|
||||||
* @example "image/jpeg"
|
* @example "image/jpeg"
|
||||||
*/
|
*/
|
||||||
@Column({ comment: '文件类型' })
|
@Column({ comment: '文件类型' })
|
||||||
mimetype: string;
|
mimetype: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件路径
|
* 文件路径
|
||||||
* @example "upload/2021/10/01/xxx.jpg"
|
* @example "/upload/2021/10/01/xxx.jpg"
|
||||||
*/
|
*/
|
||||||
@Column({ comment: '文件路径' })
|
@Column({ comment: '文件路径' })
|
||||||
path: string;
|
path: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件哈希
|
* 文件哈希
|
||||||
* @example "xxx"
|
* @example "xxx"
|
||||||
*/
|
*/
|
||||||
@Column({ comment: '文件哈希' })
|
@Column({ comment: '文件哈希' })
|
||||||
hash: string;
|
hash: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件后缀
|
* 文件后缀
|
||||||
* @example ".jpg"
|
* @example ".jpg"
|
||||||
*/
|
*/
|
||||||
@Column({ comment: '文件后缀' })
|
@Column({ comment: '文件后缀' })
|
||||||
ext: string;
|
extension: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export * from './dto/create-upload.dto';
|
||||||
|
export * from './dto/update-upload.dto';
|
||||||
|
export * from './entities/upload.entity';
|
||||||
|
export * from './upload.controller';
|
||||||
|
export * from './upload.module';
|
||||||
|
export * from './upload.service';
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { Body, Controller, Delete, Get, Param, Patch, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
import { Controller, Delete, Get, Param, Patch, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { CreateUploadDto } from './dto/create-upload.dto';
|
|
||||||
import { UpdateUploadDto } from './dto/update-upload.dto';
|
|
||||||
import { UploadService } from './upload.service';
|
import { UploadService } from './upload.service';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { Respond } from '@/features/response';
|
||||||
|
|
||||||
@ApiTags('upload')
|
@ApiTags('upload')
|
||||||
@Controller('upload')
|
@Controller('upload')
|
||||||
|
|
@ -13,27 +12,31 @@ export class UploadController {
|
||||||
@Post()
|
@Post()
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
@ApiOperation({ summary: '上传文件', operationId: 'upload' })
|
@ApiOperation({ summary: '上传文件', operationId: 'upload' })
|
||||||
create(@Body() createUploadDto: CreateUploadDto, @UploadedFile() file: Express.Multer.File) {
|
create(@UploadedFile() file: Express.Multer.File) {
|
||||||
file.filename;
|
return this.uploadService.create(file);
|
||||||
return this.uploadService.create(createUploadDto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@Respond(Respond.PAGINATION)
|
||||||
|
@ApiOperation({ summary: '批量查询', operationId: 'getUploads' })
|
||||||
findAll() {
|
findAll() {
|
||||||
return this.uploadService.findAll();
|
return this.uploadService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: '查询', operationId: 'getUpload' })
|
||||||
findOne(@Param('id') id: string) {
|
findOne(@Param('id') id: string) {
|
||||||
return this.uploadService.findOne(+id);
|
return this.uploadService.findOne(+id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
update(@Param('id') id: string, @Body() updateUploadDto: UpdateUploadDto) {
|
@ApiOperation({ summary: '更新', operationId: 'updateUpload' })
|
||||||
return this.uploadService.update(+id, updateUploadDto);
|
update() {
|
||||||
|
return this.uploadService.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: '删除', operationId: 'delUpload' })
|
||||||
remove(@Param('id') id: string) {
|
remove(@Param('id') id: string) {
|
||||||
return this.uploadService.remove(+id);
|
return this.uploadService.remove(+id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,37 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { UploadController } from './upload.controller';
|
import { UploadController } from './upload.controller';
|
||||||
import { UploadService } from './upload.service';
|
import { UploadService } from './upload.service';
|
||||||
|
import { MulterModule } from '@nestjs/platform-express';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { join, parse } from 'path';
|
||||||
|
import { diskStorage } from 'multer';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Upload } from './entities/upload.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Upload]),
|
||||||
|
MulterModule.registerAsync({
|
||||||
|
useFactory: async (configService) => {
|
||||||
|
const dest = configService.get('UPLOAD_FOLDER', './public/upload');
|
||||||
|
const storage = diskStorage({
|
||||||
|
destination: join(dest),
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const yearMonth = dayjs().format('YYYY-MM');
|
||||||
|
const { name, ext } = parse(file.originalname);
|
||||||
|
const randomName = Array(32)
|
||||||
|
.fill(null)
|
||||||
|
.map(() => Math.round(Math.random() * 16).toString(16))
|
||||||
|
.join('');
|
||||||
|
cb(null, `${yearMonth}/${name}-${randomName}${ext}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return { storage };
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
],
|
||||||
controllers: [UploadController],
|
controllers: [UploadController],
|
||||||
providers: [UploadService],
|
providers: [UploadService],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,34 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { CreateUploadDto } from './dto/create-upload.dto';
|
import { Upload } from './entities/upload.entity';
|
||||||
import { UpdateUploadDto } from './dto/update-upload.dto';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UploadService {
|
export class UploadService {
|
||||||
constructor() {
|
constructor(@InjectRepository(Upload) private readonly uploadRepository: Repository<Upload>) {}
|
||||||
console;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(createUploadDto: CreateUploadDto) {
|
async create(file: Express.Multer.File) {
|
||||||
return 'This action adds a new upload';
|
const upload = this.uploadRepository.create({
|
||||||
|
name: file.originalname,
|
||||||
|
mimetype: file.mimetype,
|
||||||
|
size: file.size,
|
||||||
|
hash: file.filename,
|
||||||
|
path: file.path,
|
||||||
|
});
|
||||||
|
await this.uploadRepository.save(upload);
|
||||||
|
return upload.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll() {
|
findAll() {
|
||||||
return `This action returns all upload`;
|
return this.uploadRepository.findAndCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
findOne(id: number) {
|
findOne(id: number) {
|
||||||
return `This action returns a #${id} upload`;
|
return `This action returns a #${id} upload`;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(id: number, updateUploadDto: UpdateUploadDto) {
|
update() {
|
||||||
return `This action updates a #${id} upload`;
|
return `This action updates a #${1} upload`;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(id: number) {
|
remove(id: number) {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,37 @@
|
||||||
import { IsOptional, IsString } from 'class-validator';
|
import { Role } from '@/modules/role/entities/role.entity';
|
||||||
|
import { IsInt, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class CreateUserDto {
|
export class CreateUserDto {
|
||||||
@IsString({ message: '用户名不能为空' })
|
/**
|
||||||
|
* 登录账号
|
||||||
|
* @example 'juetan'
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
username: string;
|
username: string;
|
||||||
|
/**
|
||||||
|
* 用户密码
|
||||||
|
* @example 'password'
|
||||||
|
*/
|
||||||
@IsString()
|
@IsString()
|
||||||
password: string;
|
password: string;
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
* @example '绝弹'
|
||||||
|
*/
|
||||||
@IsString()
|
@IsString()
|
||||||
nickname: string;
|
nickname: string;
|
||||||
|
/**
|
||||||
@IsString()
|
* 用户头像
|
||||||
|
* @example './assets/222421415123.png '
|
||||||
|
*/
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
/**
|
||||||
|
* 用户角色
|
||||||
|
* @example [1, 2, 3]
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt({ each: true })
|
||||||
|
roles: Role[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { IntersectionType } from '@nestjs/swagger';
|
import { IntersectionType } from '@nestjs/swagger';
|
||||||
import { IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
import { paginationDto } from 'src/features';
|
import { PaginationDto } from '@/features/pagination';
|
||||||
|
|
||||||
export class FindUserDto extends IntersectionType(paginationDto) {
|
export class FindUserDto extends IntersectionType(PaginationDto) {
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
nickname: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,12 @@
|
||||||
import { ApiHideProperty } from '@nestjs/swagger';
|
import { ApiHideProperty } from '@nestjs/swagger';
|
||||||
import { Exclude } from 'class-transformer';
|
import { Exclude } from 'class-transformer';
|
||||||
import { BaseEntity } from 'src/features';
|
import { BaseEntity } from '@/features/typeorm';
|
||||||
import { Post } from 'src/modules/post/entities/post.entity';
|
import { Post } from '@/modules/post';
|
||||||
import { Role } from 'src/modules/role/entities/role.entity';
|
import { Role } from '@/modules/role';
|
||||||
import { Column, Entity, ManyToMany } from 'typeorm';
|
import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity({ orderBy: { id: 'DESC' } })
|
||||||
export class User extends BaseEntity {
|
export class User extends BaseEntity {
|
||||||
/**
|
|
||||||
* 用户文章
|
|
||||||
*/
|
|
||||||
@ApiHideProperty()
|
|
||||||
@ManyToMany(() => Post, (post) => post.author)
|
|
||||||
posts: Post[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户角色
|
|
||||||
*/
|
|
||||||
@ManyToMany(() => Role, (role) => role.user)
|
|
||||||
roles: Role[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录账号
|
* 登录账号
|
||||||
* @example 'juetan'
|
* @example 'juetan'
|
||||||
|
|
@ -55,4 +42,26 @@ export class User extends BaseEntity {
|
||||||
@Exclude()
|
@Exclude()
|
||||||
@Column({ length: 64 })
|
@Column({ length: 64 })
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户邮箱
|
||||||
|
* @example 'example@mail.com'
|
||||||
|
*/
|
||||||
|
@Column({ length: 64, nullable: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户文章
|
||||||
|
*/
|
||||||
|
@ApiHideProperty()
|
||||||
|
@ManyToMany(() => Post, (post) => post.author)
|
||||||
|
@JoinTable()
|
||||||
|
posts: Post[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户角色
|
||||||
|
*/
|
||||||
|
@ManyToMany(() => Role, (role) => role.user)
|
||||||
|
@JoinTable()
|
||||||
|
roles: Role[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,13 @@
|
||||||
import {
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Version } from '@nestjs/common';
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
Patch,
|
|
||||||
Post,
|
|
||||||
Query,
|
|
||||||
UploadedFile,
|
|
||||||
UseInterceptors,
|
|
||||||
Version,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
|
||||||
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { BaseController, Pagination } from 'src/features';
|
import { Respond } from '@/features/response';
|
||||||
import { Public } from '../auth/jwt';
|
import { BaseController } from '@/features/base';
|
||||||
import { CreateUserDto, UpdateUserDto } from './dto';
|
import { CreateUserDto, UpdateUserDto } from './dto';
|
||||||
import { FindUserDto } from './dto/find-user.dto';
|
import { FindUserDto } from './dto/find-user.dto';
|
||||||
import { User } from './entities';
|
import { User } from './entities';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
import { Permission, PermissionEnum } from '@/features/permission/permission.decorator';
|
||||||
|
import { Public } from '@/modules/auth/jwt/jwt-decorator';
|
||||||
|
|
||||||
@ApiTags('user')
|
@ApiTags('user')
|
||||||
@Controller('users')
|
@Controller('users')
|
||||||
|
|
@ -27,40 +16,37 @@ export class UserController extends BaseController {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseInterceptors(FileInterceptor('avatar'))
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '创建用户', operationId: 'createUser' })
|
@ApiOperation({ summary: '创建用户', operationId: 'addUser' })
|
||||||
create(@Body() createUserDto: CreateUserDto, @UploadedFile() file: Express.Multer.File) {
|
create(@Body() createUserDto: CreateUserDto) {
|
||||||
createUserDto.avatar = `upload/${file.filename}`;
|
|
||||||
console.log(createUserDto, file);
|
|
||||||
return this.userService.create(createUserDto);
|
return this.userService.create(createUserDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@Public(false)
|
||||||
|
@Respond(Respond.PAGINATION)
|
||||||
|
@Permission(PermissionEnum.READ)
|
||||||
@ApiOkResponse({ isArray: true, type: User })
|
@ApiOkResponse({ isArray: true, type: User })
|
||||||
@ApiOperation({ summary: '批量查询', operationId: 'selectUsers' })
|
@ApiOperation({ summary: '批量查询', operationId: 'getUsers' })
|
||||||
async findMany(@Query() query: FindUserDto) {
|
async findMany(@Query() query: FindUserDto) {
|
||||||
const [data, total] = await this.userService.findAll(query);
|
return this.userService.findMany(query);
|
||||||
const { page, size } = query;
|
|
||||||
return Pagination.wrap({ page, size, total, data });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Version('2')
|
@Version('2')
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@ApiOperation({ summary: '查询用户', operationId: 'selectUserv2' })
|
@ApiOperation({ summary: '查询用户', operationId: 'getUserv2' })
|
||||||
findOne(@Param('id') id: number) {
|
findOne(@Param('id') id: number) {
|
||||||
return this.userService.findOne(+id);
|
return this.userService.findOne(+id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@ApiOperation({ summary: '更新用户', operationId: 'updateUser' })
|
@ApiOperation({ summary: '更新用户', operationId: 'setUser' })
|
||||||
update(@Param('id') id: number, @Body() updateUserDto: UpdateUserDto) {
|
update(@Param('id') id: number, @Body() updateUserDto: UpdateUserDto) {
|
||||||
return this.userService.update(+id, updateUserDto);
|
return this.userService.update(+id, updateUserDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@ApiOperation({ summary: '删除用户', operationId: 'deleteUser' })
|
@ApiOperation({ summary: '删除用户', operationId: 'delUser' })
|
||||||
remove(@Param('id') id: number) {
|
remove(@Param('id') id: number) {
|
||||||
return this.userService.remove(+id);
|
return this.userService.remove(+id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Pagination } from 'src/features';
|
import { Like, Repository } from 'typeorm';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { CreateUserDto, UpdateUserDto } from './dto';
|
import { CreateUserDto, UpdateUserDto } from './dto';
|
||||||
import { FindUserDto } from './dto/find-user.dto';
|
import { FindUserDto } from './dto/find-user.dto';
|
||||||
import { User } from './entities';
|
import { User } from './entities';
|
||||||
|
import { BaseService } from '@/features/base';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService extends BaseService {
|
||||||
constructor(@InjectRepository(User) private userRepository: Repository<User>) {}
|
constructor(@InjectRepository(User) private userRepository: Repository<User>) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建用户
|
* 创建用户
|
||||||
*/
|
*/
|
||||||
async create(createUserDto: CreateUserDto) {
|
async create(createUserDto: CreateUserDto) {
|
||||||
console.log(createUserDto);
|
|
||||||
const user = this.userRepository.create(createUserDto);
|
const user = this.userRepository.create(createUserDto);
|
||||||
|
if (createUserDto.roles) {
|
||||||
|
user.roles = createUserDto.roles.map((id) => ({ id })) as any;
|
||||||
|
}
|
||||||
await this.userRepository.save(user);
|
await this.userRepository.save(user);
|
||||||
return user.id;
|
return user.id;
|
||||||
}
|
}
|
||||||
|
|
@ -23,9 +27,17 @@ export class UserService {
|
||||||
/**
|
/**
|
||||||
* 查找所有用户
|
* 查找所有用户
|
||||||
*/
|
*/
|
||||||
async findAll(dto: FindUserDto) {
|
async findMany(findUserdto: FindUserDto) {
|
||||||
const options = Pagination.optionize(dto);
|
const { nickname, page, size } = findUserdto;
|
||||||
return this.userRepository.findAndCount({ ...options, order: { createdAt: 'DESC' } });
|
const { skip, take } = this.formatPagination(page, size);
|
||||||
|
return this.userRepository.findAndCount({
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
where: {
|
||||||
|
nickname: nickname && Like(`%${nickname}%`),
|
||||||
|
},
|
||||||
|
relations: ['roles'],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -53,7 +65,22 @@ export class UserService {
|
||||||
/**
|
/**
|
||||||
* 根据用户名查找用户
|
* 根据用户名查找用户
|
||||||
*/
|
*/
|
||||||
find(username: string) {
|
findByUsername(username: string) {
|
||||||
return this.userRepository.findOne({ where: { username } });
|
return this.userRepository.findOneOrFail({ where: { username } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户id查找用户权限
|
||||||
|
*/
|
||||||
|
async findUserPermissions(id: number) {
|
||||||
|
const user = await this.userRepository.findOne({
|
||||||
|
where: { id },
|
||||||
|
relations: ['roles', 'roles.permissions'],
|
||||||
|
});
|
||||||
|
if (user) {
|
||||||
|
const permissions = user.roles.flatMap((role) => role.permissions);
|
||||||
|
return [...new Set(permissions.map((i) => i.slug))];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,9 @@ declare namespace NodeJS {
|
||||||
* 环境变量
|
* 环境变量
|
||||||
*/
|
*/
|
||||||
NODE_ENV: 'development' | 'production' | 'test';
|
NODE_ENV: 'development' | 'production' | 'test';
|
||||||
|
/**
|
||||||
|
* 上传文件夹
|
||||||
|
*/
|
||||||
|
UPLOAD_DIR: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"modules": ["./src/modules"]
|
"@/*": ["src/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
/**
|
||||||
|
* 使用 Webpack 时的配置文件
|
||||||
|
* @param {import('webpack').Configuration} config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
module.exports = (config) => {
|
module.exports = (config) => {
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue