feat: 补齐用户管理与接口文档能力
This commit is contained in:
parent
29663ef369
commit
e60e98bf49
@ -26,6 +26,7 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kwitsukasa/knife4j-swagger-vue3": "0.1.2",
|
||||
"@nestjs/common": "^9.4.3",
|
||||
"@nestjs/config": "^2.3.4",
|
||||
"@nestjs/core": "^9.4.3",
|
||||
@ -37,7 +38,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"mqtt": "^5.15.1",
|
||||
"mysql2": "^3.22.3",
|
||||
"nestjs-knife4j-plus": "^1.0.9",
|
||||
"nestjs-minio-client": "^2.2.0",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"rxjs": "^7.8.2",
|
||||
|
||||
@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@kwitsukasa/knife4j-swagger-vue3':
|
||||
specifier: 0.1.2
|
||||
version: 0.1.2(@nestjs/common@9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2))
|
||||
'@nestjs/common':
|
||||
specifier: ^9.4.3
|
||||
version: 9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2)
|
||||
@ -41,9 +44,6 @@ importers:
|
||||
mysql2:
|
||||
specifier: ^3.22.3
|
||||
version: 3.22.3(@types/node@18.11.18)
|
||||
nestjs-knife4j-plus:
|
||||
specifier: ^1.0.9
|
||||
version: 1.0.9(@nestjs/common@9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2))(express@4.18.2)
|
||||
nestjs-minio-client:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(@nestjs/common@9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@9.4.3)
|
||||
@ -554,6 +554,11 @@ packages:
|
||||
'@js-joda/core@5.7.0':
|
||||
resolution: {integrity: sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg==}
|
||||
|
||||
'@kwitsukasa/knife4j-swagger-vue3@0.1.2':
|
||||
resolution: {integrity: sha512-OsVKUo/URe1zONjW4ZmxXIdRiVHLNufkjYYDi1DuZqXm0a743NlezAAb12wwOajwkyyUNCCgs6NP5x6WkK8NEw==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': '*'
|
||||
|
||||
'@lukeed/csprng@1.1.0':
|
||||
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2664,19 +2669,6 @@ packages:
|
||||
neo-async@2.6.2:
|
||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
|
||||
nestjs-knife4j-plus@1.0.9:
|
||||
resolution: {integrity: sha512-MgMrfXgJfRgwW43BbMAKvInxluuvdNjSiYmkBvQvUawJMwn6+Op4W8/iYG1Q7u4D7mefWtM4zwVA7gBHErWCzg==}
|
||||
peerDependencies:
|
||||
'@fastify/static': '*'
|
||||
'@nestjs/common': '*'
|
||||
express: '*'
|
||||
fastify: '*'
|
||||
peerDependenciesMeta:
|
||||
'@fastify/static':
|
||||
optional: true
|
||||
fastify:
|
||||
optional: true
|
||||
|
||||
nestjs-minio-client@2.2.0:
|
||||
resolution: {integrity: sha512-mz1vfJq/7YfSyVCIeZwOCfIfBz+msI9QynHS2QGO9GB+tVNnQOYta8PxFsH9tMxN7gNrjrf5jXsEIpgBB1oTeA==}
|
||||
peerDependencies:
|
||||
@ -4422,6 +4414,10 @@ snapshots:
|
||||
'@js-joda/core@5.7.0':
|
||||
optional: true
|
||||
|
||||
'@kwitsukasa/knife4j-swagger-vue3@0.1.2(@nestjs/common@9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2))':
|
||||
dependencies:
|
||||
'@nestjs/common': 9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2)
|
||||
|
||||
'@lukeed/csprng@1.1.0': {}
|
||||
|
||||
'@microsoft/tsdoc@0.15.1': {}
|
||||
@ -7074,11 +7070,6 @@ snapshots:
|
||||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
nestjs-knife4j-plus@1.0.9(@nestjs/common@9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2))(express@4.18.2):
|
||||
dependencies:
|
||||
'@nestjs/common': 9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2)
|
||||
express: 4.18.2
|
||||
|
||||
nestjs-minio-client@2.2.0(@nestjs/common@9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@9.4.3):
|
||||
dependencies:
|
||||
'@nestjs/common': 9.4.3(reflect-metadata@0.1.14)(rxjs@7.8.2)
|
||||
|
||||
@ -45,6 +45,7 @@ CREATE TABLE IF NOT EXISTS `admin_user` (
|
||||
`username` varchar(255) NOT NULL,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`real_name` varchar(255) NOT NULL,
|
||||
`dept_id` bigint DEFAULT NULL,
|
||||
`home_path` varchar(255) NOT NULL DEFAULT '',
|
||||
`timezone` varchar(255) NOT NULL DEFAULT 'Asia/Shanghai',
|
||||
`status` int NOT NULL DEFAULT 1,
|
||||
@ -52,7 +53,8 @@ CREATE TABLE IF NOT EXISTS `admin_user` (
|
||||
`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_admin_user_username` (`username`)
|
||||
UNIQUE KEY `uk_admin_user_username` (`username`),
|
||||
KEY `idx_admin_user_dept_id` (`dept_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `admin_dept` (
|
||||
@ -68,6 +70,38 @@ CREATE TABLE IF NOT EXISTS `admin_dept` (
|
||||
KEY `idx_admin_dept_pid` (`pid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
SET @admin_user_dept_id_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'admin_user'
|
||||
AND COLUMN_NAME = 'dept_id'
|
||||
);
|
||||
SET @admin_user_dept_id_sql := IF(
|
||||
@admin_user_dept_id_exists = 0,
|
||||
'ALTER TABLE `admin_user` ADD COLUMN `dept_id` bigint NULL AFTER `real_name`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE admin_user_dept_id_stmt FROM @admin_user_dept_id_sql;
|
||||
EXECUTE admin_user_dept_id_stmt;
|
||||
DEALLOCATE PREPARE admin_user_dept_id_stmt;
|
||||
|
||||
SET @admin_user_dept_idx_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'admin_user'
|
||||
AND INDEX_NAME = 'idx_admin_user_dept_id'
|
||||
);
|
||||
SET @admin_user_dept_idx_sql := IF(
|
||||
@admin_user_dept_idx_exists = 0,
|
||||
'ALTER TABLE `admin_user` ADD INDEX `idx_admin_user_dept_id` (`dept_id`)',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE admin_user_dept_idx_stmt FROM @admin_user_dept_idx_sql;
|
||||
EXECUTE admin_user_dept_idx_stmt;
|
||||
DEALLOCATE PREPARE admin_user_dept_idx_stmt;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `admin_dict` (
|
||||
`id` bigint NOT NULL,
|
||||
`dict_code` varchar(255) NOT NULL,
|
||||
@ -125,19 +159,23 @@ VALUES
|
||||
(2041700000000100101, 2041700000000100001, 'Analytics', '/analytics', '/dashboard/analytics/index', NULL, NULL, 'menu', '{"affixTab":true,"title":"page.dashboard.analytics"}', 1, 0),
|
||||
(2041700000000100102, 2041700000000100001, 'Workspace', '/workspace', '/dashboard/workspace/index', NULL, NULL, 'menu', '{"icon":"carbon:workspace","title":"page.dashboard.workspace"}', 1, 0),
|
||||
(2041700000000100002, 0, 'System', '/system', NULL, NULL, NULL, 'catalog', '{"badge":"new","badgeType":"normal","badgeVariants":"primary","icon":"carbon:settings","order":9997,"title":"system.title"}', 1, 9997),
|
||||
(2041700000000100200, 2041700000000100002, 'SystemRole', '/system/role', '/system/role/list', NULL, 'System:Role:List', 'menu', '{"icon":"carbon:user-role","title":"system.role.title"}', 1, 0),
|
||||
(2041700000000100199, 2041700000000100002, 'SystemUser', '/system/user', '/system/user/list', NULL, 'System:User:List', 'menu', '{"icon":"carbon:user-profile","title":"system.user.title"}', 1, 0),
|
||||
(2041700000000120191, 2041700000000100199, 'SystemUserCreate', NULL, NULL, NULL, 'System:User:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120192, 2041700000000100199, 'SystemUserEdit', NULL, NULL, NULL, 'System:User:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120193, 2041700000000100199, 'SystemUserDelete', NULL, NULL, NULL, 'System:User:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000100200, 2041700000000100002, 'SystemRole', '/system/role', '/system/role/list', NULL, 'System:Role:List', 'menu', '{"icon":"carbon:user-role","title":"system.role.title"}', 1, 1),
|
||||
(2041700000000120001, 2041700000000100200, 'SystemRoleCreate', NULL, NULL, NULL, 'System:Role:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120002, 2041700000000100200, 'SystemRoleEdit', NULL, NULL, NULL, 'System:Role:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120003, 2041700000000100200, 'SystemRoleDelete', NULL, NULL, NULL, 'System:Role:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000100201, 2041700000000100002, 'SystemMenu', '/system/menu', '/system/menu/list', NULL, 'System:Menu:List', 'menu', '{"icon":"carbon:menu","title":"system.menu.title"}', 1, 0),
|
||||
(2041700000000100201, 2041700000000100002, 'SystemMenu', '/system/menu', '/system/menu/list', NULL, 'System:Menu:List', 'menu', '{"icon":"carbon:menu","title":"system.menu.title"}', 1, 2),
|
||||
(2041700000000120101, 2041700000000100201, 'SystemMenuCreate', NULL, NULL, NULL, 'System:Menu:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120102, 2041700000000100201, 'SystemMenuEdit', NULL, NULL, NULL, 'System:Menu:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120103, 2041700000000100201, 'SystemMenuDelete', NULL, NULL, NULL, 'System:Menu:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000100202, 2041700000000100002, 'SystemDept', '/system/dept', '/system/dept/list', NULL, 'System:Dept:List', 'menu', '{"icon":"carbon:container-services","title":"system.dept.title"}', 1, 0),
|
||||
(2041700000000100202, 2041700000000100002, 'SystemDept', '/system/dept', '/system/dept/list', NULL, 'System:Dept:List', 'menu', '{"icon":"carbon:container-services","title":"system.dept.title"}', 1, 3),
|
||||
(2041700000000120201, 2041700000000100202, 'SystemDeptCreate', NULL, NULL, NULL, 'System:Dept:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120202, 2041700000000100202, 'SystemDeptEdit', NULL, NULL, NULL, 'System:Dept:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120203, 2041700000000100202, 'SystemDeptDelete', NULL, NULL, NULL, 'System:Dept:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000100203, 2041700000000100002, 'SystemKtTableDemo', '/system/ktTableDemo', '/system/ktTableDemo/list', NULL, 'System:KtTableDemo:List', 'menu', '{"icon":"lucide:table-2","title":"system.ktTableDemo.title"}', 1, 3),
|
||||
(2041700000000100203, 2041700000000100002, 'SystemKtTableDemo', '/system/ktTableDemo', '/system/ktTableDemo/list', NULL, 'System:KtTableDemo:List', 'menu', '{"icon":"lucide:table-2","title":"system.ktTableDemo.title"}', 1, 4),
|
||||
(2041700000000120204, 2041700000000100203, 'SystemKtTableDemoCreate', NULL, NULL, NULL, 'System:KtTableDemo:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120205, 2041700000000100203, 'SystemKtTableDemoEdit', NULL, NULL, NULL, 'System:KtTableDemo:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120206, 2041700000000100203, 'SystemKtTableDemoDelete', NULL, NULL, NULL, 'System:KtTableDemo:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
@ -181,14 +219,15 @@ ON DUPLICATE KEY UPDATE
|
||||
`status` = VALUES(`status`),
|
||||
`is_deleted` = 0;
|
||||
|
||||
INSERT INTO `admin_user` (`id`, `username`, `password`, `real_name`, `home_path`, `timezone`, `status`)
|
||||
INSERT INTO `admin_user` (`id`, `username`, `password`, `real_name`, `dept_id`, `home_path`, `timezone`, `status`)
|
||||
VALUES
|
||||
(2041700000000000001, 'vben', '123456', 'Vben', '/workspace', 'Asia/Shanghai', 1),
|
||||
(2041700000000000002, 'admin', '123456', 'Admin', '/workspace', 'Asia/Shanghai', 1),
|
||||
(2041700000000000003, 'jack', '123456', 'Jack', '/analytics', 'Asia/Shanghai', 1)
|
||||
(2041700000000000001, 'vben', '123456', 'Vben', 2041700000000200002, '/workspace', 'Asia/Shanghai', 1),
|
||||
(2041700000000000002, 'admin', '123456', 'Admin', 2041700000000200001, '/workspace', 'Asia/Shanghai', 1),
|
||||
(2041700000000000003, 'jack', '123456', 'Jack', 2041700000000200003, '/analytics', 'Asia/Shanghai', 1)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`password` = VALUES(`password`),
|
||||
`real_name` = VALUES(`real_name`),
|
||||
`dept_id` = VALUES(`dept_id`),
|
||||
`home_path` = VALUES(`home_path`),
|
||||
`timezone` = VALUES(`timezone`),
|
||||
`status` = VALUES(`status`),
|
||||
|
||||
@ -18,6 +18,7 @@ import { AdminRole } from './role/admin-role.entity';
|
||||
import { AdminRoleService } from './role/admin-role.service';
|
||||
import { AdminTimezoneController } from './timezone/admin-timezone.controller';
|
||||
import { AdminTimezoneService } from './timezone/admin-timezone.service';
|
||||
import { AdminUserManageController } from './user/admin-user-manage.controller';
|
||||
import { AdminUserController } from './user/admin-user.controller';
|
||||
import { AdminUser } from './user/admin-user.entity';
|
||||
import { AdminUserService } from './user/admin-user.service';
|
||||
@ -42,6 +43,7 @@ import { WordpressModule } from '@/wordpress/wordpress.module';
|
||||
controllers: [
|
||||
AdminAuthController,
|
||||
AdminUserController,
|
||||
AdminUserManageController,
|
||||
AdminMenuController,
|
||||
AdminRoleController,
|
||||
AdminDeptController,
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
Res,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import type { Request, Response } from 'express';
|
||||
import { CurrentAdminUser, Public, vbenSuccess } from '@/common';
|
||||
import { AdminMenuService } from '../menu/admin-menu.service';
|
||||
@ -29,6 +29,7 @@ export class AdminAuthController {
|
||||
) {}
|
||||
|
||||
@Post('auth/login')
|
||||
@ApiOperation({ summary: 'Admin 用户登录' })
|
||||
@Public()
|
||||
async login(
|
||||
@Body() body: { password?: string; username?: string },
|
||||
@ -63,6 +64,7 @@ export class AdminAuthController {
|
||||
}
|
||||
|
||||
@Post('auth/refresh')
|
||||
@ApiOperation({ summary: '刷新 Admin 访问令牌' })
|
||||
@Public()
|
||||
async refresh(
|
||||
@Req() req: Request,
|
||||
@ -76,6 +78,7 @@ export class AdminAuthController {
|
||||
}
|
||||
|
||||
@Post('auth/logout')
|
||||
@ApiOperation({ summary: 'Admin 用户退出登录' })
|
||||
@Public()
|
||||
logout(@Res({ passthrough: true }) res: Response) {
|
||||
this.authService.clearAccessTokenCookie(res);
|
||||
@ -85,6 +88,7 @@ export class AdminAuthController {
|
||||
}
|
||||
|
||||
@Get('auth/codes')
|
||||
@ApiOperation({ summary: '获取当前用户按钮权限码' })
|
||||
async getAccessCodes(@CurrentAdminUser() user: AdminUser) {
|
||||
return vbenSuccess(await this.menuService.getAccessCodes(user));
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { vbenSuccess } from '@/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { AdminDept } from './admin-dept.entity';
|
||||
@ -21,16 +21,19 @@ export class AdminDeptController {
|
||||
constructor(private readonly deptService: AdminDeptService) {}
|
||||
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取部门列表' })
|
||||
async list() {
|
||||
return vbenSuccess(await this.deptService.getDeptList());
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '新增部门' })
|
||||
async create(@Body() body: Partial<AdminDept>) {
|
||||
return vbenSuccess(await this.deptService.createDept(body));
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: '编辑部门' })
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() body: Partial<AdminDept>,
|
||||
@ -39,6 +42,7 @@ export class AdminDeptController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除部门' })
|
||||
async remove(@Param('id') id: string) {
|
||||
return vbenSuccess(await this.deptService.deleteDept(id));
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import type { Response } from 'express';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { Public, vbenPage, vbenSuccess } from '@/common';
|
||||
@ -73,6 +73,7 @@ export class AdminExampleController {
|
||||
constructor(private readonly minioClientService: MinioClientService) {}
|
||||
|
||||
@Post('upload')
|
||||
@ApiOperation({ summary: '上传示例文件' })
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async upload(@UploadedFile() file: MinioUploadFile) {
|
||||
const result = await this.minioClientService.uploadObject({ file });
|
||||
@ -83,6 +84,7 @@ export class AdminExampleController {
|
||||
}
|
||||
|
||||
@Get('table/list')
|
||||
@ApiOperation({ summary: '获取示例表格分页列表' })
|
||||
async tableList(
|
||||
@Query() query: Record<string, any>,
|
||||
) {
|
||||
@ -95,6 +97,7 @@ export class AdminExampleController {
|
||||
}
|
||||
|
||||
@Get('status')
|
||||
@ApiOperation({ summary: '返回指定状态码示例' })
|
||||
@Public()
|
||||
status(@Query('status') status: string, @Res() res: Response) {
|
||||
const code = Number(status) || 200;
|
||||
@ -112,6 +115,7 @@ export class AdminExampleController {
|
||||
}
|
||||
|
||||
@Get('demo/bigint')
|
||||
@ApiOperation({ summary: '获取 BigInt 序列化示例' })
|
||||
async bigint(@Res() res: Response) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(`{
|
||||
@ -135,12 +139,14 @@ export class AdminExampleController {
|
||||
}
|
||||
|
||||
@Get('test')
|
||||
@ApiOperation({ summary: 'GET 测试接口' })
|
||||
@Public()
|
||||
testGet() {
|
||||
return vbenSuccess('Test get handler');
|
||||
}
|
||||
|
||||
@Post('test')
|
||||
@ApiOperation({ summary: 'POST 测试接口' })
|
||||
@Public()
|
||||
testPost() {
|
||||
return vbenSuccess('Test post handler');
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { CurrentAdminUser, vbenSuccess } from '@/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { AdminUser } from '../user/admin-user.entity';
|
||||
@ -23,16 +23,19 @@ export class AdminMenuController {
|
||||
constructor(private readonly menuService: AdminMenuService) {}
|
||||
|
||||
@Get('menu/all')
|
||||
@ApiOperation({ summary: '获取当前用户路由菜单' })
|
||||
async all(@CurrentAdminUser() user: AdminUser) {
|
||||
return vbenSuccess(await this.menuService.getRouteMenus(user));
|
||||
}
|
||||
|
||||
@Get('system/menu/list')
|
||||
@ApiOperation({ summary: '获取系统菜单列表' })
|
||||
async list() {
|
||||
return vbenSuccess(await this.menuService.getMenuList());
|
||||
}
|
||||
|
||||
@Get('system/menu/name-exists')
|
||||
@ApiOperation({ summary: '校验菜单名称是否存在' })
|
||||
async nameExists(
|
||||
@Query('name') name: string,
|
||||
@Query('id') id?: string,
|
||||
@ -41,6 +44,7 @@ export class AdminMenuController {
|
||||
}
|
||||
|
||||
@Get('system/menu/path-exists')
|
||||
@ApiOperation({ summary: '校验菜单路径是否存在' })
|
||||
async pathExists(
|
||||
@Query('path') path: string,
|
||||
@Query('id') id?: string,
|
||||
@ -49,11 +53,13 @@ export class AdminMenuController {
|
||||
}
|
||||
|
||||
@Post('system/menu')
|
||||
@ApiOperation({ summary: '新增系统菜单' })
|
||||
async create(@Body() body: Partial<AdminMenu>) {
|
||||
return vbenSuccess(await this.menuService.createMenu(body));
|
||||
}
|
||||
|
||||
@Put('system/menu/:id')
|
||||
@ApiOperation({ summary: '编辑系统菜单' })
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() body: Partial<AdminMenu>,
|
||||
@ -62,6 +68,7 @@ export class AdminMenuController {
|
||||
}
|
||||
|
||||
@Delete('system/menu/:id')
|
||||
@ApiOperation({ summary: '删除系统菜单' })
|
||||
async remove(@Param('id') id: string) {
|
||||
return vbenSuccess(await this.menuService.deleteMenu(id));
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { vbenPage, vbenSuccess } from '@/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { AdminRoleService } from './admin-role.service';
|
||||
@ -21,17 +21,20 @@ export class AdminRoleController {
|
||||
constructor(private readonly roleService: AdminRoleService) {}
|
||||
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取角色分页列表' })
|
||||
async list(@Query() query: Record<string, any>) {
|
||||
const page = await this.roleService.getRoleList(query);
|
||||
return vbenPage(page.items, page.total);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '新增角色' })
|
||||
async create(@Body() body: Record<string, any>) {
|
||||
return vbenSuccess(await this.roleService.createRole(body));
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: '编辑角色' })
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() body: Record<string, any>,
|
||||
@ -40,6 +43,7 @@ export class AdminRoleController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除角色' })
|
||||
async remove(@Param('id') id: string) {
|
||||
return vbenSuccess(await this.roleService.deleteRole(id));
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { CurrentAdminUser, vbenSuccess } from '@/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { AdminUser } from '../user/admin-user.entity';
|
||||
@ -20,16 +20,19 @@ export class AdminTimezoneController {
|
||||
constructor(private readonly timezoneService: AdminTimezoneService) {}
|
||||
|
||||
@Get('getTimezoneOptions')
|
||||
@ApiOperation({ summary: '获取时区选项' })
|
||||
getOptions() {
|
||||
return vbenSuccess(TIMEZONE_OPTIONS);
|
||||
}
|
||||
|
||||
@Get('getTimezone')
|
||||
@ApiOperation({ summary: '获取当前用户时区' })
|
||||
async getTimezone(@CurrentAdminUser() user: AdminUser) {
|
||||
return vbenSuccess(await this.timezoneService.getTimezone(user));
|
||||
}
|
||||
|
||||
@Post('setTimezone')
|
||||
@ApiOperation({ summary: '设置当前用户时区' })
|
||||
async setTimezone(
|
||||
@CurrentAdminUser() user: AdminUser,
|
||||
@Body() body: { timezone?: string },
|
||||
|
||||
51
src/admin/user/admin-user-manage.controller.ts
Normal file
51
src/admin/user/admin-user-manage.controller.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { CurrentAdminUser, vbenPage, vbenSuccess } from '@/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { AdminUser } from './admin-user.entity';
|
||||
import { AdminUserService } from './admin-user.service';
|
||||
|
||||
@ApiTags('Admin - 用户管理')
|
||||
@Controller('system/user')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class AdminUserManageController {
|
||||
constructor(private readonly userService: AdminUserService) {}
|
||||
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取用户分页列表' })
|
||||
async list(@Query() query: Record<string, any>) {
|
||||
const page = await this.userService.getUserList(query);
|
||||
return vbenPage(page.items, page.total);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '新增用户' })
|
||||
async create(@Body() body: Record<string, any>) {
|
||||
return vbenSuccess(await this.userService.createUser(body));
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: '编辑用户' })
|
||||
async update(@Param('id') id: string, @Body() body: Record<string, any>) {
|
||||
return vbenSuccess(await this.userService.updateUser(id, body));
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除用户' })
|
||||
async remove(
|
||||
@Param('id') id: string,
|
||||
@CurrentAdminUser() currentUser: AdminUser,
|
||||
) {
|
||||
return vbenSuccess(await this.userService.deleteUser(id, currentUser?.id));
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { CurrentAdminUser, vbenSuccess } from '@/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { AdminUser } from './admin-user.entity';
|
||||
@ -12,6 +12,7 @@ export class AdminUserController {
|
||||
constructor(private readonly userService: AdminUserService) {}
|
||||
|
||||
@Get('info')
|
||||
@ApiOperation({ summary: '获取当前用户信息' })
|
||||
async info(@CurrentAdminUser() user: AdminUser) {
|
||||
return vbenSuccess(this.userService.serializeUser(user));
|
||||
}
|
||||
|
||||
@ -3,12 +3,15 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { ensureSnowflakeId } from '@/common';
|
||||
import { AdminDept } from '../dept/admin-dept.entity';
|
||||
import { AdminRole } from '../role/admin-role.entity';
|
||||
|
||||
@Entity('admin_user')
|
||||
@ -31,6 +34,13 @@ export class AdminUser {
|
||||
})
|
||||
realName: string;
|
||||
|
||||
@Column({
|
||||
name: 'dept_id',
|
||||
nullable: true,
|
||||
type: 'bigint',
|
||||
})
|
||||
deptId?: string | null;
|
||||
|
||||
@Column({
|
||||
default: '',
|
||||
name: 'home_path',
|
||||
@ -79,6 +89,15 @@ export class AdminUser {
|
||||
})
|
||||
roles: AdminRole[];
|
||||
|
||||
@ManyToOne(() => AdminDept, {
|
||||
eager: true,
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({
|
||||
name: 'dept_id',
|
||||
})
|
||||
dept?: AdminDept | null;
|
||||
|
||||
@BeforeInsert()
|
||||
createId() {
|
||||
ensureSnowflakeId(this);
|
||||
|
||||
@ -1,8 +1,152 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Not, Repository } from 'typeorm';
|
||||
import { throwVbenError } from '@/common';
|
||||
import { AdminDept } from '../dept/admin-dept.entity';
|
||||
import { AdminRole } from '../role/admin-role.entity';
|
||||
import { AdminUser } from './admin-user.entity';
|
||||
|
||||
type UserInput = Partial<AdminUser> & {
|
||||
roleIds?: string[];
|
||||
};
|
||||
|
||||
type ListQuery = Record<string, any>;
|
||||
|
||||
@Injectable()
|
||||
export class AdminUserService {
|
||||
constructor(
|
||||
@InjectRepository(AdminUser)
|
||||
private readonly userRepository: Repository<AdminUser>,
|
||||
@InjectRepository(AdminRole)
|
||||
private readonly roleRepository: Repository<AdminRole>,
|
||||
@InjectRepository(AdminDept)
|
||||
private readonly deptRepository: Repository<AdminDept>,
|
||||
) {}
|
||||
|
||||
async getUserList(query: ListQuery) {
|
||||
const page = Number(query.page || 1);
|
||||
const pageSize = Number(query.pageSize || 20);
|
||||
const builder = this.userRepository
|
||||
.createQueryBuilder('user')
|
||||
.leftJoinAndSelect('user.roles', 'role')
|
||||
.leftJoinAndSelect('user.dept', 'dept')
|
||||
.where('user.isDeleted = :isDeleted', { isDeleted: false });
|
||||
|
||||
if (query.id) {
|
||||
builder.andWhere('user.id LIKE :id', { id: `%${query.id}%` });
|
||||
}
|
||||
if (query.username) {
|
||||
builder.andWhere('user.username LIKE :username', {
|
||||
username: `%${query.username}%`,
|
||||
});
|
||||
}
|
||||
if (query.realName) {
|
||||
builder.andWhere('user.realName LIKE :realName', {
|
||||
realName: `%${query.realName}%`,
|
||||
});
|
||||
}
|
||||
if (['0', '1'].includes(String(query.status))) {
|
||||
builder.andWhere('user.status = :status', {
|
||||
status: Number(query.status),
|
||||
});
|
||||
}
|
||||
if (query.deptId) {
|
||||
const deptIds = await this.collectDeptIds(String(query.deptId));
|
||||
builder.andWhere('user.deptId IN (:...deptIds)', { deptIds });
|
||||
}
|
||||
if (query.roleId) {
|
||||
builder.andWhere('role.id = :roleId', { roleId: String(query.roleId) });
|
||||
}
|
||||
if (query.startTime) {
|
||||
builder.andWhere('user.createTime >= :startTime', {
|
||||
startTime: query.startTime,
|
||||
});
|
||||
}
|
||||
if (query.endTime) {
|
||||
builder.andWhere('user.createTime <= :endTime', {
|
||||
endTime: query.endTime,
|
||||
});
|
||||
}
|
||||
|
||||
const [users, total] = await builder
|
||||
.orderBy('user.createTime', 'ASC')
|
||||
.skip((page - 1) * pageSize)
|
||||
.take(pageSize)
|
||||
.getManyAndCount();
|
||||
|
||||
return {
|
||||
items: users.map((user) => this.serializeUserForList(user)),
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
async createUser(data: UserInput) {
|
||||
await this.ensureUsernameAvailable(String(data.username || ''));
|
||||
|
||||
const user = this.userRepository.create({
|
||||
deptId: data.deptId || null,
|
||||
homePath: data.homePath || '/workspace',
|
||||
password: data.password || '123456',
|
||||
realName: data.realName,
|
||||
status: data.status ?? 1,
|
||||
timezone: data.timezone || 'Asia/Shanghai',
|
||||
username: data.username,
|
||||
});
|
||||
user.roles = await this.findRolesByIds(data.roleIds || []);
|
||||
await this.userRepository.save(user);
|
||||
return null;
|
||||
}
|
||||
|
||||
async updateUser(id: string, data: UserInput) {
|
||||
const user = await this.userRepository.findOne({
|
||||
relations: ['roles'],
|
||||
where: {
|
||||
id,
|
||||
isDeleted: false,
|
||||
},
|
||||
});
|
||||
if (!user) throwVbenError('用户不存在', HttpStatus.BAD_REQUEST);
|
||||
|
||||
if (data.username !== undefined && data.username !== user.username) {
|
||||
await this.ensureUsernameAvailable(data.username, id);
|
||||
user.username = data.username;
|
||||
}
|
||||
if (data.password) user.password = data.password;
|
||||
if (data.deptId !== undefined) user.deptId = data.deptId || null;
|
||||
if (data.realName !== undefined) user.realName = data.realName;
|
||||
if (data.homePath !== undefined) user.homePath = data.homePath;
|
||||
if (data.timezone !== undefined) user.timezone = data.timezone;
|
||||
if (data.status !== undefined) user.status = data.status;
|
||||
if (data.roleIds) user.roles = await this.findRolesByIds(data.roleIds);
|
||||
|
||||
await this.userRepository.save(user);
|
||||
return null;
|
||||
}
|
||||
|
||||
async deleteUser(id: string, currentUserId?: string) {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
isDeleted: false,
|
||||
},
|
||||
});
|
||||
if (!user) throwVbenError('用户不存在', HttpStatus.BAD_REQUEST);
|
||||
if (id === currentUserId) {
|
||||
throwVbenError('不能删除当前登录用户', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (user.username === 'admin') {
|
||||
throwVbenError('不能删除内置管理员账号', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
await this.userRepository.update(
|
||||
{ id },
|
||||
{
|
||||
isDeleted: true,
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
serializeUser(user: AdminUser) {
|
||||
return {
|
||||
homePath: user.homePath,
|
||||
@ -14,4 +158,88 @@ export class AdminUserService {
|
||||
username: user.username,
|
||||
};
|
||||
}
|
||||
|
||||
private serializeUserForList(user: AdminUser) {
|
||||
const activeRoles = (user.roles || []).filter((role) => !role.isDeleted);
|
||||
return {
|
||||
createTime: user.createTime,
|
||||
dept: user.dept
|
||||
? {
|
||||
id: user.dept.id,
|
||||
name: user.dept.name,
|
||||
}
|
||||
: null,
|
||||
deptId: user.deptId,
|
||||
deptName: user.dept?.name || '',
|
||||
homePath: user.homePath,
|
||||
id: user.id,
|
||||
realName: user.realName,
|
||||
roleIds: activeRoles.map((role) => role.id),
|
||||
roleNames: activeRoles.map((role) => role.name),
|
||||
roles: activeRoles.map((role) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
roleCode: role.roleCode,
|
||||
status: role.status,
|
||||
})),
|
||||
status: user.status,
|
||||
timezone: user.timezone,
|
||||
updateTime: user.updateTime,
|
||||
username: user.username,
|
||||
};
|
||||
}
|
||||
|
||||
private async findRolesByIds(ids: string[]) {
|
||||
const normalizedIds = ids.map((id) => String(id)).filter(Boolean);
|
||||
if (!normalizedIds.length) return [];
|
||||
return this.roleRepository.find({
|
||||
where: normalizedIds.map((id) => ({
|
||||
id,
|
||||
isDeleted: false,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
private async collectDeptIds(deptId: string) {
|
||||
if (deptId === '0') return ['0'];
|
||||
|
||||
const depts = await this.deptRepository.find({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
},
|
||||
});
|
||||
const result = new Set<string>([deptId]);
|
||||
let changed = true;
|
||||
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const dept of depts) {
|
||||
if (result.has(String(dept.pid)) && !result.has(dept.id)) {
|
||||
result.add(dept.id);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(result);
|
||||
}
|
||||
|
||||
private async ensureUsernameAvailable(username: string, ignoreId?: string) {
|
||||
if (!username.trim()) {
|
||||
throwVbenError('用户名不能为空', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const where = ignoreId
|
||||
? {
|
||||
id: Not(ignoreId),
|
||||
username,
|
||||
}
|
||||
: {
|
||||
username,
|
||||
};
|
||||
const existing = await this.userRepository.findOne({ where });
|
||||
if (existing) {
|
||||
throwVbenError('用户名已存在', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,13 +4,10 @@ import {
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@ApiTags('基础能力 - 根入口')
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
@Redirect('/api#/', 301)
|
||||
@ApiOperation({ summary: '重定向到Swagger文档' })
|
||||
|
||||
@ -3,8 +3,8 @@ import { AppModule } from './app.module';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import type { OpenAPIObject } from '@nestjs/swagger';
|
||||
import { urlencoded, json } from 'express';
|
||||
import { knife4jSetup } from 'nestjs-knife4j-plus';
|
||||
import type { Service } from 'nestjs-knife4j-plus';
|
||||
import { knife4jSetup } from '@kwitsukasa/knife4j-swagger-vue3';
|
||||
import type { Service } from '@kwitsukasa/knife4j-swagger-vue3';
|
||||
import { applySwaggerResponseExamples } from './common';
|
||||
|
||||
type SwaggerPathMatcher = (path: string) => boolean;
|
||||
|
||||
@ -300,11 +300,11 @@ export class QqbotFf14ClientService {
|
||||
}
|
||||
|
||||
const fuzzyItems = await this.searchItemsByLanguage(keyword, language, '~');
|
||||
const fuzzyItem = this.pickSingleFuzzySearchItem(keyword, fuzzyItems);
|
||||
const fuzzyItem = this.pickSingleFuzzySearchItem(fuzzyItems);
|
||||
if (fuzzyItem || language === 'en') return fuzzyItem;
|
||||
|
||||
const enFuzzyItems = await this.searchItemsByLanguage(keyword, 'en', '~');
|
||||
return this.pickSingleFuzzySearchItem(keyword, enFuzzyItems);
|
||||
return this.pickSingleFuzzySearchItem(enFuzzyItems);
|
||||
}
|
||||
|
||||
private async searchItemsByLanguage(
|
||||
@ -337,10 +337,7 @@ export class QqbotFf14ClientService {
|
||||
return items[0];
|
||||
}
|
||||
|
||||
private pickSingleFuzzySearchItem(
|
||||
keyword: string,
|
||||
items: XivapiSearchItem[],
|
||||
) {
|
||||
private pickSingleFuzzySearchItem(items: XivapiSearchItem[]) {
|
||||
if (items.length <= 1) return items[0];
|
||||
throw new Error(
|
||||
`找到多个相似物品,请输入更完整名称或物品 ID:${this.formatSearchCandidates(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"ignoreDeprecations": "6.0",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
@ -15,6 +16,8 @@
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user