14 KiB
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)