feat: 添加字典管理

master
luoer 2023-10-27 17:33:39 +08:00
parent 7252bc3573
commit 3a29108da1
53 changed files with 549 additions and 352 deletions

Binary file not shown.

View File

@ -1,4 +1,4 @@
import { PaginationDto } from '@/common/response'; import { PaginationDto } from '@/middlewares/response';
import { IntersectionType } from '@nestjs/swagger'; import { IntersectionType } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator'; import { IsOptional, IsString } from 'class-validator';

View File

@ -1,7 +1,7 @@
import { BaseController } from '@/common/base'; import { BaseController } from '@/common/base';
import { Respond, RespondType } from '@/common/response'; import { Respond, RespondType } from '@/middlewares/response';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, ParseIntPipe } from '@nestjs/common'; import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { Create{{upcaseName name}}Dto } from './dto/create-{{fileName name}}.dto'; import { Create{{upcaseName name}}Dto } from './dto/create-{{fileName name}}.dto';
import { Find{{upcaseName name}}Dto } from './dto/find-{{fileName name}}.dto'; import { Find{{upcaseName name}}Dto } from './dto/find-{{fileName name}}.dto';
import { Update{{upcaseName name}}Dto } from './dto/update-{{fileName name}}.dto'; import { Update{{upcaseName name}}Dto } from './dto/update-{{fileName name}}.dto';
@ -31,19 +31,19 @@ export class {{upcaseName name}}Controller extends BaseController {
@Get(':id') @Get(':id')
@ApiOperation({ description: '获取{{cnName}}', operationId: 'get{{upcaseName name}}' }) @ApiOperation({ description: '获取{{cnName}}', operationId: 'get{{upcaseName name}}' })
get{{upcaseName name}}(id: number): Promise<{{upcaseName name}}> { get{{upcaseName name}}(@Param('id') id: number): Promise<{{upcaseName name}}> {
return this.{{lowcaseName name}}Service.findOne(id); return this.{{lowcaseName name}}Service.findOne(id);
} }
@Patch(':id') @Patch(':id')
@ApiOperation({ description: '更新{{cnName}}', operationId: 'set{{upcaseName name}}' }) @ApiOperation({ description: '更新{{cnName}}', operationId: 'set{{upcaseName name}}' })
update{{upcaseName name}}(id: number, @Body() update{{upcaseName name}}Dto: Update{{upcaseName name}}Dto) { update{{upcaseName name}}(@Param('id') id: number, @Body() update{{upcaseName name}}Dto: Update{{upcaseName name}}Dto) {
return this.{{lowcaseName name}}Service.update(+id, update{{upcaseName name}}Dto); return this.{{lowcaseName name}}Service.update(+id, update{{upcaseName name}}Dto);
} }
@Delete(':id') @Delete(':id')
@ApiOperation({ description: '删除{{cnName}}', operationId: 'del{{upcaseName name}}' }) @ApiOperation({ description: '删除{{cnName}}', operationId: 'del{{upcaseName name}}' })
del{{upcaseName name}}(id: number) { del{{upcaseName name}}(@Param('id') id: number) {
return this.{{lowcaseName name}}Service.remove(+id); return this.{{lowcaseName name}}Service.remove(+id);
} }
} }

View File

@ -10,12 +10,12 @@ import { LoggerModule } from '@/monitor/logger';
import { CacheModule } from '@/storage/cache'; import { CacheModule } from '@/storage/cache';
import { UploadModule } from '@/storage/file'; import { UploadModule } from '@/storage/file';
import { AuthModule } from '@/system/auth'; import { AuthModule } from '@/system/auth';
import { PermissionModule } from '@/system/permission';
import { RoleModule } from '@/system/role'; import { RoleModule } from '@/system/role';
import { UserModule } from '@/system/user'; import { UserModule } from '@/system/user';
import { ScanModule } from '@/utils/scan.module'; import { ScanModule } from '@/utils/scan.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { MenuModule } from './system/menu'; import { MenuModule } from './system/menu';
import { DictModule, DictTypeModule } from './system/dict';
@Module({ @Module({
imports: [ imports: [
@ -76,10 +76,6 @@ import { MenuModule } from './system/menu';
* *
*/ */
RoleModule, RoleModule,
/**
*
*/
PermissionModule,
/** /**
* *
@ -91,6 +87,8 @@ import { MenuModule } from './system/menu';
PostModule, PostModule,
ContentModule, ContentModule,
MenuModule, MenuModule,
DictTypeModule,
DictModule,
], ],
}) })
export class AppModule {} export class AppModule {}

View File

@ -1,3 +1,2 @@
export * from './mail.controller';
export * from './mail.module'; export * from './mail.module';
export * from './mail.service'; export * from './mail.service';

View File

@ -1,7 +0,0 @@
import { Controller } from '@nestjs/common';
import { MailService } from './mail.service';
@Controller('mail')
export class MailController {
constructor(private readonly mailService: MailService) {}
}

View File

@ -1,9 +1,9 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { MailService } from './mail.service'; import { MailService } from './mail.service';
import { MailController } from './mail.controller';
@Module({ @Module({
controllers: [MailController], controllers: [],
providers: [MailService], providers: [MailService],
exports: [MailService],
}) })
export class MailModule {} export class MailModule {}

View File

@ -1,3 +1,3 @@
export * from './datasource'; export * from './datasource';
export * from './entities/base'; export * from './entities/base.entity';
export * from './database.module'; export * from './database.module';

View File

@ -15,11 +15,11 @@ export class LoggerInterceptor implements NestInterceptor {
tap({ tap({
next: () => { next: () => {
const ms = Date.now() - now; const ms = Date.now() - now;
this.logger.log(`[Suuccess] ${method} ${url}(${ms} ms) +1`, scope); this.logger.log(`[成功] ${method} ${url}(${ms} ms) +1`, scope);
}, },
error: () => { error: () => {
const ms = Date.now() - now; const ms = Date.now() - now;
this.logger.log(`[Fail] ${method} ${url}(${ms} ms) +1`, scope); this.logger.error(`[失败] ${method} ${url}(${ms} ms) +1`, scope);
}, },
}), }),
); );

View File

@ -2,14 +2,21 @@ import { BaseEntity } from '@/database';
import { Column, Entity } from 'typeorm'; import { Column, Entity } from 'typeorm';
@Entity({ orderBy: { id: 'DESC' } }) @Entity({ orderBy: { id: 'DESC' } })
export class Upload extends BaseEntity { export class File extends BaseEntity {
/** /**
* *
* @example "xxx.jpg" * @example "头像.jpg"
*/ */
@Column({ comment: '文件名' }) @Column({ comment: '文件名' })
name: string; name: string;
/**
*
* @example '一段很长的描述'
*/
@Column({ comment: '描述' })
description: string;
/** /**
* *
* @example 1024 * @example 1024

View File

@ -34,9 +34,9 @@ export class UploadController {
} }
@Get('hash/:hash') @Get('hash/:hash')
@ApiOperation({ description: '查询文件是否已存在', operationId: 'getFileByHash' }) @ApiOperation({ description: '根据哈希查询', operationId: 'getFileByHash' })
isHashExists(@Param('hash') hash: string) { getByHash(@Param('hash') hash: string) {
return this.uploadService.isHashExists(hash); return this.uploadService.getByHash(hash);
} }
@Patch(':id') @Patch(':id')

View File

@ -5,7 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { existsSync, mkdirSync } from 'fs'; import { existsSync, mkdirSync } from 'fs';
import { diskStorage } from 'multer'; import { diskStorage } from 'multer';
import { extname, join } from 'path'; import { extname, join } from 'path';
import { Upload } from './entities/file.entity'; import { File } from './entities/file.entity';
import { UploadController } from './file.controller'; import { UploadController } from './file.controller';
import { UploadService } from './file.service'; import { UploadService } from './file.service';
import { dayjs } from '@/libraries'; import { dayjs } from '@/libraries';
@ -31,7 +31,7 @@ const MulteredModule = MulterModule.registerAsync({
}); });
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Upload]), MulteredModule], imports: [TypeOrmModule.forFeature([File]), MulteredModule],
controllers: [UploadController], controllers: [UploadController],
providers: [UploadService], providers: [UploadService],
}) })

View File

@ -3,41 +3,42 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { extname, relative, sep } from 'path'; import { extname, relative, sep } from 'path';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Upload } from './entities/file.entity'; import { File } from './entities/file.entity';
@Injectable() @Injectable()
export class UploadService extends BaseService { export class UploadService extends BaseService {
constructor(@InjectRepository(Upload) private readonly uploadRepository: Repository<Upload>) { constructor(@InjectRepository(File) private readonly repository: Repository<File>) {
super(); super();
} }
/** /**
* *
* @param file * @param uploadFile
* @returns * @returns
*/ */
async create(file: Express.Multer.File) { async create(uploadFile: Express.Multer.File) {
const path = relative(this.config.uploadDir, file.path).split(sep).join('/'); const { originalname: name, mimetype, size, path: hash } = uploadFile;
const uploadPrefix = this.config.uploadPrefix; const relativePath = relative(this.config.uploadDir, uploadFile.path).split(sep).join('/');
const uploadUrl = `${uploadPrefix}/${path}`; const path = `${this.config.uploadPrefix}/${relativePath}`;
const upload = this.uploadRepository.create({ const extension = extname(uploadFile.originalname);
name: file.originalname, const file = this.repository.create({
mimetype: file.mimetype, name,
size: file.size, mimetype,
hash: file.filename, size,
path: uploadUrl, hash,
extension: extname(file.originalname), path,
extension,
}); });
await this.uploadRepository.save(upload); await this.repository.save(file);
return upload; return file.id;
} }
findAll() { findAll() {
return this.uploadRepository.findAndCount(); return this.repository.findAndCount();
} }
findOne(id: number) { findOne(id: number) {
return this.uploadRepository.findOne({ where: { id } }); return this.repository.findOne({ where: { id } });
} }
update() { update() {
@ -49,12 +50,11 @@ export class UploadService extends BaseService {
* @param hash * @param hash
* @returns * @returns
*/ */
async isHashExists(hash: string) { async getByHash(hash: string) {
const count = await this.uploadRepository.count({ where: { hash } }); return this.repository.findOneBy({ hash });
return count > 0;
} }
remove(id: number) { remove(id: number) {
return this.uploadRepository.softDelete(id); return this.repository.softDelete(id);
} }
} }

View File

@ -11,7 +11,7 @@ import {
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthUserDto } from './dto/auth-user.dto'; import { AuthUserDto } from './dto/auth-user.dto';
import { Public } from './jwt'; import { Public } from './jwt/jwt-decorator';
import { LoginLogInterceptor } from '@/monitor/log'; import { LoginLogInterceptor } from '@/monitor/log';
import { Request } from 'express'; import { Request } from 'express';

View File

@ -2,9 +2,10 @@ 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 { JwtGuard, JwtModule } from './jwt'; import { JwtModule } from './jwt/jwt-module';
import { APP_GUARD } from '@nestjs/core'; import { APP_GUARD } from '@nestjs/core';
import { LogModule } from '@/monitor/log'; import { LogModule } from '@/monitor/log';
import { JwtGuard } from './jwt/jwt-guard';
@Module({ @Module({
imports: [UserModule, JwtModule, LogModule], imports: [UserModule, JwtModule, LogModule],

View File

@ -1,5 +1,7 @@
export * from './auth.controller'; 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/jwt-decorator';
export * from './jwt/jwt-guard';
export * from './jwt/jwt-module';
export * from './dto/auth-user.dto'; export * from './dto/auth-user.dto';

View File

@ -1,3 +0,0 @@
export * from './jwt-decorator';
export * from './jwt-guard';
export * from './jwt-module';

View File

@ -1,14 +0,0 @@
import { User } from '@/system/user';
import { OmitType } from '@nestjs/swagger';
export class LoginedUserVo extends OmitType(User, ['password', 'id'] as const) {
/**
* ID
*/
id: number;
/**
* 访
* @example 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjIsInVzZXJuYW1lIjoianVldGFuIiwiaWF0IjoxNjkxMTM5MjI3LCJleHAiOjE2OTExOTkyMjd9.6z7f-xfsHABbsyg401o2boKeqNQ1epPDYfEdavIcfYc'
*/
token: string;
}

View File

@ -0,0 +1,49 @@
import { BaseController } from '@/common/base';
import { Respond, RespondType } from '@/middlewares/response';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateDictDto } from './dto/create-dict.dto';
import { FindDictDto } from './dto/find-dict.dto';
import { UpdateDictDto } from './dto/update-dict.dto';
import { Dict } from './entities/dict.entity';
import { DictService } from './dict.service';
@ApiTags('dict')
@Controller('dicts')
export class DictController extends BaseController {
constructor(private dictService: DictService) {
super();
}
@Post()
@ApiOperation({ description: '新增字典', operationId: 'addDict' })
addDict(@Body() createDictDto: CreateDictDto) {
return this.dictService.create(createDictDto);
}
@Get()
@Respond(RespondType.PAGINATION)
@ApiOkResponse({ isArray: true, type: Dict })
@ApiOperation({ description: '查询字典', operationId: 'getDicts' })
getDicts(@Query() query: FindDictDto) {
return this.dictService.findMany(query);
}
@Get(':id')
@ApiOperation({ description: '获取字典', operationId: 'getDict' })
getDict(@Param('id') id: number): Promise<Dict> {
return this.dictService.findOne(id);
}
@Patch(':id')
@ApiOperation({ description: '更新字典', operationId: 'setDict' })
updateDict(@Param('id') id: number, @Body() updateDictDto: UpdateDictDto) {
return this.dictService.update(+id, updateDictDto);
}
@Delete(':id')
@ApiOperation({ description: '删除字典', operationId: 'delDict' })
delDict(@Param('id') id: number) {
return this.dictService.remove(+id);
}
}

View File

@ -0,0 +1,14 @@
import { Module, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Dict } from './entities/dict.entity';
import { DictController } from './dict.controller';
import { DictService } from './dict.service';
import { DictTypeModule } from '../dictType';
@Module({
imports: [TypeOrmModule.forFeature([Dict]), forwardRef(() => DictTypeModule)],
controllers: [DictController],
providers: [DictService],
exports: [DictService],
})
export class DictModule {}

View File

@ -0,0 +1,65 @@
import { BaseService } from '@/common/base';
import { Inject, Injectable, NotFoundException, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Like, Repository } from 'typeorm';
import { CreateDictDto } from './dto/create-dict.dto';
import { FindDictDto } from './dto/find-dict.dto';
import { UpdateDictDto } from './dto/update-dict.dto';
import { Dict } from './entities/dict.entity';
import { DictTypeService } from '../dictType';
@Injectable()
export class DictService extends BaseService {
constructor(
@InjectRepository(Dict) private dictRepository: Repository<Dict>,
@Inject(forwardRef(() => DictTypeService)) private dictTypeService: DictTypeService,
) {
super();
}
/**
*
*/
async create(createDictDto: CreateDictDto) {
const dict = this.dictRepository.create(createDictDto);
const { typeId } = createDictDto;
const type = await this.dictTypeService.findOne(typeId);
if (!type) {
throw new NotFoundException('字典类型不存在');
}
dict.type = type;
await this.dictRepository.save(dict);
return dict.id;
}
/**
* /
*/
async findMany(findDictdto: FindDictDto) {
const { page, size, typeId } = findDictdto;
const { skip, take } = this.formatPagination(page, size, true);
return this.dictRepository.findAndCount({ skip, take, where: { typeId } });
}
/**
* ID
*/
findOne(idOrOptions: number | Partial<Dict>) {
const where = typeof idOrOptions === 'number' ? { id: idOrOptions } : (idOrOptions as any);
return this.dictRepository.findOne({ where });
}
/**
* ID
*/
update(id: number, updateDictDto: UpdateDictDto) {
return this.dictRepository.update(id, updateDictDto);
}
/**
* ID()
*/
remove(id: number) {
return this.dictRepository.softDelete(id);
}
}

View File

@ -0,0 +1,32 @@
import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator';
export class CreateDictDto {
/**
*
* @example 1
*/
@IsNumber()
typeId: number;
/**
*
* @example '性别'
*/
@IsString()
name: string;
/**
*
* @example 'gender'
*/
@IsString()
code: string;
/**
*
* @examle '一段很长的描述'
*/
@IsOptional()
@IsString()
description: string;
}

View File

@ -0,0 +1,14 @@
import { PaginationDto } from '@/middlewares/response';
import { IntersectionType } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsInt, IsNumber, IsOptional, IsString } from 'class-validator';
export class FindDictDto extends IntersectionType(PaginationDto) {
/**
* ID
* @example 1
*/
@IsInt()
@Transform(({ value }) => Number(value))
typeId: number;
}

View File

@ -0,0 +1,8 @@
import { PartialType } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { CreateDictDto } from './create-dict.dto';
export class UpdateDictDto extends PartialType(CreateDictDto) {
@IsNumber()
id: number;
}

View File

@ -0,0 +1,48 @@
import { BaseEntity } from '@/database';
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
import { DictType } from '../../dictType';
@Entity({ orderBy: { id: 'DESC' } })
export class Dict extends BaseEntity {
/**
*
* @example '性别'
*/
@Column({ comment: '名称' })
name: string;
/**
*
* @example 'gender'
*/
@Column({ comment: '标识' })
code: string;
/**
*
* @example true
*/
@Column({ comment: '状态', default: true })
status: boolean;
/**
*
* @examle '一段很长的描述'
*/
@Column({ comment: '描述' })
description: string;
/**
*
*/
@ManyToOne(() => DictType, (dictType) => dictType.dicts)
@JoinColumn()
type: DictType;
/**
* ID
* @example 1
*/
@Column()
typeId: number;
}

View File

@ -0,0 +1,4 @@
export * from './entities/dict.entity';
export * from './dict.controller';
export * from './dict.module';
export * from './dict.service';

View File

@ -0,0 +1,49 @@
import { BaseController } from '@/common/base';
import { Respond, RespondType } from '@/middlewares/response';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateDictTypeDto } from './dto/create-dictType.dto';
import { FindDictTypeDto } from './dto/find-dictType.dto';
import { UpdateDictTypeDto } from './dto/update-dictType.dto';
import { DictType } from './entities/dictType.entity';
import { DictTypeService } from './dictType.service';
@ApiTags('dictType')
@Controller('dictTypes')
export class DictTypeController extends BaseController {
constructor(private dictTypeService: DictTypeService) {
super();
}
@Post()
@ApiOperation({ description: '新增字典类型', operationId: 'addDictType' })
addDictType(@Body() createDictTypeDto: CreateDictTypeDto) {
return this.dictTypeService.create(createDictTypeDto);
}
@Get()
@Respond(RespondType.PAGINATION)
@ApiOkResponse({ isArray: true, type: DictType })
@ApiOperation({ description: '查询字典类型', operationId: 'getDictTypes' })
getDictTypes(@Query() query: FindDictTypeDto) {
return this.dictTypeService.findMany(query);
}
@Get(':id')
@ApiOperation({ description: '获取字典类型', operationId: 'getDictType' })
getDictType(@Param('id') id: number): Promise<DictType> {
return this.dictTypeService.findOne(id);
}
@Patch(':id')
@ApiOperation({ description: '更新字典类型', operationId: 'setDictType' })
updateDictType(@Param('id') id: number, @Body() updateDictTypeDto: UpdateDictTypeDto) {
return this.dictTypeService.update(+id, updateDictTypeDto);
}
@Delete(':id')
@ApiOperation({ description: '删除字典类型', operationId: 'delDictType' })
delDictType(@Param('id') id: number) {
return this.dictTypeService.remove(+id);
}
}

View File

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DictType } from './entities/dictType.entity';
import { DictTypeController } from './dictType.controller';
import { DictTypeService } from './dictType.service';
@Module({
imports: [TypeOrmModule.forFeature([DictType])],
controllers: [DictTypeController],
providers: [DictTypeService],
exports: [DictTypeService],
})
export class DictTypeModule {}

View File

@ -0,0 +1,55 @@
import { BaseService } from '@/common/base';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Like, Repository } from 'typeorm';
import { CreateDictTypeDto } from './dto/create-dictType.dto';
import { FindDictTypeDto } from './dto/find-dictType.dto';
import { UpdateDictTypeDto } from './dto/update-dictType.dto';
import { DictType } from './entities/dictType.entity';
@Injectable()
export class DictTypeService extends BaseService {
constructor(@InjectRepository(DictType) private dictTypeRepository: Repository<DictType>) {
super();
}
/**
*
*/
async create(createDictTypeDto: CreateDictTypeDto) {
const dictType = this.dictTypeRepository.create(createDictTypeDto);
await this.dictTypeRepository.save(dictType);
return dictType.id;
}
/**
* /
*/
async findMany(findDictTypedto: FindDictTypeDto) {
const { page, size } = findDictTypedto;
const { skip, take } = this.formatPagination(page, size, true);
return this.dictTypeRepository.findAndCount({ skip, take });
}
/**
* ID
*/
findOne(idOrOptions: number | Partial<DictType>) {
const where = typeof idOrOptions === 'number' ? { id: idOrOptions } : (idOrOptions as any);
return this.dictTypeRepository.findOne({ where });
}
/**
* ID
*/
update(id: number, updateDictTypeDto: UpdateDictTypeDto) {
return this.dictTypeRepository.update(id, updateDictTypeDto);
}
/**
* ID()
*/
remove(id: number) {
return this.dictTypeRepository.softDelete(id);
}
}

View File

@ -0,0 +1,25 @@
import { IsOptional, IsString } from 'class-validator';
export class CreateDictTypeDto {
/**
*
* @example '性别'
*/
@IsString()
name: string;
/**
*
* @example 'gender'
*/
@IsString()
code: string;
/**
*
* @examle '一段很长的描述'
*/
@IsOptional()
@IsString()
description: string;
}

View File

@ -0,0 +1,13 @@
import { PaginationDto } from '@/middlewares/response';
import { IntersectionType } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator';
export class FindDictTypeDto extends IntersectionType(PaginationDto) {
/**
* (Swagger)
* @example '示例值'
*/
@IsOptional()
@IsString()
demo?: string;
}

View File

@ -0,0 +1,8 @@
import { PartialType } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { CreateDictTypeDto } from './create-dictType.dto';
export class UpdateDictTypeDto extends PartialType(CreateDictTypeDto) {
@IsNumber()
id: number;
}

View File

@ -0,0 +1,40 @@
import { BaseEntity } from '@/database';
import { Column, Entity, OneToMany } from 'typeorm';
import { Dict } from '../../data';
@Entity({ orderBy: { id: 'DESC' } })
export class DictType extends BaseEntity {
/**
*
* @example '性别'
*/
@Column({ comment: '名称' })
name: string;
/**
*
* @example 'gender'
*/
@Column({ comment: '标识' })
code: string;
/**
*
* @example true
*/
@Column({ comment: '状态', default: true })
status: boolean;
/**
*
* @examle '一段很长的描述'
*/
@Column({ comment: '描述' })
description: string;
/**
*
*/
@OneToMany(() => Dict, (dict) => dict.type)
dicts: Dict[];
}

View File

@ -0,0 +1,4 @@
export * from './entities/dictType.entity';
export * from './dictType.controller';
export * from './dictType.module';
export * from './dictType.service';

2
src/system/dict/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './dictType';
export * from './data';

View File

@ -1,5 +1,4 @@
import { PartialType } from '@nestjs/swagger'; import { PartialType } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { CreateMenuDto } from './create-menu.dto'; import { CreateMenuDto } from './create-menu.dto';
export class UpdateMenuDto extends PartialType(CreateMenuDto) {} export class UpdateMenuDto extends PartialType(CreateMenuDto) {}

View File

@ -42,17 +42,28 @@ export class Menu extends BaseEntity {
@Column({ comment: '类型(1: 目录, 2: 页面, 3: 按钮)' }) @Column({ comment: '类型(1: 目录, 2: 页面, 3: 按钮)' })
type: number; type: number;
/**
*
*/
@ApiHideProperty() @ApiHideProperty()
@TreeParent() @TreeParent()
parent: Menu; parent: Menu;
/**
* ID
*/
@Column({ comment: '父级ID', nullable: true }) @Column({ comment: '父级ID', nullable: true })
parentId: number; parentId: number;
@ApiHideProperty() /**
*
*/
@TreeChildren() @TreeChildren()
children: Menu[]; children: Menu[];
/**
*
*/
@ApiHideProperty() @ApiHideProperty()
@ManyToMany(() => Role, (role) => role.menus) @ManyToMany(() => Role, (role) => role.menus)
roles: Role[]; roles: Role[];

View File

@ -1,22 +0,0 @@
import { IsOptional, IsString } from 'class-validator';
export class CreatePermissionDto {
/**
*
* @example
*/
@IsString()
name: string;
/**
*
* @example permission:permission
*/
@IsString()
slug: string;
/**
*
*/
@IsOptional()
@IsString()
description?: string;
}

View File

@ -1,8 +0,0 @@
import { PartialType } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { CreatePermissionDto } from './create-permission.dto';
export class UpdatePermissionDto extends PartialType(CreatePermissionDto) {
@IsNumber()
id: number;
}

View File

@ -1,46 +0,0 @@
import { BaseEntity } from '@/database';
import { Role } from '@/system/role/entities/role.entity';
import { Column, Entity, ManyToMany } from 'typeorm';
enum PermissionType {
Menu = 'menu',
Api = 'api',
}
@Entity({ orderBy: { id: 'DESC' } })
export class Permission extends BaseEntity {
/**
*
* @example '文章列表'
*/
@Column({ comment: '权限名称' })
name: string;
/**
*
* @example 'post:list'
*/
@Column({ comment: '权限标识' })
slug: string;
/**
*
* @example 'menu'
*/
@Column({ nullable: true })
type: PermissionType;
/**
*
* @example '文章列表'
*/
@Column({ comment: '权限描述', nullable: true })
description: string;
/**
*
* @example {}
*/
@ManyToMany(() => Role, (role) => role.permissions)
roles: Role[];
}

View File

@ -1,8 +0,0 @@
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';
export * from './permission.decorator';
export * from './permission.guard';

View File

@ -1,43 +0,0 @@
import { Respond, RespondType } from '@/middlewares/response';
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreatePermissionDto } from './dto/create-permission.dto';
import { UpdatePermissionDto } from './dto/update-permission.dto';
import { PermissionService } from './permission.service';
@ApiTags('permission')
@Controller('permissions')
export class PermissionController {
constructor(private readonly permissionService: PermissionService) {}
@Post()
@ApiOperation({ description: '创建权限', operationId: 'addPermission' })
create(@Body() createPermissionDto: CreatePermissionDto) {
return this.permissionService.create(createPermissionDto);
}
@Get()
@Respond(RespondType.PAGINATION)
@ApiOperation({ description: '批量查询权限', operationId: 'getPermissions' })
findAll() {
return this.permissionService.findAll();
}
@Get(':id')
@ApiOperation({ description: '查询权限', operationId: 'getPermission' })
findOne(@Param('id') id: string) {
return this.permissionService.findOne(+id);
}
@Patch(':id')
@ApiOperation({ description: '更新权限', operationId: 'setPermission' })
update(@Param('id') id: string, @Body() updatePermissionDto: UpdatePermissionDto) {
return this.permissionService.update(+id, updatePermissionDto);
}
@Delete(':id')
@ApiOperation({ description: '删除权限', operationId: 'delPermission' })
remove(@Param('id') id: string) {
return this.permissionService.remove(+id);
}
}

View File

@ -1,34 +0,0 @@
import { SetMetadata } from '@nestjs/common';
export const PERMISSION_KEY = 'APP:PERMISSION';
/**
*
*/
export const enum PermissionEnum {
/**
*
*/
CREATE = 'create',
/**
*
*/
READ = 'read',
/**
*
*/
UPDATE = 'update',
/**
*
*/
DELETE = 'delete',
}
/**
*
* @param permissions
* @returns
*/
export function PermissionWith(...permissions: string[]) {
return SetMetadata(PERMISSION_KEY, permissions);
}

View File

@ -1,28 +0,0 @@
import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException, forwardRef } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PERMISSION_KEY } from './permission.decorator';
import { UserService } from '@/system/user';
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(private reflector: Reflector, @Inject(forwardRef(() => UserService)) 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) {
throw new UnauthorizedException('权限不足');
}
return true;
}
}

View File

@ -1,21 +0,0 @@
import { Module, forwardRef } from '@nestjs/common';
import { PermissionService } from './permission.service';
import { PermissionController } from './permission.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Permission } from './entities/permission.entity';
import { APP_GUARD } from '@nestjs/core';
import { PermissionGuard } from './permission.guard';
import { UserModule } from '../user';
@Module({
imports: [TypeOrmModule.forFeature([Permission]), forwardRef(() => UserModule)],
controllers: [PermissionController],
providers: [
PermissionService,
{
provide: APP_GUARD,
useClass: PermissionGuard,
},
],
})
export class PermissionModule {}

View File

@ -1,33 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreatePermissionDto } from './dto/create-permission.dto';
import { UpdatePermissionDto } from './dto/update-permission.dto';
import { Permission } from './entities/permission.entity';
@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.permissionRepository.update(id, updatePermissionDto);
}
remove(id: number) {
return `This action removes a #${id} permission`;
}
}

View File

@ -1,18 +1,33 @@
import { Permission } from '@/system/permission/entities/permission.entity';
import { IsInt, IsOptional, IsString } from 'class-validator'; import { IsInt, IsOptional, IsString } from 'class-validator';
export class CreateRoleDto { export class CreateRoleDto {
/**
*
* @example '管理员'
*/
@IsString() @IsString()
name: string; name: string;
/**
*
* @example 'admin'
*/
@IsString() @IsString()
slug: string; slug: string;
@IsString() /**
*
* @example '一段很长的描述'
*/
@IsOptional() @IsOptional()
@IsString()
description?: string; description?: string;
/**
* ID
* @example [1]
*/
@IsOptional() @IsOptional()
@IsInt({ each: true }) @IsInt({ each: true })
permissions?: Permission[]; menuIds: number[];
} }

View File

@ -1,9 +1,4 @@
import { PartialType } from '@nestjs/swagger'; import { PartialType } from '@nestjs/swagger';
import { CreateRoleDto } from './create-role.dto'; import { CreateRoleDto } from './create-role.dto';
import { IsInt, IsOptional } from 'class-validator';
export class UpdateRoleDto extends PartialType(CreateRoleDto) { export class UpdateRoleDto extends PartialType(CreateRoleDto) {}
@IsOptional()
@IsInt({ each: true })
permissionIds?: number[];
}

View File

@ -1,6 +1,5 @@
import { BaseEntity } from '@/database'; import { BaseEntity } from '@/database';
import { Menu } from '@/system/menu'; import { Menu } from '@/system/menu';
import { Permission } from '@/system/permission/entities/permission.entity';
import { User } from '@/system/user'; import { User } from '@/system/user';
import { ApiHideProperty } from '@nestjs/swagger'; import { ApiHideProperty } from '@nestjs/swagger';
import { Column, Entity, JoinTable, ManyToMany, RelationId } from 'typeorm'; import { Column, Entity, JoinTable, ManyToMany, RelationId } from 'typeorm';
@ -28,20 +27,23 @@ export class Role extends BaseEntity {
@Column({ comment: '角色描述', nullable: true }) @Column({ comment: '角色描述', nullable: true })
description: string; description: string;
/**
*
*/
@ApiHideProperty() @ApiHideProperty()
@ManyToMany(() => User, (user) => user.roles) @ManyToMany(() => User, (user) => user.roles)
user: User; user: User;
@ApiHideProperty() /**
@JoinTable() *
@ManyToMany(() => Permission, (permission) => permission.roles) */
permissions: Permission[];
@ApiHideProperty()
@RelationId('permissions')
permissionIds: number[];
@ApiHideProperty() @ApiHideProperty()
@ManyToMany(() => Menu, (menu) => menu.roles) @ManyToMany(() => Menu, (menu) => menu.roles)
menus: Menu[]; menus: Menu[];
/**
* ID
*/
@RelationId('menus')
menuIds: number[];
} }

View File

@ -11,9 +11,6 @@ export class RoleService {
async create(createRoleDto: CreateRoleDto) { async create(createRoleDto: CreateRoleDto) {
const role = this.roleRepository.create(createRoleDto); const role = this.roleRepository.create(createRoleDto);
if (createRoleDto.permissions) {
role.permissions = createRoleDto.permissions.map((id) => ({ id })) as any;
}
await this.roleRepository.save(role); await this.roleRepository.save(role);
return role.id; return role.id;
} }
@ -31,11 +28,6 @@ export class RoleService {
if (!role) { if (!role) {
throw new NotFoundException('角色不存在'); throw new NotFoundException('角色不存在');
} }
if (updateRoleDto.permissionIds) {
const permissions = updateRoleDto.permissionIds.map((id) => ({ id }));
await this.roleRepository.save({ id, permissions });
delete updateRoleDto.permissionIds;
}
return this.roleRepository.update(id, updateRoleDto); return this.roleRepository.update(id, updateRoleDto);
} }

View File

@ -60,7 +60,7 @@ export class User extends BaseEntity {
email: string; email: string;
/** /**
* *
*/ */
@ApiHideProperty() @ApiHideProperty()
@ManyToMany(() => Post, (post) => post.author) @ManyToMany(() => Post, (post) => post.author)
@ -68,7 +68,7 @@ export class User extends BaseEntity {
posts: Post[]; posts: Post[];
/** /**
* *
*/ */
@ApiHideProperty() @ApiHideProperty()
@ManyToMany(() => Role, (role) => role.user, { cascade: true }) @ManyToMany(() => Role, (role) => role.user, { cascade: true })
@ -76,7 +76,7 @@ export class User extends BaseEntity {
roles: Role[]; roles: Role[];
/** /**
* ID * ID
*/ */
@RelationId('roles') @RelationId('roles')
roleIds: number[]; roleIds: number[];

View File

@ -88,11 +88,11 @@ export class UserService extends BaseService {
async findUserPermissions(id: number) { async findUserPermissions(id: number) {
const user = await this.userRepository.findOne({ const user = await this.userRepository.findOne({
where: { id }, where: { id },
relations: ['roles', 'roles.permissions'], relations: ['roles'],
}); });
if (user) { if (user) {
const permissions = user.roles.flatMap((role) => role.permissions); const permissions = user.roles.flatMap((role) => role.menuIds);
return [...new Set(permissions.map((i) => i.slug))]; return [...new Set(permissions.map((i) => i))];
} }
return []; return [];
} }