docs(resource): Breaking - PR #1474+#1475 批删返回 VO + 餐厅 JSON 字段类型 Object
这个提交包含在:
父节点
2e16efad08
当前提交
82e204b058
@ -0,0 +1,170 @@
|
|||||||
|
# Breaking: 资源批删返回结构改变 + 餐厅 JSON 字段类型变更
|
||||||
|
|
||||||
|
**日期**: 2026-04-27
|
||||||
|
**PR**: #1474 (Closes #1472) + #1475 (Closes #1473)
|
||||||
|
**类型**: Breaking — 响应字段语义变化(软兼容)
|
||||||
|
**影响端**: 管理后台 (admin) — 8 个资源类的批量删除接口 + 餐厅详情/编辑页
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 批量删除接口响应结构改变 (#1474)
|
||||||
|
|
||||||
|
### 影响接口
|
||||||
|
|
||||||
|
| 接口 | path |
|
||||||
|
|---|---|
|
||||||
|
| 景区批删 | `DELETE /admin/scenic/spots/batch` |
|
||||||
|
| 酒店批删 | `DELETE /admin/hotel/items/batch` |
|
||||||
|
| 餐厅批删 | `DELETE /admin/restaurant/items/batch` |
|
||||||
|
| 活动批删 | `DELETE /admin/activity/items/batch` |
|
||||||
|
| 服务项批删 | `DELETE /admin/service/items/batch` |
|
||||||
|
| 工作人员批删 | `DELETE /admin/staff/items/batch` |
|
||||||
|
| 物资批删 | `DELETE /admin/supplies/items/batch` |
|
||||||
|
| 车辆批删 | `DELETE /admin/vehicle/models/batch` |
|
||||||
|
|
||||||
|
### 改前响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 200, "data": null, "message": "成功" }
|
||||||
|
```
|
||||||
|
|
||||||
|
或一旦遇越权立即整体失败:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 400, "data": null, "message": "仅创建者或超级管理员可删除该景区" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 改后响应
|
||||||
|
|
||||||
|
无论部分成功/全失败/全成功一律 `code=200`,业务结果在 `data`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"successCount": 3,
|
||||||
|
"deniedIds": [10001, 10002],
|
||||||
|
"deniedReasons": ["无权限", "审批进行中"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
|
||||||
|
- `successCount` — 真实删除成功的条数
|
||||||
|
- `deniedIds` — 被拒的 ID 列表(顺序与传入一致,但只含被拒的)
|
||||||
|
- `deniedReasons` — 与 `deniedIds` 等长的原因数组,**顺序一一对应**
|
||||||
|
|
||||||
|
### 取值集
|
||||||
|
|
||||||
|
`deniedReasons` 可能值:
|
||||||
|
|
||||||
|
| 文案 | 含义 |
|
||||||
|
|---|---|
|
||||||
|
| `记录不存在` | DB 找不到该 ID(可能已被他人删除) |
|
||||||
|
| `无权限` | 非 SUPER_ADMIN 且非创建者 |
|
||||||
|
| `已启用,不可删除` | 仅 hotel — 启用状态酒店不允许批删 |
|
||||||
|
| `审批进行中` | 仅 hotel — 审批未结束不允许批删 |
|
||||||
|
| `删除失败` | DB 操作异常兜底 |
|
||||||
|
|
||||||
|
### 前端建议
|
||||||
|
|
||||||
|
- **toast 提示** 改为「**已删除 N 条,M 条因 [拼接原因] 跳过**」
|
||||||
|
```js
|
||||||
|
const { successCount, deniedIds, deniedReasons } = res.data || {}
|
||||||
|
if (deniedIds?.length) {
|
||||||
|
const summary = [...new Set(deniedReasons)].join('/')
|
||||||
|
msg.success(`已删除 ${successCount} 条,${deniedIds.length} 条因 ${summary} 跳过`)
|
||||||
|
} else {
|
||||||
|
msg.success(`已删除 ${successCount} 条`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **软兼容**:不读 `data` 的老前端仍正常 toast 成功(code=200 还在),只是缺失精细提示
|
||||||
|
- 不再有「越权全失败」场景;原本「批删 OK 但实际只删了部分」的混淆消失
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 餐厅 JSON 字段类型变更 (#1475 P1)
|
||||||
|
|
||||||
|
### 影响接口
|
||||||
|
|
||||||
|
- `GET /admin/restaurant/item/{id}` (详情)
|
||||||
|
- `PUT /admin/restaurant/item/{id}` (修改)
|
||||||
|
- `POST /admin/restaurant/item` (新建)
|
||||||
|
- `GET /admin/restaurant` (列表 — 仅返回字段一致)
|
||||||
|
|
||||||
|
### 三字段类型 String → Object
|
||||||
|
|
||||||
|
| 字段 | 改前类型 | 改后类型 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `featuredMenu` | `String` (内含 JSON 字符串) | `Object` (List/Map/String 都可) | 招牌菜单 |
|
||||||
|
| `featureIntro` | `String` | `Object` | 餐厅特色 |
|
||||||
|
| `arrangement` | `String` | `Object` | 座位布局 |
|
||||||
|
|
||||||
|
### 修前的痛(蒋雨莲已踩生产)
|
||||||
|
|
||||||
|
前端 textarea 输出纯文本「招牌菜1\n招牌菜2」直接 PUT,后端 MySQL 抛 `Cannot create a JSON value from a string`,返回 400 `"保存失败,请检查输入或稍后重试"`。
|
||||||
|
|
||||||
|
### 修后
|
||||||
|
|
||||||
|
后端无论收到 String / List<Map> / Map / null 都能存入 JSON 列(`JacksonTypeHandler` 自动序列化)。GET 也按 typeHandler 反序列化,前端拿到的字段类型与 PUT 时一致。
|
||||||
|
|
||||||
|
### 前端动作
|
||||||
|
|
||||||
|
#### 必须 ✅
|
||||||
|
|
||||||
|
- **EditModal 提交时不要 `JSON.stringify`** 包外层(以前为绕开 String 校验可能这么写过) — 直接把 List/Map 对象作为字段值即可
|
||||||
|
```js
|
||||||
|
// 改前(绕过 BUG 用)
|
||||||
|
payload.featuredMenu = JSON.stringify([...]); // ❌ 现在不需要了
|
||||||
|
|
||||||
|
// 改后
|
||||||
|
payload.featuredMenu = [...]; // ✅ 直接传 List
|
||||||
|
```
|
||||||
|
- 详情渲染时 **类型守卫**:`Array.isArray(featuredMenu) ? renderList(...) : renderText(...)` — 因为历史数据可能是字符串,新数据是 List
|
||||||
|
|
||||||
|
#### 建议 ✅
|
||||||
|
|
||||||
|
- 把 textarea/RichText 编辑器升级为**结构化菜单/段落编辑器**,产出格式固定为:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"name":"菜名","price":88,"images":[],"description":"..."},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
- 详情查询里这三字段已经是 List<Map> 结构,前端可直接 `.map(item => ...)` 渲染
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 新增错误码 422 (来自 #1475 GlobalExceptionHandler)
|
||||||
|
|
||||||
|
后端今后遇到「Cannot create a JSON value from a string」类异常时返:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 422,
|
||||||
|
"message": "字段格式错误,请联系开发: 数据库 JSON 列收到了非 JSON 字符串(可能是 schema drift)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端可以显示「数据格式不对,请联系后端检查」之类的清晰提示,而不是兜底「保存失败」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 部署时机
|
||||||
|
|
||||||
|
- 测试服: 2026-04-27 15:46 已部署 hl-resource-service ✅
|
||||||
|
- 正式服: 待跟进部署后立即生效(无需 DDL)
|
||||||
|
|
||||||
|
## 兼容性总结
|
||||||
|
|
||||||
|
| 改动 | 老前端表现 | 新前端建议 |
|
||||||
|
|---|---|---|
|
||||||
|
| #1474 batchDelete 返回 VO | code=200 仍 toast 成功(不读 data) | 解析 `successCount/deniedIds/deniedReasons` 显示精细提示 |
|
||||||
|
| #1475 餐厅 JSON 字段 Object | 老接口收到 List 可能解析报错 | 加 `Array.isArray` 类型守卫;提交时不再 stringify |
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- 反馈人: 蒋雨莲 (橙子) 2026-04-27
|
||||||
|
- 后端 changelog: `hl-backend-changelog/changelogs/2026-04/2026-04-27_resource_batch-delete-resilient-and-restaurant-json-drift.md`
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户