文件
hl-api-changelog/changelogs-v2/2026-09/23_8226_车型大类中文名空白校验与字典白名单口径统一-修改接口-管理后台.md
T
2026-09-23 15:31:04 +08:00

14 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 8226 车型大类中文名空白校验收紧(全角空格等 Unicode 空白改为拒绝)+ 空名大类不再被挤出车型字典白名单 admin jw(GIT) 修改接口 deployed verified verified mmg 9e0897c2934a6720fb9cbab05b1e14394c74c8f6 2026-09-23 PR #8232 已合并 dev-v3(9231b8d8c),2026-09-23 14:30 滚动部署 TEST 双实例(检出 7a876ec57)。真实网关实测:新增/编辑车型大类 typeName 传全角空格、半角空格、空串、null 均返回 400「大类中文名不能为空」,库内零写入;读侧临时把 mpv 名称置空后,order-v3 车型白名单仍含 mpv(展示名回落为 mpv),验毕已原样还原。前端待办:车型大类表单的 typeName 必填校验需同样把全角空格视为空。前端已交付(2026-09-23):CategoryEditModal typeName 必填规则改 trim() 判空 validator,纯全角/半角空白提交前拦下(与后端 400 同口径),首尾带空格正常名放行且提交不 trim 原样发;新建组件 spec 4 例全绿。 2026-09-23 dev-v3

车队车型管理: 车型大类中文名空白校验收紧 + 空名大类不再被挤出字典白名单

存放目录: 二期(v3) → changelogs-v2/2026-09/

服务: hl-fleet-service PR: #8232 Issue: #8226 日期: 2026-09-23 影响范围: 管理后台「车型管理」新增/编辑车型大类表单的 typeName 校验;order-v3 用车需求车型字典白名单(582032)的数据源


⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)

  • 本次变化:新增/编辑车型大类时,typeName 如果全部由全角空格(U+3000)等 Unicode 空白组成,现在返回 400「大类中文名不能为空」。
  • 前端以前以为的:typeName 只要不是空串、不全是半角空格,后端就会接受。
  • 实际新行为:以前后端用 @NotBlank 校验,它按 String.trim() 判空,只去掉半角空白,所以全角空格能写进库。可车队给 order-v3 的车型白名单按 isBlank() 判空名,会把这种名字当成空的、把整个大类从白名单里丢掉,结果这个大类下的用车需求被 582032「不存在或已下线」 误拒。现在两处同时收口:写侧拒绝这种名字;读侧即使库里出现空名,也不再把该大类挤出白名单。

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 新增车型大类 POST /admin/fleet/vehicle-types 入参校验收紧 typeName 全为 Unicode 空白(含全角空格)改为 400
2 编辑车型大类 PUT /admin/fleet/vehicle-types/{typeId} 入参校验收紧 同上

三、接口详情

1. 新增车型大类 POST /admin/fleet/vehicle-types

VO: VehicleTypeSaveReqVO → VehicleTypeRespVO

使用场景

管理后台「车队 → 车型管理」新增一个车型大类(如 SUV系列 / 商务车)时调用。

入参

字段 位置 类型 必填 约束 说明
Authorization Header String ✅ - 管理端登录令牌
typeKey Body String ✅ 非空白,≤16,全局唯一(600101) 大类标识 key,如 suv
typeName Body String ✅ 至少含一个非空白字符(空白按 Unicode 判定,全角空格也算空白,本次收紧),≤64 大类中文名
icon Body String ❌ ≤64 图标组件名或 emoji
description Body String ❌ ≤256 大类描述
sortOrder Body Integer ✅ 非 null 排序权重,升序

出参 Result<VehicleTypeRespVO>

字段 类型 说明
id Long 大类 ID
typeKey String 大类 key
typeName String 大类中文名
icon String 图标
description String 描述
sortOrder Integer 排序权重
seatOptions Array<Integer> 座位数选项,新建恒为 []
modelCount Integer 型号数,新建恒为 0
inUseCount Integer 在役车辆数,新建恒为 0

请求示例

{
  "typeKey": "mpv",
  "typeName": "商务车",
  "icon": "",
  "description": "",
  "sortOrder": 2
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": "2064609312168083458",
    "typeKey": "mpv",
    "typeName": "商务车",
    "icon": "",
    "description": "",
    "sortOrder": 2,
    "seatOptions": [],
    "modelCount": 0,
    "inUseCount": 0
  },
  "success": true
}

空数据 / 降级响应

写接口没有空数据场景,也没有降级分支;校验不通过时零写入。

{ "code": 400, "message": "大类中文名不能为空", "data": null, "success": false }

错误响应

typeName 为 null、空串、纯半角空格、纯全角空格,一律返回同一条错误,且只报一条:

{ "code": 400, "message": "大类中文名不能为空", "data": null, "traceId": null, "success": false }
{ "code": 600101, "message": "车型大类标识已存在", "data": null, "success": false }

业务边界

  • 鉴权:未登录 → 业务码 401(「缺少有效的 Authorization 头」)。
  • 只拒绝全部由空白组成的名字;首尾带空格的正常名(如 " 商务车 ")照常放行,后端不做 trim。
  • 以前只有全角空格这一类会被放行,现在也拒绝;null、空串、半角空格在改前就被拒,行为不变。

2. 编辑车型大类 PUT /admin/fleet/vehicle-types/{typeId}

VO: VehicleTypeSaveReqVO → VehicleTypeRespVO

使用场景

管理后台「车型管理」编辑已有大类的名称、图标、描述或排序。typeKey 不可改,后端忽略入参中的 typeKey。

入参

字段 位置 类型 必填 约束 说明
Authorization Header String ✅ - 管理端登录令牌
typeId Path Long ✅ 大类须存在(600102) 大类 ID
typeName Body String ✅ 至少含一个非空白字符(空白按 Unicode 判定,全角空格也算空白,本次收紧),≤64 大类中文名
icon Body String ❌ ≤64;不传则不改 图标
description Body String ❌ ≤256;不传则不改 描述
sortOrder Body Integer ✅ 非 null 排序权重

出参 Result<VehicleTypeRespVO>

字段 类型 说明
id Long 大类 ID
typeKey String 大类 key(不可改)
typeName String 大类中文名
icon String 图标
description String 描述
sortOrder Integer 排序权重
seatOptions Array<Integer> 该大类下型号座位数选项,去重升序
modelCount Integer 型号数
inUseCount Integer 在役车辆数

请求示例

{
  "typeName": "商务车",
  "icon": "",
  "description": "",
  "sortOrder": 2
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": "2064609312168083458",
    "typeKey": "mpv",
    "typeName": "商务车",
    "sortOrder": 2,
    "seatOptions": [7],
    "modelCount": 1,
    "inUseCount": 0
  },
  "success": true
}

空数据 / 降级响应

写接口没有空数据场景,也没有降级分支;校验不通过时零写入,update_time 也不变。

{ "code": 400, "message": "大类中文名不能为空", "data": null, "success": false }

错误响应

{ "code": 400, "message": "大类中文名不能为空", "data": null, "traceId": null, "success": false }
{ "code": 600102, "message": "车型大类不存在", "data": null, "success": false }

业务边界

  • 鉴权:未登录 → 业务码 401。
  • 参数校验先于「大类是否存在」:typeName 为空白时,即使 typeId 不存在也返回 400,而不是 600102。
  • 编辑时把名字清空是被拒绝的操作,不会把一个在营大类变成空名大类。

四、契约约束与正确调用方式(接口类必写)

本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。

✅ 正确 / ❌ 错误 payload 对照

场景 payload 结果
✅ 正常中文名 typeName: "商务车" 通过
✅ 首尾带空格 typeName: " 商务车 " 通过,原样落库
❌ 纯全角空格 typeName: "  " 400(本次新增拒绝)
❌ 纯半角空格 / 空串 / null typeName: " " / "" / 不传 400(改前已拒)
  • 前端表单的必填校验应与后端一致,按「去掉全部 Unicode 空白后是否为空」判定。JS 的 String.prototype.trim() 会去掉全角空格(U+3000),可以直接用 value.trim() === '' 判空。

五、数据库行为(涉及写操作时必写)

  • 无 Flyway migration、无 DDL、无表结构变更。fleet_vehicle_type.type_name 原本就是 VARCHAR(64) NOT NULL。
  • 校验失败的请求零写入:TEST 实测 4 次 POST、2 次 PUT 被拒后,fleet_vehicle_type 全表逐字段(含 update_time)与请求前一致,也没有生成测试 key 的行。
  • 存量处理成本为 0:TEST 上在架大类 4 行,type_name 都非空白。

六、边界行为

  • 未登录 → 业务码 401。
  • typeName 只要含一个非空白字符就放行;是否是「像中文的名字」后端不判断。
  • 读侧兜底(内部接口,前端不直接调用,写在这里方便排查 582032):如果库里仍出现空名大类(例如被直接改库),车队给 order-v3 的车型白名单(GET /internal/fleet/vehicle-types/category-names)照样包含这个大类,只是展示名回落为规范 key(如 mpv),同时车队服务打一条 WARN「车型大类名称为空,展示名回落到规范 key」。改前这个大类会从白名单里整个消失,导致用车需求报 582032「车型 mpv 不在车型字典内(不存在或已下线)」,而座位数校验照常通过。
  • 同一规范 key 下有多行时,展示名取排序最前、且名称非空白的那一行。

六.6、修改前后对比(修改/删除类接口必写,新增跳过)

字段级对比

字段 改前 改后
typeName 校验 @NotBlank:按 trim() 判空,全角空格放行 @NotNull + Unicode 空白判定:全角空格等一律拒

行为级对比

行为 改前 改后
新增/编辑时 typeName 传全角空格 200,落库 400「大类中文名不能为空」,零写入
typeName 传 null / 空串 / 半角空格 400 400(同一条文案,只报一条)
库里存在空名在营大类时,order-v3 提交该大类的用车需求 582032 误拒(座位校验却通过) 正常通过字典校验

六.7、影响评估(修改/删除类必写)

  • 是否破坏向后兼容:只收紧了一种此前被接受的取值(typeName 全为全角空格等 Unicode 空白),这种名字本来就没有业务意义;请求/响应结构不变。
  • 前端是否必须同步上线:否。前端不改的话,用户输入全角空格会在提交后收到后端 400 文案,不会写坏数据。建议前端表单必填校验同步用 trim() 判空,在提交前就提示。
  • 前端 workaround 清理点:无。

七、不影响范围(显式声明, 帮前端/QA 缩小排查面)

  • 仅影响:新增/编辑车型大类的 typeName 校验;内部接口 category-names 对空名大类的取舍。
  • 零影响:
    • typeKey、icon、description、sortOrder 的校验规则。
    • GET /admin/fleet/vehicle-types(树)、/page、/list 的响应结构与内容。
    • 车型型号(/admin/fleet/vehicle-types/{typeId}/models 等)相关接口。
    • order-v3 的 582032 / 582033 错误码与文案本身;名称正常的大类在白名单里的名称与顺序不变(TEST 实测 4 个在架大类改前改后一致)。
    • 座位数选项接口 seat-options 的行为(本来就不看名称)。

八、测试环境已验证

部署:PR #8232 合并 dev-v3(9231b8d8c),2026-09-23 14:30 滚动部署 TEST 双实例(检出 7a876ec57,含本单)。

构建身份(零写入判据):直连 8087 / 8187 两个实例,PUT /admin/fleet/vehicle-types/999999999(不存在的 id):typeName 传全角空格返回 400(旧代码会放行到 600102);同一 id 传正常名返回 600102(阴性对照)。

真实网关(https://api.test.1814.love,管理端 token):

POST /admin/fleet/vehicle-types  无 token            → 401 缺少有效的 Authorization 头 ✓
POST /admin/fleet/vehicle-types  typeName="  "      → 400 大类中文名不能为空 ✓
POST /admin/fleet/vehicle-types  typeName="   "      → 400 大类中文名不能为空 ✓
POST /admin/fleet/vehicle-types  typeName=""         → 400 大类中文名不能为空 ✓
POST /admin/fleet/vehicle-types  typeName=null       → 400 大类中文名不能为空 ✓
PUT  /admin/fleet/vehicle-types/{suv2 的 id}  typeName=" "  → 400 ✓
PUT  /admin/fleet/vehicle-types/{suv2 的 id}  typeName=""    → 400 ✓
fleet_vehicle_type 全表前后逐字段一致(含 update_time)✓

读侧(内部接口,两个实例):把 mpv 的 type_name 临时依次改为空串、半角空格、全角空格,category-names 均返回 mpv → "mpv",其余 3 个大类名称与顺序不变,seat-options?vehicleType=mpv 仍返回 [7];验毕 type_name 与 update_time 均恢复原值(逐字段一致)。

单元测试:VehicleTypeServiceTest 28、VehicleTypeSaveReqVOValidationTest 5、VehicleTypeControllerTest 17、VehicleTypeControllerIdempotentTest 6、FleetRedLineArchTest 18,共 74 例 0 失败。


十、相关文档

  • 关联 Issue: wx/HL#8226
  • 关联 PR: wx/HL#8232
  • 上游来源:#8202(子订单行程用车车型大类接入车队字典校验,582032)

关联 / 联系人

链接

联系人

  • 后端负责人: @jw
  • 前端负责人: @mmg