cursor-init/src/modules/dict/dict.response.ts
expressgy cd49a78678 feat(dict): 新增软删除字典项接口
- 实现了 DELETE /api/dict/:id 接口,用于软删除字典项。
- 支持 `cascade=true` 查询参数,用于级联软删除所有子孙节点。
- 添加了 DeleteDictSchema 用于请求验证。
- 添加了 DeleteDictResponsesSchema 用于 API 文档。
- 服务层实现包含对系统字典的保护、以及对有子节点的非级联删除的防护。
- 所有数据库查询和更新均使用原生 SQL 以规避 ORM 类型问题。
- 在控制器中添加了新路由。
- 在 dict.test.md 中为删除接口添加了全面的测试用例。
2025-07-07 21:59:52 +08:00

420 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file 字典模块响应格式定义
* @author AI Assistant
* @date 2024-12-19
* @lastEditor AI Assistant
* @lastEditTime 2025-01-07
* @description 定义字典模块的响应格式,包括创建字典项等
*/
import { t, type Static } from 'elysia';
import { responseWrapperSchema } from '@/utils/responseFormate';
/**
* 创建字典项成功响应数据结构
*/
export const CreateDictSuccessSchema = t.Object({
id: t.String({
description: '字典项IDbigint类型以字符串返回防止精度丢失',
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: '父级IDbigint类型以字符串返回',
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'],
}),
});
/**
* 创建字典项接口响应组合
* @description 用于Controller中定义所有可能的响应格式
*/
export const CreateDictResponsesSchema = {
200: responseWrapperSchema(CreateDictSuccessSchema),
409: responseWrapperSchema(
t.Object({
error: t.String({
description: '唯一性冲突',
examples: ['字典代码已存在', '字典名称在同级下已存在'],
}),
}),
),
400: responseWrapperSchema(
t.Object({
error: t.String({
description: '参数错误',
examples: ['参数校验失败', '父级字典状态非active', '字典层级超过限制'],
}),
}),
),
404: responseWrapperSchema(
t.Object({
error: t.String({
description: '资源不存在',
examples: ['父级字典不存在'],
}),
}),
),
500: responseWrapperSchema(
t.Object({
error: t.String({
description: '服务器错误',
examples: ['内部服务器错误'],
}),
}),
),
};
/** 创建字典项成功响应数据类型 */
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: '字典项IDbigint类型以字符串返回防止精度丢失',
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: '父级IDbigint类型以字符串返回',
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]>;