Compare commits
No commits in common. "e8e352b6b6f006544341786f24e87f01016c09ce" and "f9f75c9d2d76da02db5ce87fe61518b9240255b8" have entirely different histories.
e8e352b6b6
...
f9f75c9d2d
@ -7,28 +7,10 @@
|
||||
* @description 字典模块的路由控制器,仅实现创建字典项接口
|
||||
*/
|
||||
|
||||
import { Elysia, t } from 'elysia';
|
||||
import { Elysia } from 'elysia';
|
||||
import { dictService } from './dict.service';
|
||||
import {
|
||||
CreateDictSchema,
|
||||
GetDictByIdSchema,
|
||||
GetDictTreeByCodeParamsSchema,
|
||||
GetDictTreeByCodeQuerySchema,
|
||||
GetDictTreeQuerySchema,
|
||||
UpdateDictBodySchema,
|
||||
UpdateDictParamsSchema,
|
||||
SortDictSchema,
|
||||
DeleteDictSchema,
|
||||
} from './dict.schema';
|
||||
import {
|
||||
CreateDictResponsesSchema,
|
||||
GetDictByIdResponsesSchema,
|
||||
GetDictTreeByCodeResponsesSchema,
|
||||
GetDictTreeResponsesSchema,
|
||||
UpdateDictResponsesSchema,
|
||||
SortDictResponsesSchema,
|
||||
DeleteDictResponsesSchema,
|
||||
} from './dict.response';
|
||||
import { CreateDictSchema } from './dict.schema';
|
||||
import { CreateDictResponsesSchema } from './dict.response';
|
||||
import { tags } from '@/constants/swaggerTags';
|
||||
|
||||
/**
|
||||
@ -50,105 +32,4 @@ export const dictController = new Elysia()
|
||||
operationId: 'createDict',
|
||||
},
|
||||
response: CreateDictResponsesSchema,
|
||||
})
|
||||
/**
|
||||
* 获取字典项内容接口
|
||||
* @route GET /api/dict/:id
|
||||
* @description 根据ID获取单个字典项的详细内容
|
||||
*/
|
||||
.get('/:id', ({ params }) => dictService.getDictById(params.id), {
|
||||
params: GetDictByIdSchema,
|
||||
detail: {
|
||||
summary: '获取字典项内容',
|
||||
description: '根据ID获取单个字典项的详细内容',
|
||||
tags: [tags.dict],
|
||||
operationId: 'getDictById',
|
||||
},
|
||||
response: GetDictByIdResponsesSchema,
|
||||
})
|
||||
/**
|
||||
* 获取完整字典树接口
|
||||
* @route GET /api/dict/tree
|
||||
* @description 获取完整的字典树结构,可根据状态和是否系统字典进行过滤
|
||||
*/
|
||||
.get('/tree', ({ query }) => dictService.getDictTree(query), {
|
||||
query: GetDictTreeQuerySchema,
|
||||
detail: {
|
||||
summary: '获取完整字典树',
|
||||
description: '获取完整的字典树结构,可根据状态和是否系统字典进行过滤',
|
||||
tags: [tags.dict],
|
||||
operationId: 'getDictTree',
|
||||
},
|
||||
response: GetDictTreeResponsesSchema,
|
||||
})
|
||||
/**
|
||||
* 获取指定字典树接口
|
||||
* @route GET /api/dict/tree/:code
|
||||
* @description 根据字典代码获取其对应的子树结构
|
||||
*/
|
||||
.get('/tree/:code', ({ params, query }) => dictService.getDictTreeByCode(params, query), {
|
||||
params: GetDictTreeByCodeParamsSchema,
|
||||
query: GetDictTreeByCodeQuerySchema,
|
||||
detail: {
|
||||
summary: '获取指定字典树',
|
||||
description: '根据字典代码获取其对应的子树结构,可根据状态和是否系统字典进行过滤',
|
||||
tags: [tags.dict],
|
||||
operationId: 'getDictTreeByCode',
|
||||
},
|
||||
response: GetDictTreeByCodeResponsesSchema,
|
||||
})
|
||||
/**
|
||||
* 更新字典项内容接口
|
||||
* @route PUT /api/dict/:id
|
||||
* @description 更新指定ID的字典项内容,所有字段均为可选
|
||||
*/
|
||||
.put('/:id', ({ params, body }) => dictService.updateDict(params.id, body), {
|
||||
params: UpdateDictParamsSchema,
|
||||
body: UpdateDictBodySchema,
|
||||
detail: {
|
||||
summary: '更新字典项内容',
|
||||
description: '更新指定ID的字典项内容,所有字段均为可选。',
|
||||
tags: [tags.dict],
|
||||
operationId: 'updateDictItem',
|
||||
},
|
||||
response: UpdateDictResponsesSchema,
|
||||
})
|
||||
/**
|
||||
* 字典项排序接口
|
||||
* @route PUT /api/dict/sort
|
||||
* @description 对字典项进行排序或移动(改变父级)
|
||||
*/
|
||||
.put('/sort', ({ body }) => dictService.sortDict(body), {
|
||||
body: SortDictSchema,
|
||||
detail: {
|
||||
summary: '字典项排序',
|
||||
description: '对多个字典项进行批量排序或移动层级。',
|
||||
tags: [tags.dict],
|
||||
operationId: 'sortDictItems',
|
||||
},
|
||||
response: SortDictResponsesSchema,
|
||||
})
|
||||
/**
|
||||
* 删除字典项接口
|
||||
* @route DELETE /api/dict/:id
|
||||
* @description 软删除指定ID的字典项,可通过cascade参数级联删除子项
|
||||
*/
|
||||
.delete(
|
||||
'/:id',
|
||||
({ params, query }) =>
|
||||
dictService.deleteDict({
|
||||
id: params.id,
|
||||
cascade: query.cascade,
|
||||
}),
|
||||
{
|
||||
params: t.Object({ id: t.String() }),
|
||||
query: t.Object({ cascade: t.Optional(t.Boolean()) }),
|
||||
detail: {
|
||||
summary: '删除字典项',
|
||||
description: '软删除指定ID的字典项。如果字典项有子项,必须使用 `?cascade=true` 进行级联删除。',
|
||||
tags: [tags.dict],
|
||||
operationId: 'deleteDictItem',
|
||||
},
|
||||
response: DeleteDictResponsesSchema,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -128,292 +128,3 @@ export const CreateDictResponsesSchema = {
|
||||
|
||||
/** 创建字典项成功响应数据类型 */
|
||||
export type CreateDictSuccessType = Static<(typeof CreateDictResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 获取字典项成功响应数据结构
|
||||
* @description 获取单个字典项的成功响应数据结构,与创建成功响应结构相同
|
||||
*/
|
||||
export const GetDictByIdSuccessSchema = CreateDictSuccessSchema;
|
||||
|
||||
/**
|
||||
* 获取字典项接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const GetDictByIdResponsesSchema = {
|
||||
200: responseWrapperSchema(GetDictByIdSuccessSchema),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['字典项不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
500: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 获取字典项成功响应数据类型 */
|
||||
export type GetDictByIdSuccessType = Static<(typeof GetDictByIdResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 字典树节点数据结构
|
||||
* @description 描述字典树中单个节点的数据结构,包含一个可选的children数组用于表示子节点
|
||||
*/
|
||||
export const DictTreeNodeSchema = t.Recursive(
|
||||
(self) =>
|
||||
t.Object({
|
||||
id: t.String({
|
||||
description: '字典项ID(bigint类型以字符串返回防止精度丢失)',
|
||||
examples: ['1', '2', '100'],
|
||||
}),
|
||||
code: t.String({
|
||||
description: '字典代码',
|
||||
examples: ['user_status'],
|
||||
}),
|
||||
name: t.String({
|
||||
description: '字典名称',
|
||||
examples: ['用户状态'],
|
||||
}),
|
||||
value: t.Optional(
|
||||
t.String({
|
||||
description: '字典值',
|
||||
examples: ['active'],
|
||||
}),
|
||||
),
|
||||
description: t.Optional(
|
||||
t.String({
|
||||
description: '字典描述',
|
||||
examples: ['用户状态字典'],
|
||||
}),
|
||||
),
|
||||
icon: t.Optional(
|
||||
t.String({
|
||||
description: '图标',
|
||||
examples: ['icon-user'],
|
||||
}),
|
||||
),
|
||||
pid: t.String({
|
||||
description: '父级ID(bigint类型以字符串返回)',
|
||||
examples: ['0', '1'],
|
||||
}),
|
||||
level: t.Number({
|
||||
description: '层级深度',
|
||||
examples: [1, 2],
|
||||
}),
|
||||
sortOrder: t.Number({
|
||||
description: '排序号',
|
||||
examples: [0, 1, 10],
|
||||
}),
|
||||
status: t.String({
|
||||
description: '状态',
|
||||
examples: ['active', 'inactive'],
|
||||
}),
|
||||
isSystem: t.Boolean({
|
||||
description: '是否系统字典',
|
||||
examples: [true, false],
|
||||
}),
|
||||
color: t.Optional(
|
||||
t.String({
|
||||
description: '颜色标识',
|
||||
examples: ['#1890ff'],
|
||||
}),
|
||||
),
|
||||
extra: t.Optional(
|
||||
t.Record(t.String(), t.Any(), {
|
||||
description: '扩展字段',
|
||||
examples: [{ key1: 'value1' }],
|
||||
}),
|
||||
),
|
||||
createdAt: t.String({
|
||||
description: '创建时间',
|
||||
examples: ['2024-12-19T10:30:00Z'],
|
||||
}),
|
||||
updatedAt: t.String({
|
||||
description: '更新时间',
|
||||
examples: ['2024-12-19T10:30:00Z'],
|
||||
}),
|
||||
children: t.Optional(t.Array(self)),
|
||||
}),
|
||||
{
|
||||
$id: 'DictTreeNode',
|
||||
description: '字典树节点',
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取完整字典树接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const GetDictTreeResponsesSchema = {
|
||||
200: responseWrapperSchema(t.Array(DictTreeNodeSchema)),
|
||||
500: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 获取完整字典树成功响应数据类型 */
|
||||
export type GetDictTreeSuccessType = Static<(typeof GetDictTreeResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 获取指定字典树接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const GetDictTreeByCodeResponsesSchema = {
|
||||
...GetDictTreeResponsesSchema,
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['字典代码不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 获取指定字典树成功响应数据类型 */
|
||||
export type GetDictTreeByCodeSuccessType = Static<(typeof GetDictTreeByCodeResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 更新字典项成功响应数据结构
|
||||
* @description 更新字典项的成功响应数据结构,与获取成功响应结构相同
|
||||
*/
|
||||
export const UpdateDictSuccessSchema = GetDictByIdSuccessSchema;
|
||||
|
||||
/**
|
||||
* 更新字典项接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const UpdateDictResponsesSchema = {
|
||||
200: responseWrapperSchema(UpdateDictSuccessSchema),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['字典项不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
409: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '唯一性冲突',
|
||||
examples: ['字典代码已存在', '字典名称在同级下已存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
500: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 更新字典项成功响应数据类型 */
|
||||
export type UpdateDictSuccessType = Static<(typeof UpdateDictResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 字典项排序接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const SortDictResponsesSchema = {
|
||||
200: responseWrapperSchema(t.Null(), {
|
||||
description: '排序成功',
|
||||
examples: {
|
||||
code: 0,
|
||||
message: '字典项排序成功',
|
||||
data: null,
|
||||
},
|
||||
}),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '参数错误',
|
||||
examples: ['参数校验失败', '排序项列表为空'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['部分字典项不存在', '父级字典不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
500: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 字典项排序成功响应数据类型 */
|
||||
export type SortDictSuccessType = Static<(typeof SortDictResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 删除字典项接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const DeleteDictResponsesSchema = {
|
||||
200: responseWrapperSchema(t.Null(), {
|
||||
description: '删除成功',
|
||||
examples: {
|
||||
code: 0,
|
||||
message: '字典项删除成功',
|
||||
data: null,
|
||||
},
|
||||
}),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '参数错误或业务规则冲突',
|
||||
examples: ['存在子字典项,无法删除,请先删除子项或使用级联删除'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['字典项不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
409: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '冲突',
|
||||
examples: ['系统字典不允许删除'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
500: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 删除字典项成功响应数据类型 */
|
||||
export type DeleteDictSuccessType = Static<(typeof DeleteDictResponsesSchema)[200]>;
|
||||
|
@ -15,52 +15,49 @@ import { t, type Static } from 'elysia';
|
||||
*/
|
||||
export const CreateDictSchema = t.Object({
|
||||
/** 字典代码,唯一标识 */
|
||||
code: t
|
||||
.Transform(
|
||||
t.String({
|
||||
code: t.Transform(t.String({
|
||||
minLength: 1,
|
||||
maxLength: 50,
|
||||
description: '字典代码,全局唯一标识,自动转换为小写并去除两端空格',
|
||||
examples: ['user_status', 'order_type', 'system_config'],
|
||||
}),
|
||||
)
|
||||
}))
|
||||
.Decode((value: string) => value.trim().toLowerCase())
|
||||
.Encode((value: string) => value),
|
||||
/** 字典名称 */
|
||||
name: t
|
||||
.Transform(
|
||||
t.String({
|
||||
name: t.Transform(t.String({
|
||||
minLength: 1,
|
||||
maxLength: 100,
|
||||
description: '字典名称,同级下唯一,自动去除两端空格',
|
||||
examples: ['用户状态', '订单类型', '系统配置'],
|
||||
}),
|
||||
)
|
||||
}))
|
||||
.Decode((value: string) => value.trim().toLowerCase())
|
||||
.Encode((value: string) => value),
|
||||
/** 字典值(叶子节点才有值) */
|
||||
value: t.Optional(
|
||||
t.String({
|
||||
t
|
||||
.String({
|
||||
maxLength: 200,
|
||||
description: '字典值,叶子节点才有值,自动去除两端空格',
|
||||
examples: ['active', 'inactive', 'pending'],
|
||||
}),
|
||||
})
|
||||
),
|
||||
/** 字典描述 */
|
||||
description: t.Optional(
|
||||
t.String({
|
||||
maxLength: 200,
|
||||
t
|
||||
.String({
|
||||
maxLength: 500,
|
||||
description: '字典描述,自动去除两端空格',
|
||||
examples: ['用户状态字典,包含激活、禁用、待审核等状态'],
|
||||
}),
|
||||
})
|
||||
),
|
||||
/** 图标(CSS类名或图标路径) */
|
||||
icon: t.Optional(
|
||||
t.String({
|
||||
t
|
||||
.String({
|
||||
maxLength: 100,
|
||||
description: '图标CSS类名或图标路径,自动去除两端空格',
|
||||
examples: ['icon-user', 'icon-order', '/icons/config.png'],
|
||||
}),
|
||||
})
|
||||
),
|
||||
/** 父级ID,0表示顶级 */
|
||||
pid: t.Optional(
|
||||
@ -89,27 +86,30 @@ export const CreateDictSchema = t.Object({
|
||||
),
|
||||
/** 状态:active-启用,inactive-禁用 */
|
||||
status: t.Optional(
|
||||
t.Union([t.Literal('active'), t.Literal('inactive')], {
|
||||
t
|
||||
.Union([t.Literal('active'), t.Literal('inactive')], {
|
||||
description: '字典状态,默认active',
|
||||
examples: ['active', 'inactive'],
|
||||
default: 'active',
|
||||
}),
|
||||
})
|
||||
),
|
||||
/** 是否系统字典 */
|
||||
isSystem: t.Optional(
|
||||
t.Boolean({
|
||||
t
|
||||
.Boolean({
|
||||
description: '是否系统字典,系统字典只能由超级管理员创建,默认false',
|
||||
examples: [true, false],
|
||||
default: false,
|
||||
}),
|
||||
})
|
||||
),
|
||||
/** 颜色标识 */
|
||||
color: t.Optional(
|
||||
t.String({
|
||||
t
|
||||
.String({
|
||||
maxLength: 20,
|
||||
description: '颜色标识,用于前端显示,自动去除两端空格',
|
||||
examples: ['#1890ff', '#52c41a', '#faad14', '#f5222d'],
|
||||
}),
|
||||
})
|
||||
),
|
||||
/** 扩展字段 */
|
||||
extra: t.Optional(
|
||||
@ -164,40 +164,7 @@ export const GetDictTreeQuerySchema = t.Object({
|
||||
export type GetDictTreeQueryRequest = Static<typeof GetDictTreeQuerySchema>;
|
||||
|
||||
/**
|
||||
* 获取指定字典树路径参数Schema
|
||||
*/
|
||||
export const GetDictTreeByCodeParamsSchema = t.Object({
|
||||
/** 字典代码 */
|
||||
code: t.String({
|
||||
minLength: 1,
|
||||
maxLength: 50,
|
||||
description: '字典代码,用于查找指定的字典树',
|
||||
examples: ['user_status', 'order_type', 'system_config'],
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取指定字典树查询参数Schema
|
||||
*/
|
||||
export const GetDictTreeByCodeQuerySchema = t.Object({
|
||||
/** 状态过滤 */
|
||||
status: t.Optional(
|
||||
t.Union([t.Literal('active'), t.Literal('inactive'), t.Literal('all')], {
|
||||
description: '状态过滤条件',
|
||||
examples: ['active', 'inactive', 'all'],
|
||||
}),
|
||||
),
|
||||
/** 是否系统字典过滤 */
|
||||
isSystem: t.Optional(
|
||||
t.Union([t.Literal('true'), t.Literal('false'), t.Literal('all')], {
|
||||
description: '是否系统字典过滤条件',
|
||||
examples: ['true', 'false', 'all'],
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated Use GetDictTreeByCodeParamsSchema and GetDictTreeByCodeQuerySchema instead
|
||||
* 获取指定字典树请求参数Schema
|
||||
* @description 根据code获取指定字典树的请求参数验证规则
|
||||
*/
|
||||
export const GetDictTreeByCodeSchema = t.Object({
|
||||
@ -225,24 +192,13 @@ export const GetDictTreeByCodeSchema = t.Object({
|
||||
});
|
||||
|
||||
/** 获取指定字典树请求参数类型 */
|
||||
export type GetDictTreeByCodeRequest = Static<typeof GetDictTreeByCodeQuerySchema>;
|
||||
export type GetDictTreeByCodeRequest = Static<typeof GetDictTreeByCodeSchema>;
|
||||
|
||||
/**
|
||||
* 更新字典项路径参数Schema
|
||||
*/
|
||||
export const UpdateDictParamsSchema = t.Object({
|
||||
id: t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '字典项ID,必须是正整数',
|
||||
examples: ['1', '2', '100'],
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新字典项Body参数Schema
|
||||
* 更新字典项请求参数Schema
|
||||
* @description 更新字典项的请求参数验证规则,所有字段都是可选的
|
||||
*/
|
||||
export const UpdateDictBodySchema = t.Object({
|
||||
export const UpdateDictSchema = t.Object({
|
||||
/** 字典代码,唯一标识 */
|
||||
code: t.Optional(
|
||||
t.String({
|
||||
@ -272,7 +228,7 @@ export const UpdateDictBodySchema = t.Object({
|
||||
/** 字典描述 */
|
||||
description: t.Optional(
|
||||
t.String({
|
||||
maxLength: 200,
|
||||
maxLength: 500,
|
||||
description: '字典描述信息',
|
||||
examples: ['用户状态字典,包含激活、禁用、待审核等状态'],
|
||||
}),
|
||||
@ -341,8 +297,8 @@ export const UpdateDictBodySchema = t.Object({
|
||||
),
|
||||
});
|
||||
|
||||
/** 更新字典项Body参数类型 */
|
||||
export type UpdateDictBodyRequest = Static<typeof UpdateDictBodySchema>;
|
||||
/** 更新字典项请求参数类型 */
|
||||
export type UpdateDictRequest = Static<typeof UpdateDictSchema>;
|
||||
|
||||
/**
|
||||
* 字典项排序请求参数Schema
|
||||
|
@ -4,33 +4,17 @@
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 字典模块的业务逻辑实现,包括创建、查询和树结构生成
|
||||
* @description 字典模块的业务逻辑实现,仅实现创建字典项
|
||||
*/
|
||||
|
||||
import { Logger } from '@/plugins/logger/logger.service';
|
||||
import { db } from '@/plugins/drizzle/drizzle.service';
|
||||
import { sysDict } from '@/eneities';
|
||||
import { eq, and, or, ne, desc, asc, sql, inArray, isNull, not, max } from 'drizzle-orm';
|
||||
import { eq, and, max } from 'drizzle-orm';
|
||||
import { successResponse, BusinessError } from '@/utils/responseFormate';
|
||||
import { nextId } from '@/utils/snowflake';
|
||||
import { DistributedLockService } from '@/utils/distributedLock';
|
||||
import type {
|
||||
CreateDictRequest,
|
||||
GetDictTreeByCodeRequest,
|
||||
GetDictTreeQueryRequest,
|
||||
UpdateDictBodyRequest,
|
||||
SortDictRequest,
|
||||
DeleteDictRequest,
|
||||
} from './dict.schema';
|
||||
import type {
|
||||
CreateDictSuccessType,
|
||||
GetDictByIdSuccessType,
|
||||
GetDictTreeByCodeSuccessType,
|
||||
GetDictTreeSuccessType,
|
||||
UpdateDictSuccessType,
|
||||
SortDictSuccessType,
|
||||
DeleteDictSuccessType,
|
||||
} from './dict.response';
|
||||
import type { CreateDictRequest } from './dict.schema';
|
||||
import type { CreateDictSuccessType } from './dict.response';
|
||||
|
||||
/**
|
||||
* 字典服务类
|
||||
@ -44,27 +28,15 @@ export class DictService {
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
*/
|
||||
public async createDict(body: CreateDictRequest): Promise<CreateDictSuccessType> {
|
||||
const pid = body.pid || '0';
|
||||
const lockKey = `dict:create:code:${body.code}:name:${body.name}:pid:${pid}`;
|
||||
const lock = await DistributedLockService.acquire({ key: lockKey, ttl: 10 });
|
||||
|
||||
try {
|
||||
// 1. code唯一性校验
|
||||
const existCode = await db()
|
||||
.select({ id: sysDict.id })
|
||||
.from(sysDict)
|
||||
.where(eq(sysDict.code, body.code))
|
||||
.limit(1);
|
||||
const existCode = await db().select({id: sysDict.id}).from(sysDict).where(eq(sysDict.code, body.code)).limit(1);
|
||||
if (existCode.length > 0) {
|
||||
throw new BusinessError(`字典代码已存在: ${body.code}`, 409);
|
||||
}
|
||||
|
||||
// 2. name同级唯一性校验
|
||||
const existName = await db()
|
||||
.select({ id: sysDict.id })
|
||||
.from(sysDict)
|
||||
.where(and(eq(sysDict.name, body.name), eq(sysDict.pid, pid)))
|
||||
.limit(1);
|
||||
const pid = body.pid || '0';
|
||||
const existName = await db().select({id: sysDict.id}).from(sysDict).where(and(eq(sysDict.name, body.name), eq(sysDict.pid, pid))).limit(1);
|
||||
if (existName.length > 0) {
|
||||
throw new BusinessError(`字典名称已存在: ${body.name}`, 409);
|
||||
}
|
||||
@ -148,469 +120,6 @@ export class DictService {
|
||||
},
|
||||
'创建字典项成功',
|
||||
);
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典项内容
|
||||
* @param id 字典项ID
|
||||
* @returns Promise<GetDictByIdSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
*/
|
||||
public async getDictById(id: string): Promise<GetDictByIdSuccessType> {
|
||||
Logger.info(`获取字典项内容:${id}`);
|
||||
|
||||
const dictArr = await db().select().from(sysDict).where(eq(sysDict.id, id)).limit(1);
|
||||
|
||||
if (!dictArr || dictArr.length === 0) {
|
||||
Logger.warn(`字典项不存在:${id}`);
|
||||
throw new BusinessError(`字典项不存在:${id}`, 404);
|
||||
}
|
||||
|
||||
const dict = dictArr[0]!;
|
||||
|
||||
Logger.info(`获取字典项内容成功:${id}`);
|
||||
|
||||
return successResponse({
|
||||
id: String(dict.id),
|
||||
code: dict.code,
|
||||
name: dict.name,
|
||||
value: dict.value,
|
||||
description: dict.description,
|
||||
icon: dict.icon,
|
||||
pid: String(dict.pid),
|
||||
level: dict.level,
|
||||
sortOrder: dict.sortOrder,
|
||||
status: dict.status,
|
||||
isSystem: Boolean(dict.isSystem),
|
||||
color: dict.color,
|
||||
extra: dict.extra,
|
||||
createdAt: dict.createdAt,
|
||||
updatedAt: dict.updatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整字典树
|
||||
* @param query 查询参数
|
||||
* @returns Promise<GetDictTreeSuccessType>
|
||||
*/
|
||||
public async getDictTree(query: GetDictTreeQueryRequest): Promise<GetDictTreeSuccessType> {
|
||||
Logger.info(`获取完整字典树, 查询参数: ${JSON.stringify(query)}`);
|
||||
|
||||
const conditions = [];
|
||||
if (query.status && query.status !== 'all') {
|
||||
conditions.push(eq(sysDict.status, query.status));
|
||||
}
|
||||
if (query.isSystem && query.isSystem !== 'all') {
|
||||
conditions.push(eq(sysDict.isSystem, query.isSystem === 'true' ? 1 : 0));
|
||||
}
|
||||
|
||||
const dictList = await db()
|
||||
.select()
|
||||
.from(sysDict)
|
||||
.where(and(...conditions))
|
||||
.orderBy(asc(sysDict.sortOrder));
|
||||
|
||||
if (!dictList || dictList.length === 0) {
|
||||
return successResponse([], '获取完整字典树成功');
|
||||
}
|
||||
|
||||
type DictNode = Omit<(typeof dictList)[0], 'id' | 'pid' | 'isSystem'> & {
|
||||
id: string;
|
||||
pid: string;
|
||||
isSystem: boolean;
|
||||
children?: DictNode[];
|
||||
};
|
||||
|
||||
const nodeMap = new Map<string, DictNode>();
|
||||
const tree: DictNode[] = [];
|
||||
|
||||
for (const item of dictList) {
|
||||
const node: DictNode = {
|
||||
...item,
|
||||
id: String(item.id),
|
||||
pid: String(item.pid),
|
||||
isSystem: Boolean(item.isSystem),
|
||||
children: [],
|
||||
};
|
||||
nodeMap.set(node.id, node);
|
||||
}
|
||||
|
||||
for (const node of nodeMap.values()) {
|
||||
if (node.pid && node.pid !== '0' && nodeMap.has(node.pid)) {
|
||||
const parent = nodeMap.get(node.pid);
|
||||
if (parent && parent.children) {
|
||||
parent.children.push(node);
|
||||
}
|
||||
} else {
|
||||
tree.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return successResponse(tree, '获取完整字典树成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定字典树
|
||||
* @param params 路径参数
|
||||
* @param query 查询参数
|
||||
* @returns Promise<GetDictTreeByCodeSuccessType>
|
||||
*/
|
||||
public async getDictTreeByCode(
|
||||
{ code }: { code: string },
|
||||
query: GetDictTreeByCodeRequest,
|
||||
): Promise<GetDictTreeByCodeSuccessType> {
|
||||
Logger.info(`获取指定字典树: ${code}, 查询参数: ${JSON.stringify(query)}`);
|
||||
|
||||
// 1. 查找根节点
|
||||
const rootNodeArr = await db().select({ id: sysDict.id }).from(sysDict).where(eq(sysDict.code, code)).limit(1);
|
||||
if (rootNodeArr.length === 0) {
|
||||
throw new BusinessError(`字典代码不存在: ${code}`, 404);
|
||||
}
|
||||
const rootId = String(rootNodeArr[0]!.id);
|
||||
|
||||
// 2. 获取所有字典数据
|
||||
const allDicts = await db().select().from(sysDict).orderBy(asc(sysDict.sortOrder));
|
||||
|
||||
// 3. 在内存中构建完整的树
|
||||
type DictNode = Omit<(typeof allDicts)[0], 'id' | 'pid' | 'isSystem'> & {
|
||||
id: string;
|
||||
pid: string;
|
||||
isSystem: boolean;
|
||||
children?: DictNode[];
|
||||
};
|
||||
|
||||
const nodeMap = new Map<string, DictNode>();
|
||||
allDicts.forEach((item) => {
|
||||
nodeMap.set(String(item.id), {
|
||||
...item,
|
||||
id: String(item.id),
|
||||
pid: String(item.pid),
|
||||
isSystem: Boolean(item.isSystem),
|
||||
children: [],
|
||||
});
|
||||
});
|
||||
|
||||
// 4. 定义递归过滤和构建子树的函数
|
||||
const buildSubTree = (node: DictNode): DictNode | null => {
|
||||
// 应用过滤条件
|
||||
if (query.status && query.status !== 'all' && node.status !== query.status) {
|
||||
return null;
|
||||
}
|
||||
if (query.isSystem && query.isSystem !== 'all' && node.isSystem !== (query.isSystem === 'true')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = allDicts
|
||||
.filter((child) => String(child.pid) === node.id)
|
||||
.map((child) => buildSubTree(nodeMap.get(String(child.id))!))
|
||||
.filter((child): child is DictNode => child !== null);
|
||||
|
||||
if (children.length > 0) {
|
||||
node.children = children;
|
||||
} else {
|
||||
delete node.children;
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
// 5. 找到根节点并构建其子树
|
||||
const root = nodeMap.get(rootId);
|
||||
if (!root) {
|
||||
return successResponse([], '获取指定字典树成功');
|
||||
}
|
||||
|
||||
const finalTree = buildSubTree(root);
|
||||
|
||||
return successResponse(finalTree ? [finalTree] : [], '获取指定字典树成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新字典项
|
||||
* @param id 字典项ID
|
||||
* @param body 更新内容
|
||||
* @returns Promise<UpdateDictSuccessType>
|
||||
*/
|
||||
public async updateDict(id: string, body: UpdateDictBodyRequest): Promise<UpdateDictSuccessType> {
|
||||
const lock = await DistributedLockService.acquire({ key: `dict:update:${id}`, ttl: 10 });
|
||||
try {
|
||||
Logger.info(`更新字典项: ${id}, body: ${JSON.stringify(body)}`);
|
||||
|
||||
// 1. 检查字典项是否存在
|
||||
const existingArr = await db().select().from(sysDict).where(eq(sysDict.id, id)).limit(1);
|
||||
if (existingArr.length === 0) {
|
||||
throw new BusinessError(`字典项不存在: ${id}`, 404);
|
||||
}
|
||||
const existing = existingArr[0]!;
|
||||
|
||||
// 2. 唯一性校验
|
||||
if (body.code) {
|
||||
const existCode = await db()
|
||||
.select({ id: sysDict.id })
|
||||
.from(sysDict)
|
||||
.where(and(eq(sysDict.code, body.code), ne(sysDict.id, id)))
|
||||
.limit(1);
|
||||
if (existCode.length > 0) {
|
||||
throw new BusinessError(`字典代码已存在: ${body.code}`, 409);
|
||||
}
|
||||
}
|
||||
if (body.name) {
|
||||
const pid = body.pid ? String(body.pid) : existing.pid ? String(existing.pid) : '0';
|
||||
const existName = await db()
|
||||
.select({ id: sysDict.id })
|
||||
.from(sysDict)
|
||||
.where(and(eq(sysDict.name, body.name), eq(sysDict.pid, pid), ne(sysDict.id, id)))
|
||||
.limit(1);
|
||||
if (existName.length > 0) {
|
||||
throw new BusinessError(`同级下字典名称已存在: ${body.name}`, 409);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 构建更新数据
|
||||
const updateData: Partial<typeof existing> = {};
|
||||
for (const key in body) {
|
||||
if (Object.prototype.hasOwnProperty.call(body, key) && body[key as keyof typeof body] !== undefined) {
|
||||
if (key === 'isSystem') {
|
||||
(updateData as any)[key] = body[key] ? 1 : 0;
|
||||
} else {
|
||||
(updateData as any)[key] = body[key as keyof typeof body];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return successResponse(
|
||||
{
|
||||
...existing,
|
||||
id: String(existing.id),
|
||||
pid: String(existing.pid),
|
||||
isSystem: Boolean(existing.isSystem),
|
||||
},
|
||||
'未提供任何更新内容',
|
||||
);
|
||||
}
|
||||
|
||||
// 4. 执行更新
|
||||
await db().update(sysDict).set(updateData).where(eq(sysDict.id, id));
|
||||
|
||||
// 5. 查询并返回更新后的数据
|
||||
const updatedArr = await db().select().from(sysDict).where(eq(sysDict.id, id)).limit(1);
|
||||
const updated = updatedArr[0]!;
|
||||
|
||||
return successResponse(
|
||||
{
|
||||
...updated,
|
||||
id: String(updated.id),
|
||||
pid: String(updated.pid),
|
||||
isSystem: Boolean(updated.isSystem),
|
||||
},
|
||||
'更新字典项成功',
|
||||
);
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典项排序
|
||||
* @param body SortDictRequest 排序请求参数
|
||||
* @returns Promise<SortDictSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
* @description 处理字典项的排序和移动(改变父级),使用原生SQL保证性能和类型安全
|
||||
*/
|
||||
public async sortDict(body: SortDictRequest): Promise<SortDictSuccessType> {
|
||||
const lock = await DistributedLockService.acquire({ key: `dict:sort`, ttl: 30 });
|
||||
try {
|
||||
const { items } = body;
|
||||
if (!items || items.length === 0) {
|
||||
throw new BusinessError('排序项列表不能为空', 400);
|
||||
}
|
||||
|
||||
const itemIds = items.map((item) => BigInt(item.id));
|
||||
const parentIds = [
|
||||
...new Set(
|
||||
items
|
||||
.map((item) => item.pid)
|
||||
.filter((pid) => pid !== 0 && pid !== '0')
|
||||
.map((pid) => BigInt(pid as string)),
|
||||
),
|
||||
];
|
||||
|
||||
try {
|
||||
await db().transaction(async (tx) => {
|
||||
// 1. 使用原生SQL验证所有待排序的字典项是否存在
|
||||
if (itemIds.length > 0) {
|
||||
const result: any[] = await tx.execute(
|
||||
sql`SELECT id FROM sys_dict WHERE id IN ${itemIds}`,
|
||||
);
|
||||
const existingIds = result[0].map((row: { id: bigint }) => row.id);
|
||||
if (existingIds.length !== itemIds.length) {
|
||||
const foundIds = new Set(existingIds);
|
||||
const notFoundIds = itemIds.filter((id) => !foundIds.has(id));
|
||||
Logger.warn(`部分字典项不存在:${notFoundIds.join(', ')}`);
|
||||
throw new BusinessError(`部分字典项不存在: ${notFoundIds.join(', ')}`, 404);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 使用原生SQL验证所有父级字典是否存在
|
||||
if (parentIds.length > 0) {
|
||||
const result: any[] = await tx.execute(
|
||||
sql`SELECT id FROM sys_dict WHERE id IN ${parentIds}`,
|
||||
);
|
||||
const existingParentIds = result[0].map((row: { id: bigint }) => row.id);
|
||||
if (existingParentIds.length !== parentIds.length) {
|
||||
const foundParentIds = new Set(existingParentIds);
|
||||
const notFoundParentIds = parentIds.filter((id) => !foundParentIds.has(id));
|
||||
Logger.warn(`部分父级字典不存在:${notFoundParentIds.join(', ')}`);
|
||||
throw new BusinessError(
|
||||
`部分父级字典不存在: ${notFoundParentIds.join(', ')}`,
|
||||
404,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 使用原生SQL构建并执行批量更新
|
||||
if (items.length > 0) {
|
||||
const pidSqlChunks = items.map(
|
||||
(item) =>
|
||||
sql`WHEN ${BigInt(item.id)} THEN ${
|
||||
item.pid === 0 || item.pid === '0' ? BigInt(0) : BigInt(item.pid)
|
||||
}`,
|
||||
);
|
||||
const sortOrderSqlChunks = items.map(
|
||||
(item) => sql`WHEN ${BigInt(item.id)} THEN ${item.sortOrder}`,
|
||||
);
|
||||
|
||||
const finalUpdateQuery = sql`
|
||||
UPDATE sys_dict
|
||||
SET
|
||||
pid = CASE id ${sql.join(pidSqlChunks, sql` `)} END,
|
||||
sort_order = CASE id ${sql.join(sortOrderSqlChunks, sql` `)} END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id IN ${itemIds}
|
||||
`;
|
||||
await tx.execute(finalUpdateQuery);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof BusinessError) {
|
||||
throw error;
|
||||
}
|
||||
throw new BusinessError('字典项排序失败,请稍后重试', 500);
|
||||
}
|
||||
|
||||
Logger.info('字典项排序成功');
|
||||
return successResponse(null, '字典项排序成功');
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典项(软删除)
|
||||
* @param params DeleteDictRequest 删除请求参数 { id, cascade }
|
||||
* @returns Promise<DeleteDictSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
* @description 软删除字典项,可选择级联删除子项
|
||||
*/
|
||||
public async deleteDict({
|
||||
id,
|
||||
cascade,
|
||||
}: DeleteDictRequest): Promise<DeleteDictSuccessType> {
|
||||
const lock = await DistributedLockService.acquire({ key: `dict:delete:${id}`, ttl: 10 });
|
||||
try {
|
||||
const dictId = BigInt(id);
|
||||
Logger.info(`请求删除字典项: ${dictId}, 是否级联: ${!!cascade}`);
|
||||
|
||||
try {
|
||||
await db().transaction(async (tx) => {
|
||||
// 1. 查找字典项
|
||||
const dictResult: any[] = await tx.execute(
|
||||
sql`SELECT * FROM sys_dict WHERE id = ${dictId} LIMIT 1`,
|
||||
);
|
||||
const dictToDelete = dictResult[0] ? dictResult[0][0] : null;
|
||||
|
||||
if (!dictToDelete) {
|
||||
Logger.warn(`删除失败,字典项不存在: ${dictId}`);
|
||||
throw new BusinessError('字典项不存在', 404);
|
||||
}
|
||||
|
||||
// 2. 系统字典不允许删除
|
||||
if (dictToDelete.is_system) {
|
||||
Logger.warn(`删除失败,系统字典不允许删除: ${dictId}`);
|
||||
throw new BusinessError('系统字典不允许删除', 409);
|
||||
}
|
||||
|
||||
// 3. 查找所有子孙节点
|
||||
const childrenIds: bigint[] = [];
|
||||
if (cascade) {
|
||||
const allDicts = await tx.query.sysDict.findMany({
|
||||
columns: { id: true, pid: true },
|
||||
});
|
||||
const allDictsBigInt = allDicts.map((d) => ({
|
||||
id: BigInt(d.id),
|
||||
pid: d.pid ? BigInt(d.pid) : null,
|
||||
}));
|
||||
this.findAllChildrenIds(dictId, allDictsBigInt, childrenIds);
|
||||
} else {
|
||||
// 如果不级联,检查是否有直接子节点
|
||||
const childResult: any[] = await tx.execute(
|
||||
sql`SELECT id FROM sys_dict WHERE pid = ${dictId} LIMIT 1`,
|
||||
);
|
||||
if (childResult[0] && childResult[0].length > 0) {
|
||||
Logger.warn(`删除失败,存在子字典项,且未启用级联删除: ${dictId}`);
|
||||
throw new BusinessError(
|
||||
'存在子字典项,无法删除,请先删除子项或使用级联删除',
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const idsToDelete = [dictId, ...childrenIds];
|
||||
|
||||
Logger.info(`准备删除以下字典项ID: ${idsToDelete.join(', ')}`);
|
||||
|
||||
// 4. 使用原生SQL执行软删除
|
||||
if (idsToDelete.length > 0) {
|
||||
await tx.execute(
|
||||
sql`UPDATE sys_dict SET status = 'inactive' WHERE id IN ${idsToDelete}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof BusinessError) {
|
||||
throw error;
|
||||
}
|
||||
throw new BusinessError('字典项删除失败,请稍后重试', 500);
|
||||
}
|
||||
|
||||
Logger.info(`字典项 ${dictId} 及关联子项(如有)已成功软删除`);
|
||||
return successResponse(null, '字典项删除成功');
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归查找所有子孙节点的ID
|
||||
* @param parentId 父级ID
|
||||
* @param allItems 所有字典项列表
|
||||
* @param childrenIds 存储子孙节点ID的数组
|
||||
*/
|
||||
private findAllChildrenIds(
|
||||
parentId: bigint,
|
||||
allItems: { id: bigint; pid: bigint | null }[],
|
||||
childrenIds: bigint[],
|
||||
) {
|
||||
const children = allItems.filter((item) => item.pid === parentId);
|
||||
for (const child of children) {
|
||||
childrenIds.push(child.id);
|
||||
this.findAllChildrenIds(child.id, allItems, childrenIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,253 +0,0 @@
|
||||
# 字典模块测试用例文档
|
||||
|
||||
## 1. 创建字典项接口 (POST /api/dict)
|
||||
|
||||
### 场景1: 成功创建顶级字典项
|
||||
|
||||
- **名称**: 成功创建一个顶级的、无父级的字典项
|
||||
- **前置条件**: 数据库中不存在 code 为 `test_root` 的字典项
|
||||
- **请求方法**: `POST`
|
||||
- **请求路径**: `/api/dict`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"code": "test_root",
|
||||
"name": "测试顶级字典",
|
||||
"description": "这是一个用于测试的顶级字典项",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体包含创建成功的字典项信息,`pid` 为 '0',`level` 为 1。
|
||||
- 数据库中存在该条记录。
|
||||
|
||||
### 场景2: 成功创建子级字典项
|
||||
|
||||
- **名称**: 成功创建一个子级的字典项
|
||||
- **前置条件**:
|
||||
1. 数据库中存在一个 code 为 `test_root` 的字典项,其 id 为 `100`。
|
||||
2. 数据库中不存在 code 为 `test_child` 的字典项。
|
||||
- **请求方法**: `POST`
|
||||
- **请求路径**: `/api/dict`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"code": "test_child",
|
||||
"name": "测试子级字典",
|
||||
"pid": "100",
|
||||
"description": "这是一个用于测试的子级字典项",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体包含创建成功的字典项信息,`pid` 为 '100',`level` 为父级 level + 1。
|
||||
- 数据库中存在该条记录。
|
||||
|
||||
### 场景3: 失败 - code冲突
|
||||
|
||||
- **名称**: 因 code 重复导致创建失败
|
||||
- **前置条件**: 数据库中已存在 code 为 `test_root` 的字典项。
|
||||
- **请求方法**: `POST`
|
||||
- **请求路径**: `/api/dict`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"code": "test_root",
|
||||
"name": "重复Code测试"
|
||||
}
|
||||
```
|
||||
- **预期响应 (409 Conflict)**:
|
||||
- 响应体包含错误信息,提示 "字典代码已存在"。
|
||||
|
||||
### 场景4: 失败 - 父级不存在
|
||||
|
||||
- **名称**: 因父级 ID 不存在导致创建失败
|
||||
- **前置条件**: -
|
||||
- **请求方法**: `POST`
|
||||
- **请求路径**: `/api/dict`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"code": "test_no_parent",
|
||||
"name": "无效父级测试",
|
||||
"pid": "99999"
|
||||
}
|
||||
```
|
||||
- **预期响应 (404 Not Found)**:
|
||||
- 响应体包含错误信息,提示 "父级字典不存在"。
|
||||
|
||||
## 2. 获取字典项内容接口 (GET /api/dict/:id)
|
||||
|
||||
### 场景1: 成功获取存在的字典项
|
||||
|
||||
- **名称**: 根据存在的 ID 成功获取字典项
|
||||
- **前置条件**: 数据库中存在一个 id 为 `100` 的字典项。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/100`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体包含 id 为 `100` 的字典项的完整信息。
|
||||
|
||||
### 场景2: 失败 - 字典项不存在
|
||||
|
||||
- **名称**: 因 ID 不存在导致获取失败
|
||||
- **前置条件**: 数据库中不存在 id 为 `99999` 的字典项。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/99999`
|
||||
- **预期响应 (404 Not Found)**:
|
||||
- 响应体包含错误信息,提示 "字典项不存在"。
|
||||
|
||||
### 场景3: 失败 - ID格式错误
|
||||
|
||||
- **名称**: 因 ID 格式无效(非数字)导致获取失败
|
||||
- **前置条件**: -
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/abc`
|
||||
- **预期响应 (400 Bad Request / 422 Unprocessable Entity)**:
|
||||
- 响应体包含参数验证错误信息。
|
||||
|
||||
## 3. 获取完整字典树接口 (GET /api/dict/tree)
|
||||
|
||||
### 场景1: 成功获取所有字典
|
||||
|
||||
- **名称**: 成功获取完整的字典树结构
|
||||
- **前置条件**: 数据库中存在多个字典项,可以构成树形结构。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体 `data` 是一个数组,每个元素是一个顶层字典节点。
|
||||
- 每个节点包含 `children` 数组,递归地包含其子节点。
|
||||
- 节点按 `sortOrder` 排序。
|
||||
|
||||
### 场景2: 根据状态过滤 (active)
|
||||
|
||||
- **名称**: 获取所有状态为 'active' 的字典树
|
||||
- **前置条件**: 数据库中同时存在 'active' 和 'inactive' 状态的字典项。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree?status=active`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应的树结构中只包含 `status` 为 'active' 的节点。
|
||||
|
||||
### 场景3: 根据系统字典过滤 (true)
|
||||
|
||||
- **名称**: 获取所有系统字典的字典树
|
||||
- **前置条件**: 数据库中同时存在系统字典 (`is_system`=true) 和非系统字典 (`is_system`=false)。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree?is_system=true`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应的树结构中只包含 `is_system` 为 `true` 的节点。
|
||||
|
||||
### 场景4: 组合过滤
|
||||
|
||||
- **名称**: 获取所有状态为 'active' 且非系统字典的字典树
|
||||
- **前置条件**: 数据库中存在满足各种组合条件的字典项。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree?status=active&is_system=false`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应的树结构中只包含 `status` 为 'active' 且 `is_system` 为 `false` 的节点。
|
||||
|
||||
### 场景5: 数据库为空
|
||||
|
||||
- **名称**: 在没有字典数据的情况下获取字典树
|
||||
- **前置条件**: 数据库 `sys_dict` 表为空。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体 `data` 是一个空数组 `[]`。
|
||||
|
||||
## 4. 获取指定字典树接口 (GET /api/dict/tree/:code)
|
||||
|
||||
### 场景1: 成功获取存在的字典树
|
||||
|
||||
- **名称**: 根据存在的 code 成功获取指定的字典子树
|
||||
- **前置条件**: 数据库中存在一个 code 为 `user_gender` 的字典项,且它有若干子项。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree/user_gender`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体 `data` 是一个数组,仅包含一个根节点(即 `user_gender` 字典项)。
|
||||
- 该根节点包含其所有的子孙节点,形成一棵完整的子树。
|
||||
|
||||
### 场景2: 失败 - code 不存在
|
||||
|
||||
- **名称**: 因 code 不存在导致获取失败
|
||||
- **前置条件**: 数据库中不存在 code 为 `non_existent_code` 的字典项。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree/non_existent_code`
|
||||
- **预期响应 (404 Not Found)**:
|
||||
- 响应体包含错误信息,提示 "字典代码不存在"。
|
||||
|
||||
### 场景3: 带查询参数过滤
|
||||
|
||||
- **名称**: 获取指定字典树并根据状态过滤
|
||||
- **前置条件**: `user_gender` 字典树中,部分子项的状态为 `inactive`。
|
||||
- **请求方法**: `GET`
|
||||
- **请求路径**: `/api/dict/tree/user_gender?status=active`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 返回的 `user_gender` 子树中,只包含状态为 `active` 的节点。
|
||||
|
||||
## 5. 更新字典项内容接口 (PUT /api/dict/:id)
|
||||
|
||||
### 场景1: 成功更新部分字段
|
||||
|
||||
- **名称**: 成功更新一个字典项的名称和描述
|
||||
- **前置条件**: 数据库中存在一个 id 为 `101` 的字典项。
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/101`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"name": "更新后的字典名称",
|
||||
"description": "这是更新后的描述信息。"
|
||||
}
|
||||
```
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体包含更新后的完整字典项信息。
|
||||
- 数据库中对应记录的 `name` 和 `description` 字段已更新。
|
||||
|
||||
### 场景2: 失败 - ID 不存在
|
||||
|
||||
- **名称**: 因 ID 不存在导致更新失败
|
||||
- **前置条件**: 数据库中不存在 id 为 `99999` 的字典项。
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/99999`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"name": "任意名称"
|
||||
}
|
||||
```
|
||||
- **预期响应 (404 Not Found)**:
|
||||
- 响应体包含错误信息,提示 "字典项不存在"。
|
||||
|
||||
### 场景3: 失败 - code 冲突
|
||||
|
||||
- **名称**: 更新时提供的 code 与其他字典项冲突
|
||||
- **前置条件**:
|
||||
1. 数据库中存在 id 为 `101` 的字典项 (code: `dict_a`)。
|
||||
2. 数据库中存在另一个 id 为 `102` 的字典项 (code: `dict_b`)。
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/101`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"code": "dict_b"
|
||||
}
|
||||
```
|
||||
- **预期响应 (409 Conflict)**:
|
||||
- 响应体包含错误信息,提示 "字典代码已存在"。
|
||||
|
||||
### 场景4: 失败 - name 同级冲突
|
||||
|
||||
- **名称**: 更新时提供的 name 与同级其他字典项冲突
|
||||
- **前置条件**:
|
||||
1. 数据库中存在一个父id为 `100` 的字典项 (id: `101`, name: `name_a`)。
|
||||
2. 数据库中存在另一个父id也为 `100` 的字典项 (id: `102`, name: `name_b`)。
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/101`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"name": "name_b"
|
||||
}
|
||||
```
|
||||
- **预期响应 (409 Conflict)**:
|
||||
- 响应体包含错误信息,提示 "同级下字典名称已存在"。
|
@ -1,169 +0,0 @@
|
||||
- **预期响应 (409 Conflict)**:
|
||||
- 响应体包含错误信息,提示 "同级下字典名称已存在"。
|
||||
## 6. 字典项排序接口 (PUT /api/dict/sort)
|
||||
|
||||
### 场景1: 成功 - 同级排序
|
||||
|
||||
- **名称**: 成功对同一父级下的多个字典项进行重新排序
|
||||
- **前置条件**:
|
||||
- 字典项A (id: 201, pid: 200, sortOrder: 1)
|
||||
- 字典项B (id: 202, pid: 200, sortOrder: 2)
|
||||
- 字典项C (id: 203, pid: 200, sortOrder: 3)
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/sort`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{ "id": "201", "pid": "200", "sortOrder": 3 },
|
||||
{ "id": "202", "pid": "200", "sortOrder": 1 },
|
||||
{ "id": "203", "pid": "200", "sortOrder": 2 }
|
||||
]
|
||||
}
|
||||
```
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体 data 为 `null`,message 为 "字典项排序成功"。
|
||||
- 数据库中,字典项A, B, C 的 `sortOrder` 分别更新为 3, 1, 2。
|
||||
|
||||
### 场景2: 成功 - 移动节点(跨父级)
|
||||
|
||||
- **名称**: 成功将一个字典项移动到另一个父级下
|
||||
- **前置条件**:
|
||||
- 字典项A (id: 201, pid: 200, sortOrder: 1)
|
||||
- 父字典X (id: 200)
|
||||
- 父字典Y (id: 300)
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/sort`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{ "id": "201", "pid": "300", "sortOrder": 5 }
|
||||
]
|
||||
}
|
||||
```
|
||||
- **预期响应 (200 OK)**:
|
||||
- 数据库中,字典项A 的 `pid` 更新为 300,`sortOrder` 更新为 5。
|
||||
|
||||
### 场景3: 成功 - 移动节点到根级
|
||||
|
||||
- **名称**: 成功将一个字典项移动到根级
|
||||
- **前置条件**:
|
||||
- 字典项A (id: 201, pid: 200, sortOrder: 1)
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/sort`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{ "id": "201", "pid": 0, "sortOrder": 10 }
|
||||
]
|
||||
}
|
||||
```
|
||||
- **预期响应 (200 OK)**:
|
||||
- 数据库中,字典项A 的 `pid` 更新为 0,`sortOrder` 更新为 10。
|
||||
|
||||
### 场景4: 失败 - 字典项ID不存在
|
||||
|
||||
- **名称**: 因请求中包含不存在的字典项ID而失败
|
||||
- **前置条件**: 数据库中不存在 id 为 `999` 的字典项。
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/sort`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{ "id": "999", "pid": "200", "sortOrder": 1 }
|
||||
]
|
||||
}
|
||||
```
|
||||
- **预期响应 (404 Not Found)**:
|
||||
- 响应体包含错误信息,提示 "部分字典项不存在"。
|
||||
|
||||
### 场景5: 失败 - 父级ID不存在
|
||||
|
||||
- **名称**: 因请求中包含不存在的父级ID而失败
|
||||
- **前置条件**:
|
||||
- 字典项A (id: 201) 存在。
|
||||
- 数据库中不存在 id 为 `998` 的父级字典项。
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/sort`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{ "id": "201", "pid": "998", "sortOrder": 1 }
|
||||
]
|
||||
}
|
||||
```
|
||||
- **预期响应 (404 Not Found)**:
|
||||
- 响应体包含错误信息,提示 "部分父级字典不存在"。
|
||||
|
||||
### 场景6: 失败 - 请求体为空
|
||||
|
||||
- **名称**: 因请求体中的 `items` 数组为空导致失败
|
||||
- **前置条件**: -
|
||||
- **请求方法**: `PUT`
|
||||
- **请求路径**: `/api/dict/sort`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"items": []
|
||||
}
|
||||
```
|
||||
- **预期响应 (400 Bad Request)**:
|
||||
- 响应体包含错误信息,提示 "排序项列表不能为空"。
|
||||
## 7. 删除字典项接口 (DELETE /api/dict/:id)
|
||||
|
||||
### 场景1: 成功 - 删除无子项的节点
|
||||
|
||||
- **名称**: 成功软删除一个没有子节点的字典项
|
||||
- **前置条件**:
|
||||
- 字典项A (id: 401, pid: 400) 存在,且没有子节点。
|
||||
- 字典项A的`is_system`为`false`。
|
||||
- **请求方法**: `DELETE`
|
||||
- **请求路径**: `/api/dict/401`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 响应体 data 为 `null`,message 为 "字典项删除成功"。
|
||||
- 数据库中,字典项A 的 `status` 更新为 `inactive`。
|
||||
|
||||
### 场景2: 成功 - 级联删除有子项的节点
|
||||
|
||||
- **名称**: 成功使用级联方式软删除一个有子节点的字典项
|
||||
- **前置条件**:
|
||||
- 字典项A (id: 401, pid: 400) 存在。
|
||||
- 字典项B (id: 402, pid: 401) 是A的子节点。
|
||||
- 字典项C (id: 403, pid: 402) 是B的子节点。
|
||||
- **请求方法**: `DELETE`
|
||||
- **请求路径**: `/api/dict/401?cascade=true`
|
||||
- **预期响应 (200 OK)**:
|
||||
- 数据库中,字典项A, B, C 的 `status` 都更新为 `inactive`。
|
||||
|
||||
### 场景3: 失败 - 删除有子项的节点(不使用级联)
|
||||
|
||||
- **名称**: 在不提供`cascade`参数的情况下,尝试删除一个有子节点的字典项而失败
|
||||
- **前置条件**:
|
||||
- 字典项A (id: 401, pid: 400) 存在。
|
||||
- 字典项B (id: 402, pid: 401) 是A的子节点。
|
||||
- **请求方法**: `DELETE`
|
||||
- **请求路径**: `/api/dict/401`
|
||||
- **预期响应 (400 Bad Request)**:
|
||||
- 响应体包含错误信息,提示 "存在子字典项,无法删除..."。
|
||||
|
||||
### 场景4: 失败 - 删除系统字典
|
||||
|
||||
- **名称**: 尝试删除一个被标记为系统字典的项而失败
|
||||
- **前置条件**: 字典项A (id: 500) 存在,且其 `is_system` 字段为 `true`。
|
||||
- **请求方法**: `DELETE`
|
||||
- **请求路径**: `/api/dict/500`
|
||||
- **预期响应 (409 Conflict)**:
|
||||
- 响应体包含错误信息,提示 "系统字典不允许删除"。
|
||||
|
||||
### 场景5: 失败 - 字典项ID不存在
|
||||
|
||||
- **名称**: 尝试删除一个不存在的字典项ID
|
||||
- **前置条件**: 数据库中不存在 id 为 `9999` 的字典项。
|
||||
- **请求方法**: `DELETE`
|
||||
- **请求路径**: `/api/dict/9999`
|
||||
- **预期响应 (404 Not Found)**:
|
||||
- 响应体包含错误信息,提示 "字典项不存在"。
|
@ -75,14 +75,6 @@ export const errorHandlerPlugin = (app: Elysia) =>
|
||||
errors: error.message || error.response.message || error.response,
|
||||
};
|
||||
}
|
||||
case 404: {
|
||||
set.status = code;
|
||||
return {
|
||||
code: error.code,
|
||||
message: '资源未找到',
|
||||
errors: error.message || error.response.message || error.response,
|
||||
};
|
||||
}
|
||||
case 408: {
|
||||
set.status = code;
|
||||
return {
|
||||
|
@ -12,7 +12,7 @@ CREATE TABLE `sys_dict` (
|
||||
`code` VARCHAR(50) NOT NULL COMMENT '字典代码,唯一标识',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '字典名称',
|
||||
`value` VARCHAR(200) NULL COMMENT '字典值(叶子节点才有值)',
|
||||
`description` VARCHAR(200) NULL COMMENT '字典描述',
|
||||
`description` VARCHAR(500) NULL COMMENT '字典描述',
|
||||
`icon` VARCHAR(100) NULL COMMENT '图标(CSS类名或图标路径)',
|
||||
`pid` BIGINT NULL DEFAULT 0 COMMENT '父级ID,0表示顶级',
|
||||
`level` INT NOT NULL DEFAULT 1 COMMENT '层级深度,1为顶级',
|
||||
@ -101,63 +101,63 @@ CREATE TABLE `sys_dict` (
|
||||
|
||||
### 阶段2:字典模块核心接口开发
|
||||
|
||||
- [x] 2.0 创建字典项接口 (POST /api/dict)
|
||||
- [x] 2.1 生成接口业务逻辑文档,写入 `dict.docs.md`
|
||||
- [x] 2.2 创建 `dict.schema.ts` - 定义创建字典项Schema
|
||||
- [x] 2.3 创建 `dict.response.ts` - 定义创建字典项响应格式
|
||||
- [x] 2.4 创建 `dict.service.ts` - 实现创建字典项业务逻辑
|
||||
- [x] 2.5 创建 `dict.controller.ts` - 实现创建字典项路由
|
||||
- [x] 2.6 创建 `dict.test.md` - 编写创建字典项测试用例
|
||||
- [ ] 2.0 创建字典项接口 (POST /api/dict)
|
||||
- [ ] 2.1 生成接口业务逻辑文档,写入 `dict.docs.md`
|
||||
- [ ] 2.2 创建 `dict.schema.ts` - 定义创建字典项Schema
|
||||
- [ ] 2.3 创建 `dict.response.ts` - 定义创建字典项响应格式
|
||||
- [ ] 2.4 创建 `dict.service.ts` - 实现创建字典项业务逻辑
|
||||
- [ ] 2.5 创建 `dict.controller.ts` - 实现创建字典项路由
|
||||
- [ ] 2.6 创建 `dict.test.md` - 编写创建字典项测试用例
|
||||
|
||||
- [x] 3.0 获取字典项内容接口 (GET /api/dict/:id)
|
||||
- [x] 3.1 更新 `dict.docs.md` - 添加获取字典项业务逻辑
|
||||
- [x] 3.2 扩展 `dict.schema.ts` - 定义获取字典项Schema
|
||||
- [x] 3.3 扩展 `dict.response.ts` - 定义获取字典项响应格式
|
||||
- [x] 3.4 扩展 `dict.service.ts` - 实现获取字典项业务逻辑
|
||||
- [x] 3.5 扩展 `dict.controller.ts` - 实现获取字典项路由
|
||||
- [x] 3.6 更新 `dict.test.md` - 添加获取字典项测试用例
|
||||
- [ ] 3.0 获取字典项内容接口 (GET /api/dict/:id)
|
||||
- [ ] 3.1 更新 `dict.docs.md` - 添加获取字典项业务逻辑
|
||||
- [ ] 3.2 扩展 `dict.schema.ts` - 定义获取字典项Schema
|
||||
- [ ] 3.3 扩展 `dict.response.ts` - 定义获取字典项响应格式
|
||||
- [ ] 3.4 扩展 `dict.service.ts` - 实现获取字典项业务逻辑
|
||||
- [ ] 3.5 扩展 `dict.controller.ts` - 实现获取字典项路由
|
||||
- [ ] 3.6 更新 `dict.test.md` - 添加获取字典项测试用例
|
||||
|
||||
- [x] 4.0 获取完整字典树接口 (GET /api/dict/tree)
|
||||
- [x] 4.1 更新 `dict.docs.md` - 添加获取完整字典树业务逻辑
|
||||
- [x] 4.2 扩展 `dict.schema.ts` - 定义获取字典树查询Schema
|
||||
- [x] 4.3 扩展 `dict.response.ts` - 定义字典树响应格式
|
||||
- [x] 4.4 扩展 `dict.service.ts` - 实现获取完整字典树业务逻辑
|
||||
- [x] 4.5 扩展 `dict.controller.ts` - 实现获取完整字典树路由
|
||||
- [x] 4.6 更新 `dict.test.md` - 添加获取完整字典树测试用例
|
||||
- [ ] 4.0 获取完整字典树接口 (GET /api/dict/tree)
|
||||
- [ ] 4.1 更新 `dict.docs.md` - 添加获取完整字典树业务逻辑
|
||||
- [ ] 4.2 扩展 `dict.schema.ts` - 定义获取字典树查询Schema
|
||||
- [ ] 4.3 扩展 `dict.response.ts` - 定义字典树响应格式
|
||||
- [ ] 4.4 扩展 `dict.service.ts` - 实现获取完整字典树业务逻辑
|
||||
- [ ] 4.5 扩展 `dict.controller.ts` - 实现获取完整字典树路由
|
||||
- [ ] 4.6 更新 `dict.test.md` - 添加获取完整字典树测试用例
|
||||
|
||||
- [x] 5.0 获取指定字典树接口 (GET /api/dict/tree/:code)
|
||||
- [x] 5.1 更新 `dict.docs.md` - 添加获取指定字典树业务逻辑
|
||||
- [x] 5.2 扩展 `dict.schema.ts` - 定义获取指定字典树Schema
|
||||
- [x] 5.3 扩展 `dict.response.ts` - 定义指定字典树响应格式
|
||||
- [x] 5.4 扩展 `dict.service.ts` - 实现获取指定字典树业务逻辑
|
||||
- [x] 5.5 扩展 `dict.controller.ts` - 实现获取指定字典树路由
|
||||
- [x] 5.6 更新 `dict.test.md` - 添加获取指定字典树测试用例
|
||||
- [ ] 5.0 获取指定字典树接口 (GET /api/dict/tree/:code)
|
||||
- [ ] 5.1 更新 `dict.docs.md` - 添加获取指定字典树业务逻辑
|
||||
- [ ] 5.2 扩展 `dict.schema.ts` - 定义获取指定字典树Schema
|
||||
- [ ] 5.3 扩展 `dict.response.ts` - 定义指定字典树响应格式
|
||||
- [ ] 5.4 扩展 `dict.service.ts` - 实现获取指定字典树业务逻辑
|
||||
- [ ] 5.5 扩展 `dict.controller.ts` - 实现获取指定字典树路由
|
||||
- [ ] 5.6 更新 `dict.test.md` - 添加获取指定字典树测试用例
|
||||
|
||||
### 阶段3:字典管理接口开发
|
||||
|
||||
- [x] 6.0 更新字典项内容接口 (PUT /api/dict/:id)
|
||||
- [x] 6.1 更新 `dict.docs.md` - 添加更新字典项业务逻辑
|
||||
- [x] 6.2 扩展 `dict.schema.ts` - 定义更新字典项Schema
|
||||
- [x] 6.3 扩展 `dict.response.ts` - 定义更新字典项响应格式
|
||||
- [x] 6.4 扩展 `dict.service.ts` - 实现更新字典项业务逻辑
|
||||
- [x] 6.5 扩展 `dict.controller.ts` - 实现更新字典项路由
|
||||
- [x] 6.6 更新 `dict.test.md` - 添加更新字典项测试用例
|
||||
- [ ] 6.0 更新字典项内容接口 (PUT /api/dict/:id)
|
||||
- [ ] 6.1 更新 `dict.docs.md` - 添加更新字典项业务逻辑
|
||||
- [ ] 6.2 扩展 `dict.schema.ts` - 定义更新字典项Schema
|
||||
- [ ] 6.3 扩展 `dict.response.ts` - 定义更新字典项响应格式
|
||||
- [ ] 6.4 扩展 `dict.service.ts` - 实现更新字典项业务逻辑
|
||||
- [ ] 6.5 扩展 `dict.controller.ts` - 实现更新字典项路由
|
||||
- [ ] 6.6 更新 `dict.test.md` - 添加更新字典项测试用例
|
||||
|
||||
- [x] 7.0 字典项排序接口 (PUT /api/dict/sort)
|
||||
- [x] 7.1 更新 `dict.docs.md` - 添加字典项排序业务逻辑
|
||||
- [x] 7.2 扩展 `dict.schema.ts` - 定义字典项排序Schema
|
||||
- [x] 7.3 扩展 `dict.response.ts` - 定义字典项排序响应格式
|
||||
- [x] 7.4 扩展 `dict.service.ts` - 实现字典项排序业务逻辑
|
||||
- [x] 7.5 扩展 `dict.controller.ts` - 实现字典项排序路由
|
||||
- [x] 7.6 在 `dict.test.md` 中添加排序接口测试用例
|
||||
- [ ] 7.0 字典项排序接口 (PUT /api/dict/sort)
|
||||
- [ ] 7.1 更新 `dict.docs.md` - 添加字典项排序业务逻辑
|
||||
- [ ] 7.2 扩展 `dict.schema.ts` - 定义字典项排序Schema
|
||||
- [ ] 7.3 扩展 `dict.response.ts` - 定义字典项排序响应格式
|
||||
- [ ] 7.4 扩展 `dict.service.ts` - 实现字典项排序业务逻辑
|
||||
- [ ] 7.5 扩展 `dict.controller.ts` - 实现字典项排序路由
|
||||
- [ ] 7.6 更新 `dict.test.md` - 添加字典项排序测试用例
|
||||
|
||||
- [x] 8.0 删除字典项接口 (DELETE /api/dict/:id)
|
||||
- [x] 8.1 更新 `dict.docs.md` - 添加删除字典项业务逻辑(软删除)
|
||||
- [x] 8.2 扩展 `dict.schema.ts` - 定义删除字典项Schema
|
||||
- [x] 8.3 扩展 `dict.response.ts` - 定义删除字典项响应格式
|
||||
- [x] 8.4 扩展 `dict.service.ts` - 实现删除字典项业务逻辑
|
||||
- [x] 8.5 扩展 `dict.controller.ts` - 实现删除字典项路由
|
||||
- [x] 8.6 在 `dict.test.md` 中添加删除接口测试用例
|
||||
- [ ] 8.0 删除字典项接口 (DELETE /api/dict/:id)
|
||||
- [ ] 8.1 更新 `dict.docs.md` - 添加删除字典项业务逻辑(软删除)
|
||||
- [ ] 8.2 扩展 `dict.schema.ts` - 定义删除字典项Schema
|
||||
- [ ] 8.3 扩展 `dict.response.ts` - 定义删除字典项响应格式
|
||||
- [ ] 8.4 扩展 `dict.service.ts` - 实现删除字典项业务逻辑
|
||||
- [ ] 8.5 扩展 `dict.controller.ts` - 实现删除字典项路由
|
||||
- [ ] 8.6 更新 `dict.test.md` - 添加删除字典项测试用例
|
||||
|
||||
### 阶段4:缓存机制和优化
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user