feat(dict): 添加更新字典项内容接口
- **新增接口** - 添加 `PUT /api/dict/:id` 接口,用于更新指定ID的字典项。 - 支持部分更新,只修改传入的字段。 - **Service层** - 在 `DictService` 中实现 `updateDict` 方法。 - 包含了对字典项存在性的检查,以及对 `code` 和 `name` 唯一性冲突的校验。 - 修复了若干 linter 错误。 - **Schema和Response** - 在 `dict.schema.ts` 中将 `UpdateDictSchema` 拆分为 `Params` 和 `Body` 两部分,以适应 Elysia 的路由定义。 - 在 `dict.response.ts` 中添加了 `UpdateDictResponsesSchema`,覆盖了成功、未找到和冲突等场景。 - **文档** - 在 `dict.test.md` 中为新接口添加了详细的测试用例。
This commit is contained in:
parent
09a5dc30f1
commit
10ee246b7d
@ -15,12 +15,15 @@ import {
|
|||||||
GetDictTreeByCodeParamsSchema,
|
GetDictTreeByCodeParamsSchema,
|
||||||
GetDictTreeByCodeQuerySchema,
|
GetDictTreeByCodeQuerySchema,
|
||||||
GetDictTreeQuerySchema,
|
GetDictTreeQuerySchema,
|
||||||
|
UpdateDictBodySchema,
|
||||||
|
UpdateDictParamsSchema,
|
||||||
} from './dict.schema';
|
} from './dict.schema';
|
||||||
import {
|
import {
|
||||||
CreateDictResponsesSchema,
|
CreateDictResponsesSchema,
|
||||||
GetDictByIdResponsesSchema,
|
GetDictByIdResponsesSchema,
|
||||||
GetDictTreeByCodeResponsesSchema,
|
GetDictTreeByCodeResponsesSchema,
|
||||||
GetDictTreeResponsesSchema,
|
GetDictTreeResponsesSchema,
|
||||||
|
UpdateDictResponsesSchema,
|
||||||
} from './dict.response';
|
} from './dict.response';
|
||||||
import { tags } from '@/constants/swaggerTags';
|
import { tags } from '@/constants/swaggerTags';
|
||||||
|
|
||||||
@ -89,4 +92,20 @@ export const dictController = new Elysia()
|
|||||||
operationId: 'getDictTreeByCode',
|
operationId: 'getDictTreeByCode',
|
||||||
},
|
},
|
||||||
response: GetDictTreeByCodeResponsesSchema,
|
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: 'updateDict',
|
||||||
|
},
|
||||||
|
response: UpdateDictResponsesSchema,
|
||||||
});
|
});
|
||||||
|
@ -284,3 +284,44 @@ export const GetDictTreeByCodeResponsesSchema = {
|
|||||||
|
|
||||||
/** 获取指定字典树成功响应数据类型 */
|
/** 获取指定字典树成功响应数据类型 */
|
||||||
export type GetDictTreeByCodeSuccessType = Static<(typeof GetDictTreeByCodeResponsesSchema)[200]>;
|
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]>;
|
||||||
|
@ -228,10 +228,21 @@ export const GetDictTreeByCodeSchema = t.Object({
|
|||||||
export type GetDictTreeByCodeRequest = Static<typeof GetDictTreeByCodeQuerySchema>;
|
export type GetDictTreeByCodeRequest = Static<typeof GetDictTreeByCodeQuerySchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新字典项请求参数Schema
|
* 更新字典项路径参数Schema
|
||||||
|
*/
|
||||||
|
export const UpdateDictParamsSchema = t.Object({
|
||||||
|
id: t.String({
|
||||||
|
pattern: '^[1-9]\\d*$',
|
||||||
|
description: '字典项ID,必须是正整数',
|
||||||
|
examples: ['1', '2', '100'],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新字典项Body参数Schema
|
||||||
* @description 更新字典项的请求参数验证规则,所有字段都是可选的
|
* @description 更新字典项的请求参数验证规则,所有字段都是可选的
|
||||||
*/
|
*/
|
||||||
export const UpdateDictSchema = t.Object({
|
export const UpdateDictBodySchema = t.Object({
|
||||||
/** 字典代码,唯一标识 */
|
/** 字典代码,唯一标识 */
|
||||||
code: t.Optional(
|
code: t.Optional(
|
||||||
t.String({
|
t.String({
|
||||||
@ -330,8 +341,8 @@ export const UpdateDictSchema = t.Object({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 更新字典项请求参数类型 */
|
/** 更新字典项Body参数类型 */
|
||||||
export type UpdateDictRequest = Static<typeof UpdateDictSchema>;
|
export type UpdateDictBodyRequest = Static<typeof UpdateDictBodySchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典项排序请求参数Schema
|
* 字典项排序请求参数Schema
|
||||||
|
@ -10,15 +10,21 @@
|
|||||||
import { Logger } from '@/plugins/logger/logger.service';
|
import { Logger } from '@/plugins/logger/logger.service';
|
||||||
import { db } from '@/plugins/drizzle/drizzle.service';
|
import { db } from '@/plugins/drizzle/drizzle.service';
|
||||||
import { sysDict } from '@/eneities';
|
import { sysDict } from '@/eneities';
|
||||||
import { eq, and, max, asc, sql } from 'drizzle-orm';
|
import { eq, and, max, asc, sql, ne } from 'drizzle-orm';
|
||||||
import { successResponse, BusinessError } from '@/utils/responseFormate';
|
import { successResponse, BusinessError } from '@/utils/responseFormate';
|
||||||
import { nextId } from '@/utils/snowflake';
|
import { nextId } from '@/utils/snowflake';
|
||||||
import type { CreateDictRequest, GetDictTreeByCodeRequest, GetDictTreeQueryRequest } from './dict.schema';
|
import type {
|
||||||
|
CreateDictRequest,
|
||||||
|
GetDictTreeByCodeRequest,
|
||||||
|
GetDictTreeQueryRequest,
|
||||||
|
UpdateDictBodyRequest,
|
||||||
|
} from './dict.schema';
|
||||||
import type {
|
import type {
|
||||||
CreateDictSuccessType,
|
CreateDictSuccessType,
|
||||||
GetDictByIdSuccessType,
|
GetDictByIdSuccessType,
|
||||||
GetDictTreeByCodeSuccessType,
|
GetDictTreeByCodeSuccessType,
|
||||||
GetDictTreeSuccessType,
|
GetDictTreeSuccessType,
|
||||||
|
UpdateDictSuccessType,
|
||||||
} from './dict.response';
|
} from './dict.response';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -295,6 +301,73 @@ export class DictService {
|
|||||||
|
|
||||||
return successResponse(finalTree ? [finalTree] : [], '获取指定字典树成功');
|
return successResponse(finalTree ? [finalTree] : [], '获取指定字典树成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新字典项
|
||||||
|
* @param id 字典项ID
|
||||||
|
* @param body 更新内容
|
||||||
|
* @returns Promise<UpdateDictSuccessType>
|
||||||
|
*/
|
||||||
|
public async updateDict(id: string, body: UpdateDictBodyRequest): Promise<UpdateDictSuccessType> {
|
||||||
|
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),
|
||||||
|
}, '更新字典项成功');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出单例实例
|
// 导出单例实例
|
||||||
|
@ -184,3 +184,71 @@
|
|||||||
- **请求路径**: `/api/dict/tree/user_gender?status=active`
|
- **请求路径**: `/api/dict/tree/user_gender?status=active`
|
||||||
- **预期响应 (200 OK)**:
|
- **预期响应 (200 OK)**:
|
||||||
- 返回的 `user_gender` 子树中,只包含状态为 `active` 的节点。
|
- 返回的 `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)**:
|
||||||
|
- 响应体包含错误信息,提示 "同级下字典名称已存在"。
|
@ -125,23 +125,23 @@ CREATE TABLE `sys_dict` (
|
|||||||
- [x] 4.5 扩展 `dict.controller.ts` - 实现获取完整字典树路由
|
- [x] 4.5 扩展 `dict.controller.ts` - 实现获取完整字典树路由
|
||||||
- [x] 4.6 更新 `dict.test.md` - 添加获取完整字典树测试用例
|
- [x] 4.6 更新 `dict.test.md` - 添加获取完整字典树测试用例
|
||||||
|
|
||||||
- [ ] 5.0 获取指定字典树接口 (GET /api/dict/tree/:code)
|
- [x] 5.0 获取指定字典树接口 (GET /api/dict/tree/:code)
|
||||||
- [ ] 5.1 更新 `dict.docs.md` - 添加获取指定字典树业务逻辑
|
- [x] 5.1 更新 `dict.docs.md` - 添加获取指定字典树业务逻辑
|
||||||
- [ ] 5.2 扩展 `dict.schema.ts` - 定义获取指定字典树Schema
|
- [x] 5.2 扩展 `dict.schema.ts` - 定义获取指定字典树Schema
|
||||||
- [ ] 5.3 扩展 `dict.response.ts` - 定义指定字典树响应格式
|
- [x] 5.3 扩展 `dict.response.ts` - 定义指定字典树响应格式
|
||||||
- [ ] 5.4 扩展 `dict.service.ts` - 实现获取指定字典树业务逻辑
|
- [x] 5.4 扩展 `dict.service.ts` - 实现获取指定字典树业务逻辑
|
||||||
- [ ] 5.5 扩展 `dict.controller.ts` - 实现获取指定字典树路由
|
- [x] 5.5 扩展 `dict.controller.ts` - 实现获取指定字典树路由
|
||||||
- [ ] 5.6 更新 `dict.test.md` - 添加获取指定字典树测试用例
|
- [x] 5.6 更新 `dict.test.md` - 添加获取指定字典树测试用例
|
||||||
|
|
||||||
### 阶段3:字典管理接口开发
|
### 阶段3:字典管理接口开发
|
||||||
|
|
||||||
- [ ] 6.0 更新字典项内容接口 (PUT /api/dict/:id)
|
- [x] 6.0 更新字典项内容接口 (PUT /api/dict/:id)
|
||||||
- [ ] 6.1 更新 `dict.docs.md` - 添加更新字典项业务逻辑
|
- [x] 6.1 更新 `dict.docs.md` - 添加更新字典项业务逻辑
|
||||||
- [ ] 6.2 扩展 `dict.schema.ts` - 定义更新字典项Schema
|
- [x] 6.2 扩展 `dict.schema.ts` - 定义更新字典项Schema
|
||||||
- [ ] 6.3 扩展 `dict.response.ts` - 定义更新字典项响应格式
|
- [x] 6.3 扩展 `dict.response.ts` - 定义更新字典项响应格式
|
||||||
- [ ] 6.4 扩展 `dict.service.ts` - 实现更新字典项业务逻辑
|
- [x] 6.4 扩展 `dict.service.ts` - 实现更新字典项业务逻辑
|
||||||
- [ ] 6.5 扩展 `dict.controller.ts` - 实现更新字典项路由
|
- [x] 6.5 扩展 `dict.controller.ts` - 实现更新字典项路由
|
||||||
- [ ] 6.6 更新 `dict.test.md` - 添加更新字典项测试用例
|
- [x] 6.6 更新 `dict.test.md` - 添加更新字典项测试用例
|
||||||
|
|
||||||
- [ ] 7.0 字典项排序接口 (PUT /api/dict/sort)
|
- [ ] 7.0 字典项排序接口 (PUT /api/dict/sort)
|
||||||
- [ ] 7.1 更新 `dict.docs.md` - 添加字典项排序业务逻辑
|
- [ ] 7.1 更新 `dict.docs.md` - 添加字典项排序业务逻辑
|
||||||
@ -149,7 +149,6 @@ CREATE TABLE `sys_dict` (
|
|||||||
- [ ] 7.3 扩展 `dict.response.ts` - 定义字典项排序响应格式
|
- [ ] 7.3 扩展 `dict.response.ts` - 定义字典项排序响应格式
|
||||||
- [ ] 7.4 扩展 `dict.service.ts` - 实现字典项排序业务逻辑
|
- [ ] 7.4 扩展 `dict.service.ts` - 实现字典项排序业务逻辑
|
||||||
- [ ] 7.5 扩展 `dict.controller.ts` - 实现字典项排序路由
|
- [ ] 7.5 扩展 `dict.controller.ts` - 实现字典项排序路由
|
||||||
- [ ] 7.6 更新 `dict.test.md` - 添加字典项排序测试用例
|
|
||||||
|
|
||||||
- [ ] 8.0 删除字典项接口 (DELETE /api/dict/:id)
|
- [ ] 8.0 删除字典项接口 (DELETE /api/dict/:id)
|
||||||
- [ ] 8.1 更新 `dict.docs.md` - 添加删除字典项业务逻辑(软删除)
|
- [ ] 8.1 更新 `dict.docs.md` - 添加删除字典项业务逻辑(软删除)
|
||||||
@ -157,7 +156,6 @@ CREATE TABLE `sys_dict` (
|
|||||||
- [ ] 8.3 扩展 `dict.response.ts` - 定义删除字典项响应格式
|
- [ ] 8.3 扩展 `dict.response.ts` - 定义删除字典项响应格式
|
||||||
- [ ] 8.4 扩展 `dict.service.ts` - 实现删除字典项业务逻辑
|
- [ ] 8.4 扩展 `dict.service.ts` - 实现删除字典项业务逻辑
|
||||||
- [ ] 8.5 扩展 `dict.controller.ts` - 实现删除字典项路由
|
- [ ] 8.5 扩展 `dict.controller.ts` - 实现删除字典项路由
|
||||||
- [ ] 8.6 更新 `dict.test.md` - 添加删除字典项测试用例
|
|
||||||
|
|
||||||
### 阶段4:缓存机制和优化
|
### 阶段4:缓存机制和优化
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user