feat: 解决冲突
commit
f11b7e85e1
8
.env
8
.env
|
|
@ -12,7 +12,7 @@ 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 = /api
|
||||||
|
|
||||||
# ========================================================================================
|
# ========================================================================================
|
||||||
# 数据库配置
|
# 数据库配置
|
||||||
|
|
@ -20,7 +20,7 @@ SERVER_OPENAPI_URL = /openapi
|
||||||
# 数据库类型
|
# 数据库类型
|
||||||
DB_TYPE = sqlite
|
DB_TYPE = sqlite
|
||||||
# sqlite数据库地址
|
# sqlite数据库地址
|
||||||
DB_SQLITE_PATH = ./database/db.sqlite
|
DB_SQLITE_PATH = ./content/database/db.sqlite
|
||||||
# mysql数据库地址
|
# mysql数据库地址
|
||||||
DB_MYSQL_HOST = 127.0.0.1
|
DB_MYSQL_HOST = 127.0.0.1
|
||||||
# mysql数据库端口
|
# mysql数据库端口
|
||||||
|
|
@ -36,9 +36,9 @@ DB_MYSQL_DATABASE = test1
|
||||||
# 上传和静态文件配置
|
# 上传和静态文件配置
|
||||||
# ========================================================================================
|
# ========================================================================================
|
||||||
# 上传文件目录
|
# 上传文件目录
|
||||||
UPLOAD_DIR = ./uploads
|
UPLOAD_DIR = ./content/uploads
|
||||||
# 静态文件目录
|
# 静态文件目录
|
||||||
STATIC_DIR = ./public
|
STATIC_DIR = ./content/html
|
||||||
|
|
||||||
# ========================================================================================
|
# ========================================================================================
|
||||||
# 分页配置
|
# 分页配置
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -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/datasource/index.ts"
|
"orm": "typeorm-ts-node-esm -d ./src/database/datasource/index.ts"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
|
|
|
||||||
4334
pnpm-lock.yaml
4334
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,61 +1,71 @@
|
||||||
import { ClassSerializerInterceptor, Global, Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
|
||||||
import { PostModule } from '@/modules/post';
|
import { PostModule } from '@/modules/post';
|
||||||
import { RoleModule } from '@/modules/role';
|
import { RoleModule } from '@/modules/role';
|
||||||
import { UploadModule } from '@/modules/upload';
|
import { UploadModule } from '@/modules/upload';
|
||||||
import { PermissionModule } from '@/modules/permission';
|
import { PermissionModule } from '@/modules/permission';
|
||||||
import { PermissionGuard } from '@/features/permission/permission.guard';
|
|
||||||
import { ConfigModule } from '@/config';
|
import { ConfigModule } from '@/config';
|
||||||
import { LoggerInterceptor, LoggerModule } from '@/features/logger';
|
import { LoggerModule } from '@/common/logger';
|
||||||
import { ServeStaticModule } from '@/features/static';
|
import { ServeStaticModule } from '@/common/static';
|
||||||
import { BaseModule } from '@/features/base';
|
import { DatabaseModule } from '@/database';
|
||||||
import { AllExecptionFilter, HttpExecptionFilter } from '@/features/exception';
|
import { ValidationModule } from '@/common/validation';
|
||||||
import { ResponseInterceptor } from '@/features/response';
|
import { AuthModule } from '@/modules/auth';
|
||||||
import { TypeormModule } from '@/features/typeorm';
|
|
||||||
import { validationPipeFactory, ValidationExecptionFilter } from '@/features/validation';
|
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
|
||||||
import { AuthModule, JwtGuard } from '@/modules/auth';
|
|
||||||
import { UserModule } from '@/modules/user';
|
import { UserModule } from '@/modules/user';
|
||||||
|
import { ResponseModule } from '@/common/response';
|
||||||
|
import { SerializationModule } from '@/common/serialization';
|
||||||
|
|
||||||
@Global()
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
/**
|
/**
|
||||||
* 配置模块(全局),提供ConfigService类
|
* 配置模块(全局)
|
||||||
|
* @description 加载.env配置文件
|
||||||
*/
|
*/
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
/**
|
/**
|
||||||
* 日志模块(全局),提供LoggerService类
|
* 日志模块(全局)
|
||||||
|
* @description 用于记录日志
|
||||||
*/
|
*/
|
||||||
LoggerModule,
|
LoggerModule,
|
||||||
/**
|
/**
|
||||||
* 静态资源(全局),/upload和/web
|
* 静态资源(全局)
|
||||||
|
* @description 为静态页面/上传文件提供服务
|
||||||
*/
|
*/
|
||||||
ServeStaticModule,
|
ServeStaticModule,
|
||||||
/**
|
/**
|
||||||
* 基础模块(全局),提供基础服务
|
* 序列化模块
|
||||||
|
* @description 序列化响应结果/异常结果,移除/替换字段
|
||||||
*/
|
*/
|
||||||
BaseModule,
|
SerializationModule,
|
||||||
|
/**
|
||||||
|
* 响应模块
|
||||||
|
* @description 包装响应结果/异常结果
|
||||||
|
*/
|
||||||
|
ResponseModule,
|
||||||
|
/**
|
||||||
|
* 校验模块
|
||||||
|
* @description 校验请求参数,抛出异常时按响应模块的格式包装
|
||||||
|
*/
|
||||||
|
ValidationModule,
|
||||||
/**
|
/**
|
||||||
* 数据库ORM
|
* 数据库ORM
|
||||||
|
* @description 用于连接数据库
|
||||||
*/
|
*/
|
||||||
TypeormModule,
|
DatabaseModule,
|
||||||
/**
|
/**
|
||||||
* 用户模块
|
* 用户模块
|
||||||
*/
|
*/
|
||||||
UserModule,
|
UserModule,
|
||||||
/**
|
/**
|
||||||
* 账户模块
|
* 登陆模块
|
||||||
*/
|
*/
|
||||||
AuthModule,
|
AuthModule,
|
||||||
/**
|
|
||||||
* JWT模块
|
|
||||||
*/
|
|
||||||
JwtModule,
|
|
||||||
/**
|
/**
|
||||||
* 角色模块
|
* 角色模块
|
||||||
*/
|
*/
|
||||||
RoleModule,
|
RoleModule,
|
||||||
|
/**
|
||||||
|
* 权限模块
|
||||||
|
*/
|
||||||
|
PermissionModule,
|
||||||
/**
|
/**
|
||||||
* 上传模块
|
* 上传模块
|
||||||
*/
|
*/
|
||||||
|
|
@ -64,82 +74,6 @@ import { UserModule } from '@/modules/user';
|
||||||
* 文章模块
|
* 文章模块
|
||||||
*/
|
*/
|
||||||
PostModule,
|
PostModule,
|
||||||
/**
|
|
||||||
* 权限模块
|
|
||||||
*/
|
|
||||||
PermissionModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
/**
|
|
||||||
* 全局序列化拦截器
|
|
||||||
* @description 由于中间件的洋葱机制,需放在响应拦截器之前,否则无法检测到实例类型
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_INTERCEPTOR,
|
|
||||||
useClass: ClassSerializerInterceptor,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局响应拦截器
|
|
||||||
* @description 将返回值统一包装成{code, message, data, meta}格式
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_INTERCEPTOR,
|
|
||||||
useClass: ResponseInterceptor,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局日志拦截器
|
|
||||||
* @description 将请求和响应日志打印到控制台
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_INTERCEPTOR,
|
|
||||||
useClass: LoggerInterceptor,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局异常过滤器
|
|
||||||
* @description 将异常统一包装成{code, message, data, meta}格式
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_FILTER,
|
|
||||||
useClass: AllExecptionFilter,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局HTTP异常过滤器
|
|
||||||
* @description 将HTTP异常统一包装成{code, message, data, meta}格式
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_FILTER,
|
|
||||||
useClass: HttpExecptionFilter,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局验证管道
|
|
||||||
* @description 校验和转换输入数据
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_PIPE,
|
|
||||||
useFactory: validationPipeFactory,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局验证异常过滤器
|
|
||||||
* @description 将验证异常统一包装成{code, message, data, meta}格式
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_FILTER,
|
|
||||||
useClass: ValidationExecptionFilter,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局JWT守卫(校验是否登陆)
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_GUARD,
|
|
||||||
useClass: JwtGuard,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 全局权限守卫(校验是否有权限)
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
provide: APP_GUARD,
|
|
||||||
useClass: PermissionGuard,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { LoggerService } from '../logger';
|
import { LoggerService } from '../logger';
|
||||||
|
import { ConfigService } from '@/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础控制器
|
* 控制器基类
|
||||||
*/
|
*/
|
||||||
export class BaseController {
|
export class BaseController {
|
||||||
/**
|
/**
|
||||||
|
|
@ -10,4 +11,10 @@ export class BaseController {
|
||||||
*/
|
*/
|
||||||
@Inject(LoggerService)
|
@Inject(LoggerService)
|
||||||
readonly logger: LoggerService;
|
readonly logger: LoggerService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置服务
|
||||||
|
*/
|
||||||
|
@Inject(ConfigService)
|
||||||
|
readonly config: ConfigService;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Module({})
|
||||||
|
export class BaseModule {}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务基类
|
||||||
|
*/
|
||||||
|
export class BaseService {
|
||||||
|
/**
|
||||||
|
* 配置服务
|
||||||
|
*/
|
||||||
|
@Inject(ConfigService)
|
||||||
|
protected readonly config: ConfigService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化分页参数
|
||||||
|
*/
|
||||||
|
formatPagination(page = this.config.defaultPage, size = this.config.defaultPageSize) {
|
||||||
|
return {
|
||||||
|
skip: (page - 1) * size,
|
||||||
|
take: size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { LoggerService } from './logger.service';
|
||||||
|
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
|
import { LoggerInterceptor } from './logger.interceptor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志模块
|
||||||
|
* @description 包含全局拦截器
|
||||||
|
*/
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
LoggerService,
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: LoggerInterceptor,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exports: [LoggerService],
|
||||||
|
})
|
||||||
|
export class LoggerModule {}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
|
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
|
||||||
import { Response as _Response } from 'express';
|
import { Response as _Response } from 'express';
|
||||||
import { Response } from '../response';
|
import { Response } from './response';
|
||||||
|
|
||||||
@Catch(HttpException)
|
@Catch(HttpException)
|
||||||
export class HttpExecptionFilter implements ExceptionFilter {
|
export class HttpExecptionFilter implements ExceptionFilter {
|
||||||
|
|
@ -2,3 +2,7 @@ export * from './response';
|
||||||
export * from './response.code';
|
export * from './response.code';
|
||||||
export * from './response.decorator';
|
export * from './response.decorator';
|
||||||
export * from './response.interceptor';
|
export * from './response.interceptor';
|
||||||
|
export * from './response.module';
|
||||||
|
export * from './pagination.dto';
|
||||||
|
export * from './http.filter';
|
||||||
|
export * from './notcaptured.filter';
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
|
import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
|
||||||
import { Response as _Response } from 'express';
|
import { Response as _Response } from 'express';
|
||||||
import { Response, ResponseCode } from '../response';
|
import { Response } from './response';
|
||||||
|
import { ResponseCode } from './response.code';
|
||||||
|
|
||||||
@Catch()
|
@Catch()
|
||||||
export class AllExecptionFilter implements ExceptionFilter {
|
export class AllExecptionFilter implements ExceptionFilter {
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { IsNumber, IsOptional, Min } from 'class-validator';
|
import { IsNumber, IsOptional, Min } from 'class-validator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页 DTO
|
||||||
|
* @example { page: 1, size: 10 }
|
||||||
|
*/
|
||||||
export class PaginationDto {
|
export class PaginationDto {
|
||||||
/**
|
/**
|
||||||
* 页码
|
* 页码
|
||||||
|
* @example 1
|
||||||
*/
|
*/
|
||||||
// @IsNumber()
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
|
|
@ -14,6 +18,7 @@ export class PaginationDto {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每页条数
|
* 每页条数
|
||||||
|
* @example 10
|
||||||
*/
|
*/
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
|
|
@ -4,12 +4,11 @@ import { Observable, map } from 'rxjs';
|
||||||
import { Response } from './response';
|
import { Response } from './response';
|
||||||
import { RESPONSE_KEY, Respond } from './response.decorator';
|
import { RESPONSE_KEY, Respond } from './response.decorator';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@/config';
|
||||||
import { Config } from '@/config';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResponseInterceptor implements NestInterceptor {
|
export class ResponseInterceptor implements NestInterceptor {
|
||||||
constructor(private reflector: Reflector, private configService: ConfigService<Config>) {}
|
constructor(private reflector: Reflector, private config: ConfigService) {}
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
const controller = context.getClass();
|
const controller = context.getClass();
|
||||||
|
|
@ -24,8 +23,8 @@ export class ResponseInterceptor implements NestInterceptor {
|
||||||
const request = context.switchToHttp().getRequest<Request>();
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
const [list, total] = data;
|
const [list, total] = data;
|
||||||
if (request.query.meta) {
|
if (request.query.meta) {
|
||||||
const page = Number(request.query.page || this.configService.get('DEFAULT_PAGE', 1));
|
const page = Number(request.query.page || this.config.defaultPage);
|
||||||
const size = Number(request.query.size || this.configService.get('DEFAULT_SIZE', 10));
|
const size = Number(request.query.size || this.config.defaultPageSize);
|
||||||
return Response.success({
|
return Response.success({
|
||||||
data: list,
|
data: list,
|
||||||
meta: {
|
meta: {
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
|
import { AllExecptionFilter } from './notcaptured.filter';
|
||||||
|
import { HttpExecptionFilter } from './http.filter';
|
||||||
|
import { ResponseInterceptor } from './response.interceptor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应模块
|
||||||
|
* @description 包含全局异常/HTTP异常/响应结果拦截器
|
||||||
|
*/
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
/**
|
||||||
|
* 全局异常过滤器
|
||||||
|
* @description 将异常统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: AllExecptionFilter,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局HTTP异常过滤器
|
||||||
|
* @description 将HTTP异常统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: HttpExecptionFilter,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局响应拦截器
|
||||||
|
* @description 将返回值统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ResponseInterceptor,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ResponseModule {}
|
||||||
|
|
@ -9,20 +9,14 @@ export class Response<T = any> {
|
||||||
* 状态码
|
* 状态码
|
||||||
* @example 2000
|
* @example 2000
|
||||||
*/
|
*/
|
||||||
@ApiProperty({
|
@ApiProperty({ type: 'number', example: 2000 })
|
||||||
type: 'number',
|
|
||||||
example: 2000,
|
|
||||||
})
|
|
||||||
code?: ResponseCode;
|
code?: ResponseCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应消息
|
* 响应消息
|
||||||
* @example '请求成功'
|
* @example '请求成功'
|
||||||
*/
|
*/
|
||||||
@ApiProperty({
|
@ApiProperty({ type: 'string', example: '请求成功' })
|
||||||
type: 'string',
|
|
||||||
example: '请求成功',
|
|
||||||
})
|
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,6 +34,7 @@ export class Response<T = any> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据总数
|
* 数据总数
|
||||||
|
* @example 100
|
||||||
*/
|
*/
|
||||||
total?: number;
|
total?: number;
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './serialization.module';
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { ClassSerializerInterceptor, Module } from '@nestjs/common';
|
||||||
|
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化模块
|
||||||
|
* @description 包含全局序列化拦截器
|
||||||
|
*/
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
/**
|
||||||
|
* 全局序列化拦截器
|
||||||
|
* @description 由于中间件的洋葱机制,需放在响应拦截器之前,否则无法检测到实例类型
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ClassSerializerInterceptor,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SerializationModule {}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './static.module';
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ConfigService } from '@/config';
|
||||||
|
import { ServeStaticModule as _ServeStaticModule } from '@nestjs/serve-static';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静态资源模块
|
||||||
|
* @see https://docs.nestjs.com/techniques/mvc#serve-static
|
||||||
|
*/
|
||||||
|
export const ServeStaticModule = _ServeStaticModule.forRootAsync({
|
||||||
|
useFactory: (config: ConfigService) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
rootPath: config.uploadDir,
|
||||||
|
serveRoot: config.uploadPrefix,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootPath: config.staticDir,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@/config';
|
||||||
|
import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import { addResponseWrapper } from './util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Swagger
|
||||||
|
* @param app 应用实例
|
||||||
|
*/
|
||||||
|
export const initSwagger = (app: INestApplication) => {
|
||||||
|
const config = app.get(ConfigService);
|
||||||
|
const docConfig = new DocumentBuilder()
|
||||||
|
.setTitle(`${config.title}接口文档`)
|
||||||
|
.setVersion('1.0')
|
||||||
|
.setDescription('Openapi 3.0文档')
|
||||||
|
.setExternalDoc('JSON数据', `${config.apiDocPrefix}.json`)
|
||||||
|
.addTag('user', '用户管理')
|
||||||
|
.addTag('auth', '认证管理')
|
||||||
|
.addTag('role', '角色管理')
|
||||||
|
.addTag('permission', '权限管理')
|
||||||
|
.addTag('post', '文章管理')
|
||||||
|
.addTag('upload', '文件上传')
|
||||||
|
.build();
|
||||||
|
const options: SwaggerDocumentOptions = {
|
||||||
|
operationIdFactory(controllerKey, methodKey) {
|
||||||
|
return `${controllerKey}_${methodKey}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const document = addResponseWrapper(SwaggerModule.createDocument(app, docConfig, options));
|
||||||
|
SwaggerModule.setup(config.apiDocPrefix, app, document, {
|
||||||
|
jsonDocumentUrl: `${config.apiDocPrefix}.json`,
|
||||||
|
yamlDocumentUrl: `${config.apiDocPrefix}.yaml`,
|
||||||
|
customfavIcon: '/favicon.ico',
|
||||||
|
customSiteTitle: `接口文档 | ${config.subtitle}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { OpenAPIObject } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为所有接口添加统一的返回数据结构
|
||||||
|
* @param doc OPENAPI文档对象
|
||||||
|
* @example
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "code": 2000,
|
||||||
|
* "message": "请求成功",
|
||||||
|
* "data": []
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function addResponseWrapper(doc: OpenAPIObject) {
|
||||||
|
for (const path of Object.keys(doc.paths)) {
|
||||||
|
const pathItem = doc.paths[path];
|
||||||
|
if (!pathItem) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const method of Object.keys(pathItem)) {
|
||||||
|
const responses = doc.paths[path][method].responses;
|
||||||
|
if (!responses) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const status of Object.keys(responses)) {
|
||||||
|
const json = responses[status].content?.['application/json'];
|
||||||
|
if (!json) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const schema = json.schema;
|
||||||
|
json.schema = {
|
||||||
|
allOf: [
|
||||||
|
{
|
||||||
|
$ref: '#/components/schemas/Response',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
data: schema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.components.schemas.Response = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
code: {
|
||||||
|
type: 'integer',
|
||||||
|
description: '状态码',
|
||||||
|
example: 2000,
|
||||||
|
format: 'int32',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: 'string',
|
||||||
|
description: '提示信息',
|
||||||
|
example: '请求成功',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['code', 'message'],
|
||||||
|
};
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './validation.error';
|
export * from './validation.error';
|
||||||
export * from './validation.filter';
|
export * from './validation.filter';
|
||||||
export * from './validation.pipe';
|
export * from './validation.pipe';
|
||||||
|
export * from './validation.module';
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
|
import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { ResponseCode } from '../response';
|
import { ResponseCode } from '../response';
|
||||||
import { AppValidationError } from './validation.error';
|
import { AppValidationError } from './validation.error';
|
||||||
|
|
@ -11,6 +11,6 @@ export class ValidationExecptionFilter implements ExceptionFilter {
|
||||||
const code = ResponseCode.PARAM_ERROR;
|
const code = ResponseCode.PARAM_ERROR;
|
||||||
const message = exception.message;
|
const message = exception.message;
|
||||||
const data = exception.messages;
|
const data = exception.messages;
|
||||||
response.status(400).json({ code, message, data });
|
response.status(HttpStatus.BAD_REQUEST).json({ code, message, data });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { APP_FILTER, APP_PIPE } from '@nestjs/core';
|
||||||
|
import { validationPipeFactory } from './validation.pipe';
|
||||||
|
import { ValidationExecptionFilter } from './validation.filter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验模块
|
||||||
|
* @description 包含全局验证管道和全局验证异常过滤器
|
||||||
|
*/
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
/**
|
||||||
|
* 全局验证管道
|
||||||
|
* @description 校验和转换输入数据
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_PIPE,
|
||||||
|
useFactory: validationPipeFactory,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局验证异常过滤器
|
||||||
|
* @description 将验证异常统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: ValidationExecptionFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ValidationModule {}
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,19 @@
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
import { ConfigModule as _ConfigModule } from '@nestjs/config';
|
import { ConfigModule as _ConfigModule } from '@nestjs/config';
|
||||||
|
import { ConfigService } from './config.service';
|
||||||
|
|
||||||
export const ConfigModule = _ConfigModule.forRoot({
|
/**
|
||||||
envFilePath: ['.env.development', '.env.local', '.env'],
|
* 配置模块
|
||||||
isGlobal: true,
|
* @description 基于 `@nestjs/config` 封装,提供更便捷且类型安全的配置读取方式
|
||||||
});
|
*/
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
_ConfigModule.forRoot({
|
||||||
|
envFilePath: ['.env.development.locale', '.env.development', '.env.local', '.env'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [ConfigService],
|
||||||
|
exports: [ConfigService],
|
||||||
|
})
|
||||||
|
export class ConfigModule {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService as _ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ConfigService {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* `@nestjs/config` 的 ConfigService实例
|
||||||
|
*/
|
||||||
|
public config: _ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保留原有的get方法
|
||||||
|
*/
|
||||||
|
get(...args: [string, any]) {
|
||||||
|
return this.config.get(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
* @default 'Appnify'
|
||||||
|
*/
|
||||||
|
get title(): string {
|
||||||
|
return this.config.get('TITLE', 'Appnify');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用CORS(跨域)
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
get cors(): boolean {
|
||||||
|
return this.config.get('CORS', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 副标题
|
||||||
|
* @default 'Appnify'
|
||||||
|
*/
|
||||||
|
get subtitle(): string {
|
||||||
|
return this.config.get('SUBTITLE', 'Appnify');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API前缀
|
||||||
|
* @default '/api'
|
||||||
|
*/
|
||||||
|
get apiPrefix(): string {
|
||||||
|
return this.config.get('API_PREFIX', '/api');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API默认版本
|
||||||
|
* @default '1'
|
||||||
|
*/
|
||||||
|
get apiVersion(): string {
|
||||||
|
return this.config.get('API_VERSION', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API文档前缀
|
||||||
|
* @default '/openapi'
|
||||||
|
*/
|
||||||
|
get apiDocPrefix(): string {
|
||||||
|
const prefix = this.config.get('API_DOC_PREFIX', '/openapi');
|
||||||
|
return prefix.startsWith('/') ? prefix : `/${prefix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行端口
|
||||||
|
* @default 3030
|
||||||
|
*/
|
||||||
|
get port(): number {
|
||||||
|
return Number(this.config.get('SERVER_PORT', 3030));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行地址
|
||||||
|
* @default '0:0:0:0'
|
||||||
|
*/
|
||||||
|
get host(): string {
|
||||||
|
return this.config.get('SERVER_HOST', '0:0:0:0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库类型
|
||||||
|
* @default 'sqlite'
|
||||||
|
*/
|
||||||
|
get dbType(): 'sqlite' | 'mysql' | 'mongodb' {
|
||||||
|
return this.config.get('DB_TYPE', 'sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite数据库文件路径
|
||||||
|
* @default './content/db.sqlite'
|
||||||
|
*/
|
||||||
|
get dbSqlitePath(): string {
|
||||||
|
return this.config.get('DB_SQLITE_PATH', './content/db.sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件目录
|
||||||
|
* @default './content/uploads'
|
||||||
|
*/
|
||||||
|
get uploadDir(): string {
|
||||||
|
return this.config.get('UPLOAD_DIR', './content/uploads');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件URL前缀
|
||||||
|
* @default '/uploads'
|
||||||
|
*/
|
||||||
|
get uploadPrefix(): string {
|
||||||
|
return this.config.get('UPLOAD_URL', '/uploads');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静态文件目录
|
||||||
|
* @default './content/static'
|
||||||
|
*/
|
||||||
|
get staticDir(): string {
|
||||||
|
return this.config.get('STATIC_DIR', './content/static');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认页码
|
||||||
|
* @default 1
|
||||||
|
*/
|
||||||
|
get defaultPage(): number {
|
||||||
|
return Number(this.config.get('DEFAULT_PAGE_NUMBER', 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认分页大小
|
||||||
|
* @default 10
|
||||||
|
*/
|
||||||
|
get defaultPageSize(): number {
|
||||||
|
return Number(this.config.get('DEFAULT_PAGE_SIZE', 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT密钥
|
||||||
|
* @default 'todo'
|
||||||
|
*/
|
||||||
|
get jwtSecret(): string {
|
||||||
|
return this.config.get('JWT_SECRET', 'todo');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志保存目录
|
||||||
|
* @default './content/logs'
|
||||||
|
*/
|
||||||
|
get logDir(): string {
|
||||||
|
return this.config.get('LOG_DIR', './content/logs');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './config.module';
|
export * from './config.module';
|
||||||
export * from './config.interface';
|
export * from './config.service';
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/**
|
|
||||||
* 环境变量枚举
|
|
||||||
*/
|
|
||||||
export enum envKeys {
|
|
||||||
/**
|
|
||||||
* 服务器地址
|
|
||||||
*/
|
|
||||||
SERVER_HOST = 'SERVER_HOST',
|
|
||||||
/**
|
|
||||||
* 服务器端口
|
|
||||||
*/
|
|
||||||
SERVER_PORT = 'SERVER_PORT',
|
|
||||||
/**
|
|
||||||
* 上传文件夹
|
|
||||||
*/
|
|
||||||
UPLOAD_DIR = 'UPLOAD_DIR',
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './env';
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ConfigService } from '@/config';
|
||||||
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库模块
|
||||||
|
* @description 基于 `typeorm` 封装
|
||||||
|
*/
|
||||||
|
export const DatabaseModule = TypeOrmModule.forRootAsync({
|
||||||
|
useFactory: (config: ConfigService) => {
|
||||||
|
if (config.dbType === 'sqlite') {
|
||||||
|
return {
|
||||||
|
type: config.dbType,
|
||||||
|
database: config.dbSqlitePath,
|
||||||
|
synchronize: true,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (config.dbType === 'mysql') {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
|
import { CreateUsersTable1682693329275 } from '../migrations/1682693329275-CreateUsersTable';
|
||||||
|
import { MockPosts1685026010848 } from '../migrations/1685026010848-MockPosts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于生成迁移文件
|
||||||
|
*/
|
||||||
|
export default new DataSource({
|
||||||
|
type: 'sqlite',
|
||||||
|
database: 'database/db.sqlite',
|
||||||
|
logging: false,
|
||||||
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
|
entities: ['src/**/*.entity.ts'],
|
||||||
|
migrations: [CreateUsersTable1682693329275, MockPosts1685026010848],
|
||||||
|
});
|
||||||
|
|
@ -2,9 +2,10 @@ import { Exclude } from 'class-transformer';
|
||||||
import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础实体, 查询时默认按照id倒序
|
* 基础实体
|
||||||
|
* @description 所有实体都应该继承该类
|
||||||
*/
|
*/
|
||||||
@Entity({ orderBy: { id: 'DESC' } })
|
@Entity()
|
||||||
export class BaseEntity {
|
export class BaseEntity {
|
||||||
/**
|
/**
|
||||||
* 自增ID
|
* 自增ID
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './datasource';
|
||||||
|
export * from './entities/base';
|
||||||
|
export * from './database.module';
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { Global, Module } from '@nestjs/common';
|
|
||||||
import { LoggerService } from '../logger';
|
|
||||||
|
|
||||||
@Global()
|
|
||||||
@Module({
|
|
||||||
providers: [LoggerService],
|
|
||||||
})
|
|
||||||
export class BaseModule {}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { Inject } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { Config } from '@/config';
|
|
||||||
|
|
||||||
export class BaseService {
|
|
||||||
@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,2 +0,0 @@
|
||||||
export * from './all.filter';
|
|
||||||
export * from './http.filter';
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { Global, Module } from '@nestjs/common';
|
|
||||||
import { LoggerService } from './logger.service';
|
|
||||||
|
|
||||||
@Global()
|
|
||||||
@Module({
|
|
||||||
providers: [LoggerService],
|
|
||||||
exports: [LoggerService],
|
|
||||||
})
|
|
||||||
export class LoggerModule {}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './pagination';
|
|
||||||
export * from './pagination.dto';
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { Response, ResponseCode } from '../response';
|
|
||||||
|
|
||||||
interface Options {
|
|
||||||
page: number;
|
|
||||||
size: number;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
type WrapOptions<T> = {
|
|
||||||
page: number;
|
|
||||||
size: number;
|
|
||||||
total: number;
|
|
||||||
data: T[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultPage = 1;
|
|
||||||
|
|
||||||
export const defaultSize = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页工具类
|
|
||||||
*/
|
|
||||||
export class Pagination {
|
|
||||||
/**
|
|
||||||
* 包装响应结果
|
|
||||||
*/
|
|
||||||
static wrap<T>(options: WrapOptions<T>) {
|
|
||||||
const { page = defaultPage, size = defaultSize, total, data } = options;
|
|
||||||
return Response.create({
|
|
||||||
code: ResponseCode.SUCESS,
|
|
||||||
message: '请求成功',
|
|
||||||
data,
|
|
||||||
meta: { page, size, total },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 将分页参数转换为typeorm查询参数
|
|
||||||
*/
|
|
||||||
static optionize(options: Options) {
|
|
||||||
const { page = defaultPage, size: take = defaultSize, ...where } = options || {};
|
|
||||||
const skip = (page - 1) * take;
|
|
||||||
return { skip, take, where };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { ServeStaticModule as _ServeStaticModule } from '@nestjs/serve-static';
|
|
||||||
import { Config } from '@/config';
|
|
||||||
|
|
||||||
export const ServeStaticModule = _ServeStaticModule.forRootAsync({
|
|
||||||
useFactory: (configService: ConfigService<Config>) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
rootPath: configService.get<string>('UPLOAD_DIR', 'uploads'),
|
|
||||||
serveRoot: configService.get<string>('UPLOAD_URL', '/uploads'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rootPath: configService.get<string>('STATIC_DIR', 'public'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
inject: [ConfigService],
|
|
||||||
});
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
||||||
import { Config } from '@/config';
|
|
||||||
|
|
||||||
export const initSwagger = (app: INestApplication) => {
|
|
||||||
const configService = app.get(ConfigService<Config>);
|
|
||||||
const openapiUrl = configService.get<string>('SERVER_OPENAPI_URL', 'openapi');
|
|
||||||
const appTitle = configService.get<string>('APP_TITLE', 'Apptify');
|
|
||||||
const appSubtitle = configService.get<string>('APP_SUBTITLE', 'Apptify');
|
|
||||||
const config = new DocumentBuilder()
|
|
||||||
.setTitle(`${appTitle}接口文档`)
|
|
||||||
.setVersion('1.0')
|
|
||||||
.setDescription('Openapi 3.0文档')
|
|
||||||
.setExternalDoc('JSON数据', `${openapiUrl}.json`)
|
|
||||||
.addTag('user', '用户管理')
|
|
||||||
.addTag('auth', '认证管理')
|
|
||||||
.addTag('role', '角色管理')
|
|
||||||
.addTag('permission', '权限管理')
|
|
||||||
.addTag('post', '文章管理')
|
|
||||||
.addTag('upload', '文件上传')
|
|
||||||
.build();
|
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
|
||||||
SwaggerModule.setup(openapiUrl, app, document, {
|
|
||||||
jsonDocumentUrl: `${openapiUrl}.json`,
|
|
||||||
yamlDocumentUrl: `${openapiUrl}.yaml`,
|
|
||||||
customfavIcon: '/favicon.ico',
|
|
||||||
customSiteTitle: `接口文档 | ${appSubtitle}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
|
||||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
|
||||||
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
|
||||||
import { CreateUsersTable1682693329275 } from '../migrations/1682693329275-CreateUsersTable';
|
|
||||||
import { MockPosts1685026010848 } from '../migrations/1685026010848-MockPosts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基本配置
|
|
||||||
*/
|
|
||||||
export const baseConfig: DataSourceOptions = {
|
|
||||||
type: 'sqlite',
|
|
||||||
database: 'database/db.sqlite',
|
|
||||||
logging: false,
|
|
||||||
namingStrategy: new SnakeNamingStrategy(),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于运行时连接数据库
|
|
||||||
*/
|
|
||||||
export const ormConfig: TypeOrmModuleOptions = {
|
|
||||||
...baseConfig,
|
|
||||||
synchronize: true,
|
|
||||||
autoLoadEntities: true,
|
|
||||||
logging: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于生成迁移文件
|
|
||||||
*/
|
|
||||||
export const cliConfig: DataSourceOptions = {
|
|
||||||
...baseConfig,
|
|
||||||
entities: ['src/**/*.entity.ts'],
|
|
||||||
migrations: [CreateUsersTable1682693329275, MockPosts1685026010848],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于生成迁移文件
|
|
||||||
*/
|
|
||||||
export default new DataSource(cliConfig);
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { ormConfig } from './datasource';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
|
||||||
import { Config } from '@/config';
|
|
||||||
export * from './datasource';
|
|
||||||
export * from './entities/base';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接数据库
|
|
||||||
*/
|
|
||||||
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],
|
|
||||||
});
|
|
||||||
20
src/main.ts
20
src/main.ts
|
|
@ -1,17 +1,21 @@
|
||||||
import { VersioningType } from '@nestjs/common';
|
import { VersioningType } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { initSwagger } from '@/features/swagger';
|
import { initSwagger } from '@/common/swagger';
|
||||||
import { LoggerService } from '@/features/logger';
|
import { LoggerService } from '@/common/logger';
|
||||||
|
import { ConfigService } from '@/config';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const { SERVER_HOST, SERVER_PORT } = process.env;
|
|
||||||
/**
|
/**
|
||||||
* 创建应用
|
* 创建应用
|
||||||
*/
|
*/
|
||||||
const app = await NestFactory.create(AppModule, { bufferLogs: false });
|
const app = await NestFactory.create(AppModule, { bufferLogs: false });
|
||||||
/**
|
/**
|
||||||
* 使用全局日志
|
* 获取配置服务
|
||||||
|
*/
|
||||||
|
const config = app.get(ConfigService);
|
||||||
|
/**
|
||||||
|
* 获取日志服务
|
||||||
*/
|
*/
|
||||||
const logger = app.get(LoggerService);
|
const logger = app.get(LoggerService);
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,11 +29,11 @@ async function bootstrap() {
|
||||||
/**
|
/**
|
||||||
* API前缀
|
* API前缀
|
||||||
*/
|
*/
|
||||||
app.setGlobalPrefix('/api');
|
app.setGlobalPrefix(config.apiPrefix);
|
||||||
/**
|
/**
|
||||||
* 接口版本
|
* 接口版本
|
||||||
*/
|
*/
|
||||||
app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' });
|
app.enableVersioning({ type: VersioningType.URI, defaultVersion: config.apiVersion });
|
||||||
/**
|
/**
|
||||||
* 接口文档(swagger)
|
* 接口文档(swagger)
|
||||||
*/
|
*/
|
||||||
|
|
@ -37,7 +41,7 @@ async function bootstrap() {
|
||||||
/**
|
/**
|
||||||
* 监听端口
|
* 监听端口
|
||||||
*/
|
*/
|
||||||
await app.listen(SERVER_PORT, SERVER_HOST);
|
await app.listen(config.port, config.host);
|
||||||
/**
|
/**
|
||||||
* 输出项目运行URL
|
* 输出项目运行URL
|
||||||
*/
|
*/
|
||||||
|
|
@ -45,7 +49,7 @@ async function bootstrap() {
|
||||||
/**
|
/**
|
||||||
* 输出接口文档URL
|
* 输出接口文档URL
|
||||||
*/
|
*/
|
||||||
logger.log(`OpenapiDocs is running at ${await app.getUrl()}/openapi`, 'NestApplication');
|
logger.log(`OpenapiDocs is running at ${await app.getUrl()}${config.apiDocPrefix}`, 'NestApplication');
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,19 @@ 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 { JwtModule } from './jwt';
|
import { JwtGuard, JwtModule } from './jwt';
|
||||||
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
imports: [UserModule, JwtModule],
|
imports: [UserModule, JwtModule],
|
||||||
providers: [AuthService],
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: JwtGuard,
|
||||||
|
},
|
||||||
|
],
|
||||||
exports: [AuthService],
|
exports: [AuthService],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@ import { Reflector } from '@nestjs/core';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { PUBLICK_KEY } from './jwt-decorator';
|
import { PUBLICK_KEY } from './jwt-decorator';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtGuard implements CanActivate {
|
export class JwtGuard implements CanActivate {
|
||||||
constructor(private reflector: Reflector, private jwtService: JwtService, private configService: ConfigService) {}
|
constructor(private reflector: Reflector, private jwtService: JwtService, private config: ConfigService) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const request = context.switchToHttp().getRequest<Request>();
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
const token = this.extractTokenFromHeader(request);
|
const token = this.extractTokenFromHeader(request);
|
||||||
if (token) {
|
if (token) {
|
||||||
const secret = this.configService.get('JWT_SECRET');
|
const secret = this.config.jwtSecret;
|
||||||
const user = await this.jwtService.verifyAsync(token, { secret });
|
const user = await this.jwtService.verifyAsync(token, { secret });
|
||||||
request['user'] = user;
|
request['user'] = user;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { Config } from '@/config';
|
import { ConfigService } from '@/config';
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { JwtModule as _JwtModule } from '@nestjs/jwt';
|
import { JwtModule as _JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
export const JwtModule = _JwtModule.registerAsync({
|
export const JwtModule = _JwtModule.registerAsync({
|
||||||
useFactory: (configService: ConfigService<Config>) => {
|
useFactory: (config: ConfigService) => {
|
||||||
return {
|
return {
|
||||||
secret: configService.get('JWT_SECRET', 'todo'),
|
secret: config.jwtSecret,
|
||||||
signOptions: {
|
signOptions: {
|
||||||
expiresIn: '60000s',
|
expiresIn: '60000s',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseEntity } from '@/features/typeorm';
|
import { BaseEntity } from '@/database';
|
||||||
import { Role } from '@/modules/role/entities/role.entity';
|
import { Role } from '@/modules/role/entities/role.entity';
|
||||||
import { Column, Entity, ManyToMany } from 'typeorm';
|
import { Column, Entity, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,5 @@ export * from './entities/permission.entity';
|
||||||
export * from './permission.controller';
|
export * from './permission.controller';
|
||||||
export * from './permission.module';
|
export * from './permission.module';
|
||||||
export * from './permission.service';
|
export * from './permission.service';
|
||||||
|
export * from './permission.decorator';
|
||||||
|
export * from './permission.guard';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { PermissionService } from './permission.service';
|
||||||
import { CreatePermissionDto } from './dto/create-permission.dto';
|
import { CreatePermissionDto } from './dto/create-permission.dto';
|
||||||
import { UpdatePermissionDto } from './dto/update-permission.dto';
|
import { UpdatePermissionDto } from './dto/update-permission.dto';
|
||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { Respond } from '@/features/response';
|
import { Respond } from '@/common/response';
|
||||||
|
|
||||||
@ApiTags('permission')
|
@ApiTags('permission')
|
||||||
@Controller('permissions')
|
@Controller('permissions')
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,6 @@ export const enum PermissionEnum {
|
||||||
* @param permissions
|
* @param permissions
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function Permission(...permissions: PermissionEnum[]) {
|
export function NeedPermission(...permissions: PermissionEnum[]) {
|
||||||
return SetMetadata(PERMISSION_KEY, permissions);
|
return SetMetadata(PERMISSION_KEY, permissions);
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException, forwardRef } from '@nestjs/common';
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { PERMISSION_KEY } from './permission.decorator';
|
import { PERMISSION_KEY } from './permission.decorator';
|
||||||
import { UserService } from '@/modules/user';
|
import { UserService } from '@/modules/user';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PermissionGuard implements CanActivate {
|
export class PermissionGuard implements CanActivate {
|
||||||
constructor(private reflector: Reflector, private userService: UserService) {}
|
constructor(private reflector: Reflector, @Inject(forwardRef(() => UserService)) private userService: UserService) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const controller = context.getClass();
|
const controller = context.getClass();
|
||||||
|
|
@ -21,7 +21,6 @@ export class PermissionGuard implements CanActivate {
|
||||||
const userPermissions = await this.userService.findUserPermissions(user.id);
|
const userPermissions = await this.userService.findUserPermissions(user.id);
|
||||||
const hasPermission = permissions.every((permission) => userPermissions.includes(permission));
|
const hasPermission = permissions.every((permission) => userPermissions.includes(permission));
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
console.log(userPermissions, permissions);
|
|
||||||
throw new UnauthorizedException('权限不足');
|
throw new UnauthorizedException('权限不足');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module, forwardRef } from '@nestjs/common';
|
||||||
import { PermissionService } from './permission.service';
|
import { PermissionService } from './permission.service';
|
||||||
import { PermissionController } from './permission.controller';
|
import { PermissionController } from './permission.controller';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { Permission } from './entities/permission.entity';
|
import { Permission } from './entities/permission.entity';
|
||||||
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
|
import { PermissionGuard } from './permission.guard';
|
||||||
|
import { UserModule } from '../user';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Permission])],
|
imports: [TypeOrmModule.forFeature([Permission]), forwardRef(() => UserModule)],
|
||||||
controllers: [PermissionController],
|
controllers: [PermissionController],
|
||||||
providers: [PermissionService],
|
providers: [
|
||||||
|
PermissionService,
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: PermissionGuard,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class PermissionModule {}
|
export class PermissionModule {}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,13 @@ export class PermissionService {
|
||||||
return `This action returns a #${id} permission`;
|
return `This action returns a #${id} permission`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
async update(id: number, updatePermissionDto: UpdatePermissionDto) {
|
async update(id: number, updatePermissionDto: UpdatePermissionDto) {
|
||||||
await this.permissionRepository.update(id, updatePermissionDto);
|
await this.permissionRepository.update(id, updatePermissionDto);
|
||||||
|
=======
|
||||||
|
update(id: number, updatePermissionDto: UpdatePermissionDto) {
|
||||||
|
return this.permissionRepository.update(id, updatePermissionDto);
|
||||||
|
>>>>>>> 1a32173fc73bbb94906f9ffde5874d47f6dfdad8
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(id: number) {
|
remove(id: number) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseEntity } from '@/features/typeorm';
|
import { BaseEntity } from '@/database';
|
||||||
import { User } from 'src/modules/user';
|
import { User } from 'src/modules/user';
|
||||||
import { Column, Entity, ManyToMany } from 'typeorm';
|
import { Column, Entity, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,33 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
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 { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Post } from './entities/post.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PostService {
|
export class PostService {
|
||||||
create(createPostDto: CreatePostDto) {
|
constructor(@InjectRepository(Post) private postRepository: Repository<Post>) {}
|
||||||
return 'This action adds a new post';
|
|
||||||
|
async create(createPostDto: CreatePostDto) {
|
||||||
|
const post = this.postRepository.create(createPostDto);
|
||||||
|
await this.postRepository.save(post);
|
||||||
|
return post.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll() {
|
findAll() {
|
||||||
return `This action returns all post`;
|
return this.postRepository.findAndCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
findOne(id: number) {
|
findOne(id: number) {
|
||||||
return `This action returns a #${id} post`;
|
return this.postRepository.findOne({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
update(id: number, updatePostDto: UpdatePostDto) {
|
update(id: number, updatePostDto: UpdatePostDto) {
|
||||||
return `This action updates a #${id} post`;
|
return this.postRepository.update(id, updatePostDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(id: number) {
|
remove(id: number) {
|
||||||
return `This action removes a #${id} post`;
|
return this.postRepository.softDelete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseEntity } from '@/features/typeorm';
|
import { BaseEntity } from '@/database';
|
||||||
import { Permission } from '@/modules/permission/entities/permission.entity';
|
import { Permission } from '@/modules/permission/entities/permission.entity';
|
||||||
import { User } from 'src/modules/user';
|
import { User } from 'src/modules/user';
|
||||||
import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
|
import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ 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';
|
import { Respond } from '@/common/response';
|
||||||
|
|
||||||
@ApiTags('role')
|
@ApiTags('role')
|
||||||
@Controller('roles')
|
@Controller('roles')
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,16 @@ export class RoleService {
|
||||||
return `This action returns a #${id} role`;
|
return `This action returns a #${id} role`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
async update(id: number, updateRoleDto: UpdateRoleDto) {
|
async update(id: number, updateRoleDto: UpdateRoleDto) {
|
||||||
if (updateRoleDto.permissions) {
|
if (updateRoleDto.permissions) {
|
||||||
delete updateRoleDto.permissions;
|
delete updateRoleDto.permissions;
|
||||||
}
|
}
|
||||||
await this.roleRepository.update(id, updateRoleDto);
|
await this.roleRepository.update(id, updateRoleDto);
|
||||||
|
=======
|
||||||
|
update(id: number, updateRoleDto: UpdateRoleDto) {
|
||||||
|
return this.roleRepository.update(id, updateRoleDto);
|
||||||
|
>>>>>>> 1a32173fc73bbb94906f9ffde5874d47f6dfdad8
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(id: number) {
|
remove(id: number) {
|
||||||
|
|
|
||||||
|
|
@ -1 +1,6 @@
|
||||||
export class CreateUploadDto {}
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateUploadDto {
|
||||||
|
@ApiProperty({ type: 'string', format: 'binary' })
|
||||||
|
file: any;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseEntity } from '@/features/typeorm';
|
import { BaseEntity } from '@/database';
|
||||||
import { Column, Entity } from 'typeorm';
|
import { Column, Entity } from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { 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 { ApiBody, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
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';
|
import { Respond } from '@/common/response';
|
||||||
|
import { CreateUploadDto } from './dto/create-upload.dto';
|
||||||
|
|
||||||
@ApiTags('upload')
|
@ApiTags('upload')
|
||||||
@Controller('upload')
|
@Controller('upload')
|
||||||
|
|
@ -12,6 +13,8 @@ export class UploadController {
|
||||||
@Post()
|
@Post()
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
@ApiOperation({ summary: '上传文件', operationId: 'upload' })
|
@ApiOperation({ summary: '上传文件', operationId: 'upload' })
|
||||||
|
@ApiConsumes('multipart/form-data')
|
||||||
|
@ApiBody({ description: '文件', type: CreateUploadDto })
|
||||||
create(@UploadedFile() file: Express.Multer.File) {
|
create(@UploadedFile() file: Express.Multer.File) {
|
||||||
return this.uploadService.create(file);
|
return this.uploadService.create(file);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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 { MulterModule } from '@nestjs/platform-express';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@/config';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { join, parse } from 'path';
|
import { join, parse } from 'path';
|
||||||
import { diskStorage } from 'multer';
|
import { diskStorage } from 'multer';
|
||||||
|
|
@ -13,8 +13,8 @@ import { Upload } from './entities/upload.entity';
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Upload]),
|
TypeOrmModule.forFeature([Upload]),
|
||||||
MulterModule.registerAsync({
|
MulterModule.registerAsync({
|
||||||
useFactory: async (configService) => {
|
useFactory: async (config: ConfigService) => {
|
||||||
const dest = configService.get('UPLOAD_FOLDER', './public/upload');
|
const dest = config.uploadDir;
|
||||||
const storage = diskStorage({
|
const storage = diskStorage({
|
||||||
destination: join(dest),
|
destination: join(dest),
|
||||||
filename: (req, file, cb) => {
|
filename: (req, file, cb) => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import { PaginationDto } from '@/features/pagination';
|
import { PaginationDto } from '@/features/pagination';
|
||||||
import { IntersectionType } from '@nestjs/swagger';
|
import { IntersectionType } from '@nestjs/swagger';
|
||||||
import { IsOptional, IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
import { PaginationDto } from '@/common/response';
|
||||||
|
>>>>>>> 1a32173fc73bbb94906f9ffde5874d47f6dfdad8
|
||||||
|
|
||||||
export class FindUserDto extends IntersectionType(PaginationDto) {
|
export class FindUserDto extends IntersectionType(PaginationDto) {
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
* @example '绝弹'
|
||||||
|
*/
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './create-user.dto';
|
|
||||||
export * from './update-user.dto';
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './user.entity';
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ApiHideProperty } from '@nestjs/swagger';
|
import { ApiHideProperty } from '@nestjs/swagger';
|
||||||
import { Exclude } from 'class-transformer';
|
import { Exclude } from 'class-transformer';
|
||||||
import { BaseEntity } from '@/features/typeorm';
|
import { BaseEntity } from '@/database';
|
||||||
import { Post } from '@/modules/post';
|
import { Post } from '@/modules/post';
|
||||||
import { Role } from '@/modules/role';
|
import { Role } from '@/modules/role';
|
||||||
import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
|
import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
|
||||||
|
|
@ -61,6 +61,7 @@ export class User extends BaseEntity {
|
||||||
/**
|
/**
|
||||||
* 用户角色
|
* 用户角色
|
||||||
*/
|
*/
|
||||||
|
@ApiHideProperty()
|
||||||
@ManyToMany(() => Role, (role) => role.user)
|
@ManyToMany(() => Role, (role) => role.user)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
roles: Role[];
|
roles: Role[];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './dto';
|
export * from './dto/create-user.dto';
|
||||||
export * from './entities';
|
export * from './dto/update-user.dto';
|
||||||
|
export * from './entities/user.entity';
|
||||||
export * from './user.controller';
|
export * from './user.controller';
|
||||||
export * from './user.module';
|
export * from './user.module';
|
||||||
export * from './user.service';
|
export * from './user.service';
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,20 @@ import { BaseController } from '@/features/base';
|
||||||
import { Respond } from '@/features/response';
|
import { Respond } from '@/features/response';
|
||||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Version } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Version } from '@nestjs/common';
|
||||||
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
<<<<<<< HEAD
|
||||||
import { CreateUserDto, UpdateUserDto } from './dto';
|
import { CreateUserDto, UpdateUserDto } from './dto';
|
||||||
|
=======
|
||||||
|
import { Respond } from '@/common/response';
|
||||||
|
import { BaseController } from '@/common/base';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
>>>>>>> 1a32173fc73bbb94906f9ffde5874d47f6dfdad8
|
||||||
import { FindUserDto } from './dto/find-user.dto';
|
import { FindUserDto } from './dto/find-user.dto';
|
||||||
import { User } from './entities';
|
import { User } from './entities/user.entity';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
>>>>>>> 1a32173fc73bbb94906f9ffde5874d47f6dfdad8
|
||||||
|
|
||||||
@ApiTags('user')
|
@ApiTags('user')
|
||||||
@Controller('users')
|
@Controller('users')
|
||||||
|
|
@ -15,7 +25,7 @@ export class UserController extends BaseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '创建用户', operationId: 'addUser' })
|
@ApiOperation({ description: '创建用户', operationId: 'addUser' })
|
||||||
create(@Body() createUserDto: CreateUserDto) {
|
create(@Body() createUserDto: CreateUserDto) {
|
||||||
return this.userService.create(createUserDto);
|
return this.userService.create(createUserDto);
|
||||||
}
|
}
|
||||||
|
|
@ -23,26 +33,26 @@ export class UserController extends BaseController {
|
||||||
@Get()
|
@Get()
|
||||||
@Respond(Respond.PAGINATION)
|
@Respond(Respond.PAGINATION)
|
||||||
@ApiOkResponse({ isArray: true, type: User })
|
@ApiOkResponse({ isArray: true, type: User })
|
||||||
@ApiOperation({ summary: '批量查询', operationId: 'getUsers' })
|
@ApiOperation({ description: '批量查询用户', operationId: 'getUsers' })
|
||||||
async findMany(@Query() query: FindUserDto) {
|
async findMany(@Query() query: FindUserDto) {
|
||||||
return this.userService.findMany(query);
|
return this.userService.findMany(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Version('2')
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@ApiOperation({ summary: '查询用户', operationId: 'getUserv2' })
|
@Version('2')
|
||||||
|
@ApiOperation({ description: '查询用户', 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: 'setUser' })
|
@ApiOperation({ description: '更新用户', operationId: 'updateUser' })
|
||||||
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: 'delUser' })
|
@ApiOperation({ description: '删除用户', operationId: 'deleteUser' })
|
||||||
remove(@Param('id') id: number) {
|
remove(@Param('id') id: number) {
|
||||||
return this.userService.remove(+id);
|
return this.userService.remove(+id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { User } from './entities';
|
import { User } from './entities/user.entity';
|
||||||
import { UserController } from './user.controller';
|
import { UserController } from './user.controller';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Like, Repository } from 'typeorm';
|
import { Like, Repository } from 'typeorm';
|
||||||
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/user.entity';
|
||||||
import { BaseService } from '@/features/base';
|
import { BaseService } from '@/common/base';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService extends BaseService {
|
export class UserService extends BaseService {
|
||||||
|
|
@ -36,7 +37,6 @@ export class UserService extends BaseService {
|
||||||
where: {
|
where: {
|
||||||
nickname: nickname && Like(`%${nickname}%`),
|
nickname: nickname && Like(`%${nickname}%`),
|
||||||
},
|
},
|
||||||
relations: ['roles'],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,5 @@
|
||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
/**
|
|
||||||
* 运行端口
|
|
||||||
*/
|
|
||||||
SERVER_PORT: number;
|
|
||||||
/**
|
|
||||||
* 运行IP
|
|
||||||
*/
|
|
||||||
SERVER_HOST: string;
|
|
||||||
/**
|
|
||||||
* 项目根目录路径
|
|
||||||
*/
|
|
||||||
ROOT_PATH: string;
|
|
||||||
/**
|
|
||||||
* 环境变量
|
|
||||||
*/
|
|
||||||
NODE_ENV: 'development' | 'production' | 'test';
|
NODE_ENV: 'development' | 'production' | 'test';
|
||||||
/**
|
|
||||||
* 上传文件夹
|
|
||||||
*/
|
|
||||||
UPLOAD_DIR: string;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"target": "es2017",
|
"target": "ESNext",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue