hl-api-changelog/changelogs/2026-04/27_breaking_resource_batch-delete-result-and-restaurant-json.md

5.5 KiB

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

改前响应

{ "code": 200, "data": null, "message": "成功" }

或一旦遇越权立即整体失败:

{ "code": 400, "data": null, "message": "仅创建者或超级管理员可删除该景区" }

改后响应

无论部分成功/全失败/全成功一律 code=200,业务结果在 data:

{
  "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 条因 [拼接原因] 跳过
    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 / null 都能存入 JSON 列(JacksonTypeHandler 自动序列化)。GET 也按 typeHandler 反序列化,前端拿到的字段类型与 PUT 时一致。

前端动作

必须

  • EditModal 提交时不要 JSON.stringify 包外层(以前为绕开 String 校验可能这么写过) — 直接把 List/Map 对象作为字段值即可
    // 改前(绕过 BUG 用)
    payload.featuredMenu = JSON.stringify([...]);  // ❌ 现在不需要了
    
    // 改后
    payload.featuredMenu = [...];  // ✅ 直接传 List
    
  • 详情渲染时 类型守卫:Array.isArray(featuredMenu) ? renderList(...) : renderText(...) — 因为历史数据可能是字符串,新数据是 List

建议

  • 把 textarea/RichText 编辑器升级为结构化菜单/段落编辑器,产出格式固定为:
    [
      {"name":"菜名","price":88,"images":[],"description":"..."},
      ...
    ]
    
  • 详情查询里这三字段已经是 List 结构,前端可直接 .map(item => ...) 渲染

3. 新增错误码 422 (来自 #1475 GlobalExceptionHandler)

后端今后遇到「Cannot create a JSON value from a string」类异常时返:

{
  "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