hl-api-changelog/changelogs/2026-04/2026-04-23_order-complaint-full-contract.md
API Changelog Bot 7e50821d0d docs(complaint): 投诉模块完整接口契约 - 全字段/错误码/状态机/前端对接清单 (替代简版)
- 字典 complaint_type 4 项
- 小程序 5 接口详细字段表 + 请求/响应示例 + 错误码逐条
- 管理端 2 接口含权限模型 + 25 字段表(含通知状态、脱敏电话双字段)
- 统一错误码 20301~20307 附加字段与前端应对
- 前端状态机: 客户视角 + 定制师视角 + 超管视角
- 通知链路说明 + 短信模板 SMS_505775156(审核通过, Nacos 已热更新)
- 前端对接清单按管理端/小程序分点拆开
2026-04-23 10:13:48 +08:00

16 KiB

投诉模块完整接口契约(前端对接专用)

  • 日期: 2026-04-23更新版,替代 2026-04-23_order-complaint-module.md 的简版)
  • 相关 PR: #1221 主 + #1222 #1223 #1225 #1235hotfix+ Nacos SMS_505775156
  • Closes: #1220
  • 短信模板: SMS_505775156(阿里云审核通过,签名"呼籁旅行"
  • 网关前缀: https://api.test.1814.love:9443(测试)/ https://api.1814.love(正式)
  • 鉴权:
    • 小程序 /mp/**Authorization: Bearer <mp-token>userId 由网关注入 request.userId
    • 管理端 /admin/**Authorization: Bearer <admin-token> + 按钮权限

📚 目录


A. 字典

GET /admin/dict/data/complaint_type
Authorization: Bearer <admin-token>

响应 data:
[
  {"dictDataId":90401,"dictValue":"ITINERARY","dictLabel":"行程","sortOrder":1,"status":"ACTIVE"},
  {"dictDataId":90402,"dictValue":"VEHICLE","dictLabel":"车","sortOrder":2,"status":"ACTIVE"},
  {"dictDataId":90403,"dictValue":"HOTEL","dictLabel":"房","sortOrder":3,"status":"ACTIVE"},
  {"dictDataId":90404,"dictValue":"OTHER","dictLabel":"其他","sortOrder":4,"status":"ACTIVE"}
]

小程序端无单独字典接口;响应里已内嵌 types:[{code,label}] 结构,前端不需要自己 map


B. 小程序端 5 接口

统一前缀:/mp/complaint。全部接口不需要前端传 userId,网关从 Token 注入。

B1. POST /mp/complaint/create — 发起投诉

请求 bodyMpComplaintSaveReqVO

字段 类型 必填 约束 说明
orderId Long - 订单 ID序列化为 String,前端直接透传
dayNumber Integer null/0/≥1 null 或 0 = 整程投诉;≥1 = 第 N 天
types List<String> 非空,元素∈{ITINERARY,VEHICLE,HOTEL,OTHER} 可多选
content String 1~500 字 投诉文字
imageUrls List<String> ≤9 项,单个 URL ≤500 字符 OSS 直传后的 URL
videoUrl String ≤500 字符 单个视频 URL

请求示例

POST /mp/complaint/create
Authorization: Bearer eyJ...
Content-Type: application/json

{
  "orderId": "7123456789012345678",
  "dayNumber": 2,
  "types": ["ITINERARY", "VEHICLE"],
  "content": "第二天景点讲解员迟到半小时,车辆空调坏了",
  "imageUrls": [
    "https://cdn.hulalv.com/complaint/2026/04/abc.jpg",
    "https://cdn.hulalv.com/complaint/2026/04/def.jpg"
  ],
  "videoUrl": null
}

响应

{"code":200,"message":"成功","data":"8123456789012345678","success":true}

data 是 complaintIdLong 序列化为 String

错误码2030110 分钟冷却)/ 20302订单非本人/ 20303dayNumber 超范围,不能投诉未发生的天)


B2. POST /mp/complaint/{id}/cancel — 撤回投诉

路径参数id = 投诉 ID

请求 body:无

响应

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

错误码20304不存在或已撤回/ 20305非本人/ 20306创建已超 10 分钟)

规则:仅创建后 10 分钟内本人可撤回;软删 deleted_at + status=WITHDRAWN;企微+短信已发出不撤回(定制师需要自行忽略已收到的通知)。


B3. GET /mp/complaint/my-page — 我的投诉分页

Query

参数 类型 默认 约束
pageNo Integer 1 ≥1
pageSize Integer 20 1~50

响应(分页 PageResult<MpComplaintSimpleRespVO>

{
  "code":200,"message":"成功","success":true,
  "data":{
    "list":[
      {
        "id":"8123456789012345678",
        "orderId":"7123456789012345678",
        "orderNo":"HL20260422001",
        "groupCode":"TG001",
        "dayNumber":2,
        "dayLabel":"第2天",
        "types":[
          {"code":"ITINERARY","label":"行程"},
          {"code":"VEHICLE","label":"车"}
        ],
        "contentSnippet":"第二天景点讲解员迟到半小时,沟通后...",
        "imageCount":3,
        "videoCount":0,
        "isResolved":false,
        "resolutionEditCount":0,
        "createdAt":"2026-04-23 10:15:30"
      }
    ],
    "total":"8"
  }
}

列表按 createdAt 倒序


B4. GET /mp/complaint/{id} — 投诉详情

响应MpComplaintRespVO,20 字段):

字段 类型 说明
id Long 投诉 ID
orderId / orderNo / groupCode - 订单冗余信息
dayNumber Integer 0=整程 / ≥1=具体天
dayLabel String "整程" 或 "第 N 天"
types List<{code,label}> 投诉类型列表
content String 完整文字(区别于 list 的 snippet
contentSnippet String 前 50 字
imageUrls List<String> 图片 URL 列表
videoUrl String 视频 URL 或 null
imageCount / videoCount Integer 数量(便于列表快速渲染)
resolutionNote String 解决备注(客户本人填)
resolutionEditCount Integer 已修改次数 0~2
resolutionUpdatedAt DateTime 最近一次修改时间
isResolved Boolean resolutionNote 非空
canCancel Boolean 是否可撤回(创建 < 10min 内 + status=ACTIVE
canEditResolution Boolean 是否可填/改解决备注editCount < 2
status String ACTIVE / WITHDRAWN
createdAt DateTime 创建时间

错误码20304不存在/已撤回)/ 20305非本人

⚠️ 前端注意canCancel / canEditResolution后端计算返回,前端直接用不要自己根据 createdAt 算时间差(时钟偏移问题)。


B5. PUT /mp/complaint/{id}/resolution — 填/改解决备注

请求 body

字段 类型 必填 约束
resolutionNote String 1~500 字

请求示例

PUT /mp/complaint/8123456789012345678/resolution
Authorization: Bearer eyJ...
Content-Type: application/json

{"resolutionNote":"已与定制师电话沟通,明天补偿安排"}

响应

{"code":200,"message":"成功","data":1,"success":true}

data = 剩余可编辑次数2 - newEditCount。0 = 已锁定不能再改。

错误码20305非本人/ 20307已达 2 次上限)

规则:最多写入 2 次(一次填 + 一次改);达 2 次后 canEditResolution=false


C. 管理端 2 接口

统一前缀:/admin/complaint只读,无写操作。

权限(按钮权限码):

  • complaint:list:self(定制师)— 后端强制在 SQL 里注入 WHERE customizer_id = currentAdminId前端不需要传 customizerId
  • complaint:list:all(超管/主管)— 无过滤
  • complaint:detail — 详情

角色默认绑定:SUPER_ADMIN(1) / ADMIN(2):all + :detailCUSTOMIZER(3):self + :detail

C1. GET /admin/complaint/page — 分页列表

QueryComplaintPageReqVO

参数 类型 默认 说明
pageNo Integer 1 -
pageSize Integer 20 -
keyword String - 订单号 / 昵称 / 内容 模糊 匹配
orderNo String - 订单号 精确 匹配
groupCode String - 团号 精确
customizerNickname String - 定制师昵称 模糊
types List<String> - 多选,如 ?types=ITINERARY&types=VEHICLE
isResolved Boolean - true=已解决(备注非空)/ false=未解决 / 不传=全部
dateFrom LocalDate - yyyy-MM-dd,含当天
dateTo LocalDate - yyyy-MM-dd,含当天
dayScope String ALL ALL / WHOLE(仅整程)/ SPECIFIC(仅指定天)

请求示例

GET /admin/complaint/page?pageNo=1&pageSize=20&types=ITINERARY&isResolved=false&dateFrom=2026-04-01&dateTo=2026-04-30&dayScope=ALL
Authorization: Bearer <admin-token>

响应PageResult<ComplaintRespVO>)。单项字段见 C2,列表里 contactPhoneDecrypted 为 null(仅详情按权限才返明文)。

C2. GET /admin/complaint/{id} — 详情

响应ComplaintRespVO,完整字段):

字段 类型 说明
id / orderId / orderNo / groupCode - 基础
customizerId / customizerNickname - 定制师
userId / userNickname - 投诉人
contactName String 联系人姓名
contactPhoneMasked String 脱敏后 4 位,如 ***1234永远返
contactPhoneDecrypted String 明文,仅有权时返;否则 null
dayNumber / dayLabel - 天维度
types List<{code,label}>
content String 完整文字
contentSnippet String 前 50 字(列表用)
imageUrls List<String>
videoUrl String
resolutionNote / resolutionEditCount / resolutionUpdatedAt / isResolved - 解决备注(客户本人写)
notifyWechatStatus String PENDING / SUCCESS / FAIL / SKIP
notifyWechatError String FAIL 时的错误信息
notifySmsStatus String 同上
notifySmsError String 同上
status String ACTIVE / WITHDRAWN
createdAt / updatedAt DateTime
canRemindCustomizer Boolean 永远 false预留,MVP 管理端只读)

⚠️ 前端展示建议

  • 列表页只显示 contactPhoneMasked;详情页如果 contactPhoneDecrypted 非空则按"点击查看"展开,否则灰显
  • notifyWechatStatus / notifySmsStatus 用小圆点 + 中文标签渲染SUCCESS 绿 / FAIL 红 / SKIP 灰 / PENDING 黄)

D. 统一错误码表

Code 常量 场景 附加字段 前端应做
20301 ERR_COMPLAINT_COOLDOWN 同 user+order+day 10 分钟内重复投诉 remainingSeconds(业务码信息里) 展示"请 X 秒后再试"倒计时
20302 ERR_ORDER_NOT_OWN 订单不属于当前 user - 提示"订单不存在或无权"
20303 ERR_DAY_NUMBER_INVALID dayNumber 超出已发生范围 maxDayNumber 限制前端表单 dayNumber 选择范围
20304 ERR_COMPLAINT_NOT_FOUND 投诉不存在或已撤回 - 跳回我的投诉列表
20305 ERR_COMPLAINT_NOT_OWN 非本人投诉 - 提示"无权限"
20306 ERR_WITHDRAW_EXPIRED 撤回已超 10 分钟 - 隐藏撤回按钮
20307 ERR_RESOLUTION_LOCKED 解决备注已填满 2 次 - 隐藏"再修改一次"按钮

响应结构统一

{"code":20301,"message":"10 分钟内已投诉过该天行程","data":null,"success":false}

HTTP 状态码恒为 200(项目规范),只看 body 的 code


E. 前端状态机

小程序端客户视角

[订单详情]
   │
   ├── 点击"整程投诉" → dayNumber=null → POST /create
   └── 点击第 N 天的"投诉" → dayNumber=N → POST /create
                                 │
                                 ├── 20301 → 展示冷却倒计时
                                 ├── 20303 → 提示"尚未开始的天无法投诉"
                                 └── 200 → 跳转[我的投诉列表]
                                                │
                                                └── 点某条 → [详情]
                                                              ├── canCancel=true → 展示"撤回"按钮
                                                              │     点击 → POST /{id}/cancel
                                                              │            ├── 20306 → 按钮已过期灰掉
                                                              │            └── 200 → 回列表
                                                              ├── canEditResolution=true → 展示"填/改解决备注"按钮
                                                              │     点击 → 弹编辑框 → PUT /{id}/resolution
                                                              │            ├── 20307 → 灰掉按钮
                                                              │            └── 200 → 刷新详情
                                                              └── 只读展示

管理端定制师视角

[投诉管理菜单] → /admin/complaint/page
      │
      ├── 看到所有自己负责订单的投诉(后端按 :self 过滤)
      ├── 顶部筛选:订单号 / 团号 / 类型 / 是否已解决 / 时间 / 整程or指定天
      └── 点某条 → [详情](只读,无任何写按钮)
                      ├── 查看完整文字 / 图片预览 / 视频播放
                      ├── 看客户写的 resolutionNote若有
                      └── 看 notifyWechatStatus / notifySmsStatus 确认是否收到通知

管理端超管视角

同上,但看到全部投诉(后端按 :all 不加 customizerId 过滤)。


F. 通知链路

前端无感知,此处仅说明供你理解触发场景:

  • 投诉创建 POST /mp/complaint/create 成功 → afterCommit 触发双链路 @Async
    • 企微:发给 order.customizerId → admin_user.enterprise_wechat_id → wechat_user.userid 对应的定制师,文案:
      【客户投诉】
      团号TG001
      订单号HL20260422001
      联系人:张三 13800138000
      第 2 天 · 行程/车
      内容:第二天景点讲解员迟到半小时,沟通后...(前 50 字)
      图片 3 视频 0
      
    • 短信(阿里云 SMS_505775156,签名 呼籁旅行):
      【呼籁旅行】团号TG001订单HL20260422001第2天收到客户投诉(行程/车),请登录后台查看。
      
  • 定制师未绑企微(enterprise_wechat_id 空)→ notifyWechatStatus=SKIP
  • 定制师无手机号(wechat_user.mobile 空)→ notifySmsStatus=SKIP
  • 任一链路失败 → status=FAIL + error 字段写入,不阻塞主流程
  • 前端不需要关心重试(后端无自动重试,定制师可通过"我的投诉"列表底部的创建时间对比企微收件时间自行排查)

G. 前端对接清单

管理端hl-ui admin

  • 菜单 投诉管理1250 系列,侧边栏末尾)自动由 sys_menu 返回渲染
  • 列表页:表格 + 7 个筛选条件(上方) + dayScope 切换标签
  • 详情页Drawer 或独立页):完整展示 C2 所有字段
  • 图片预览走 el-image 或同级组件
  • 视频预览 <video controls>
  • contactPhoneDecrypted 使用"点击查看"折叠
  • notifyWechatStatus / notifySmsStatus 状态圆点
  • Long 字段id/orderId/userId/customizerId不要做数字运算,直接当字符串透传

小程序端hl-mp

  • 订单详情页每日卡片右上新增"投诉"入口 → dayNumber=N
  • 订单详情页底部新增"整程投诉"入口 → dayNumber=null
  • 投诉表单页多选类型chip 或 checkbox+ 文字counter 0/500+ 图片上传OSS 直传,最多 9+ 视频上传(最多 1
  • 冷却 20301显示"{remainingSeconds} 秒后可再次投诉"倒计时 Toast
  • "我的投诉"入口(我的 tab 底部 或 订单详情页按钮)→ 列表 → 详情
  • 详情页按 canCancel / canEditResolution 动态显隐按钮
  • 撤回成功 → 回列表 + 自动移除该条(软删不再返回)

H. 冷启动兜底

  • 投诉表 order_complaint 已在测试服 hl_order_service_v2 库就绪29 字段 + UNIQUE 防刷索引)
  • 字典 complaint_type + 菜单 1250 系列已执行
  • Nacos aliyun.sms.template.complaint-notify: SMS_505775156 已热更新(测试服)
  • 正式服上线前需运维同步 Nacos 一次(阿里云账号跨环境共享,模板码通用)

变更记录

日期 变更
2026-04-23 阿里云模板审核通过 SMS_505775156,Nacos 热更新,短信真发
2026-04-23 完整接口契约本文件(替代简版)
2026-04-22 投诉模块上线PR #1221,mp 5 接口 + admin 2 接口 + 字典菜单