# 评价模块迁移到 order-v2 服务（URL 不变 / 状态机对照 / Feign 链路变化）

> **日期**：2026-04-21
> **服务**：`hl-user-service` → `hl-order-service`（v2）
> **PR**：#1130（merge commit `cff993d12e3eb33f8c19d14bb18a9de001f5f971`）
> **类型**：后端跨服务迁移 / 重构
> **前端调用影响**：✅ **REST 路径零变化，前端无需改代码**（本 changelog 只是告知 + 状态机对照，防止有硬编码状态常量踩坑）

---

## 一、变更总览

评价（review）模块从 **hl-user-service** 整体搬到 **hl-order-service-v2**。

- **网关路由已同步切换**：`/admin/review/**` 原先由 gateway 转 `lb://hl-user-service`，现在转 `lb://hl-order-service`。前端用原 URL 直接调就行
- **BFF（hl-mp-service）Feign 内部目标服务切换**：`MpReviewFeignClient(name="hl-user-service")` → `name="hl-order-service"`，小程序前端调 `/mp/review/**` 完全无感
- **DB 真实表**从 `hl_user_service` / `hl_review_service`（VIEW + 废弃库）搬到 `hl_order_service_v2`
- **评价审核 5 状态 + 6 事件** 改用 cola 状态机（原先是 if-else 散写）

---

## 二、受影响的 URL（全部路径不变）

### 管理端（hl-ui）— 7 个 `/admin/review/**`

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/admin/review/list` | 评价列表（分页） |
| GET | `/admin/review/{reviewId}` | 评价详情 |
| POST | `/admin/review/{reviewId}/approve` | 人工审核通过 |
| POST | `/admin/review/{reviewId}/reject` | 人工审核拒绝 |
| POST | `/admin/review/{reviewId}/override-approve` | 覆盖通过（机审拒绝后管理员放行） |
| POST | `/admin/review/{reviewId}/reply` | 管理员回复 |
| PUT | `/admin/review/{reviewId}/keyword-tags` | 编辑评价关键词标签 |

### 小程序端（hl-mp）— 14 个 `/mp/review/**` + 3 个 `/mp/like/**`

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/mp/review/rating-categories` | 评分类别字典 |
| GET | `/mp/review/filter-tags` | 过滤标签（支持 productCategory） |
| POST | `/mp/review/create` | 创建评价 |
| GET | `/mp/review/my` | 我的评价列表 |
| GET | `/mp/review/search` | 搜索评价 |
| GET | `/mp/review/target` | 按目标查（productId/customizerId 等） |
| GET | `/mp/review/product/{productId}` | 产品评价列表 |
| GET | `/mp/review/product/{productId}/highlights` | 产品精选好评 |
| GET | `/mp/review/stats` | 评价统计（平均分/好评率） |
| GET | `/mp/review/featured` | 首页/列表精选评价 |
| GET | `/mp/review/order/{orderId}/reviewed` | 订单是否已评价 |
| GET | `/mp/review/order/{orderId}/reviewable-targets` | 订单可评价目标列表 |
| POST | `/mp/review/{reviewId}/like` | 点赞 |
| GET | `/mp/review/{reviewId}/like/check` | 查询是否已点赞 |
| POST | `/mp/like/*`（3 个） | 透调评价点赞 Feign |

### 其它端点（无变化）

- 订单详情里"是否已评价"标记 `orders.is_reviewed`：由后端同事务内强一致更新（原本是 Feign + afterCommit 最终一致），前端字段取法不变
- 评价图片/视频 OSS 链接字段 `images[].imageUrl` / `videos[].videoUrl`：位置、命名、协议不变

---

## 三、⚠️ 状态机改造 — 前端请核对硬编码状态常量

评价审核状态机重新梳理，**旧状态值有变**。如果前端存在状态字符串硬编码（status 字段 switch/判断），**必须参考下表确认取值是否还匹配**。

### 1. 状态对照表

| 老值（user-service 时代） | 新值（order-v2 时代） | 含义 | 前端处理建议 |
|---|---|---|---|
| `PENDING` | `PENDING_MODERATION` | 机审中（评价刚提交，异步跑阿里云绿网） | 若前端用此常量过滤"待审核"，注意改名；建议改为判断 `status != 'APPROVED' && status != 'REJECTED'` |
| `MACHINE_PENDING` | `PENDING_MODERATION` | 同上（并入） | 老取值建议 fallback 兼容 |
| `MACHINE_BLOCKED` | `MACHINE_REJECTED` | 机审命中 block 级关键词，系统拒（但管理员可覆盖） | 改名 |
| `MACHINE_REVIEW` / 其他"待人工" | `PENDING_MANUAL` | 机审命中 review 级关键词，转人工复审 | 改名 |
| `APPROVED` | `APPROVED` | 审核通过 / 前台可见 | ✅ 不变 |
| `REJECTED` | `REJECTED` | 人工拒绝 / 前台不可见 | ✅ 不变 |
| `OVERRIDE_APPROVED` | 不再作为独立状态，覆盖后直接是 `APPROVED` | 原"管理员覆盖通过"独立状态 | ⚠️ 若前端用此判断"是否覆盖"，改用 `machineResult.override == true` 或事件日志字段判断 |

### 2. 新状态机（order-v2 侧实现）

**5 个状态**（`ReviewStatus` 枚举）：

| 值 | 描述 |
|---|---|
| `PENDING_MODERATION` | 机审中（初始状态） |
| `PENDING_MANUAL` | 机审命中 review 级关键词，待人工复审 |
| `MACHINE_REJECTED` | 机审命中 block 级关键词，系统拒绝 |
| `APPROVED` | 审核通过（前台可见） |
| `REJECTED` | 人工拒绝（前台不可见） |

**6 个事件**（`ReviewEvent`）：

| 事件 | 触发 | 转移 |
|---|---|---|
| `MACHINE_PASS` | 阿里云绿网回调通过 | `PENDING_MODERATION` → `APPROVED` |
| `MACHINE_FAIL_REVIEW` | 命中 review 级关键词 | `PENDING_MODERATION` → `PENDING_MANUAL` |
| `MACHINE_FAIL_BLOCK` | 命中 block 级关键词 | `PENDING_MODERATION` → `MACHINE_REJECTED` |
| `MANUAL_APPROVE` | 管理端 `/admin/review/{id}/approve` | `PENDING_MANUAL` → `APPROVED` |
| `MANUAL_REJECT` | 管理端 `/admin/review/{id}/reject` | `PENDING_MANUAL` → `REJECTED` |
| `ADMIN_OVERRIDE_APPROVE` | 管理端 `/admin/review/{id}/override-approve` | `MACHINE_REJECTED` → `APPROVED` |

### 3. 状态显示建议（供 hl-ui / hl-mp 参考）

```javascript
// 管理端列表筛选建议
const ADMIN_FILTER = {
  待审核: ['PENDING_MODERATION', 'PENDING_MANUAL'],  // 需要管理员关注
  机审拒绝: ['MACHINE_REJECTED'],                     // 可覆盖通过
  已通过: ['APPROVED'],
  已拒绝: ['REJECTED'],
};

// 小程序端（只关心是否已审核通过）
if (review.status === 'APPROVED') {
  // 正常展示
} else {
  // 一般不展示或隐藏
}
```

---

## 四、前端无感但内部链路变化

### 1. 创建评价：订单"已评价"标记改为强一致

- **老链路**（最终一致）：`createReview` 成功 → afterCommit 调 `user-service.OrderReviewFeignClient → hl-order-service.PUT /internal/mp/order/{id}/mark-reviewed`（Feign 跨服务一次）
- **新链路**（强一致）：review 与 order 同库同事务，`createReview` 同事务内直接更新 `orders.is_reviewed = 1`
- **前端体感**：
  - 创建评价响应时间预计快 **50~100ms**（省一跳 Feign）
  - 创建成功立刻查订单 `/mp/order/{orderId}` 的 `isReviewed` 字段就是 `true`，**不再有短暂不一致窗口**（老版本偶发 50ms 内查到 false）

### 2. 管理端审核（approve / reject / override-approve）

- 老：user-service 本地写 review.status → Feign 调 order-v2 → order.is_reviewed 更新
- 新：order-v2 本地写 review.status + orders.is_reviewed（同事务）
- 响应时间预计 **快 30~50ms**

### 3. 精选评价缓存键（Redis 内部实现，前端无直接感知）

- 前缀保持 `review:featured:limit:{limit}`，TTL 600s±60s
- MP 侧兼容层前缀 `mp:review:featured*` 保持不变
- 迁移后首次访问缓存 miss 约 1~3 秒回源，后续正常（业务调用量低，不影响体验）

---

## 五、响应字段清单（仅列前端需要关注的 VO）

### 管理端 `ReviewVO` / `ReviewListVO`（`/admin/review/**` 返回）

| 字段 | 类型 | 说明 | 变化 |
|---|---|---|---|
| `reviewId` | Long | 评价 ID | - |
| `orderId` / `orderNo` | Long / String | 订单 | - |
| `userId` / `userName` / `userAvatar` | Long / String | 用户 | - |
| `productId` / `productName` / `productType` | Long / String | 产品 | productType 新增 `CUSTOM` 取值（已在 PR #935 生效，本 PR 无变化） |
| `targetType` / `targetId` | String / Long | 评价目标（PRODUCT/CUSTOMIZER/SCENIC 等） | - |
| `ratingItinerary` / `ratingAccommodation` / `ratingDriver` / `ratingDining` / `ratingOverall` | Integer (1~5) | 五维评分 | - |
| `ratingLevel` | String (GOOD/MEDIUM/BAD) | 评分等级 | - |
| `ratingAvg` | Decimal(2,1) | 平均分 | - |
| `content` | String | 评价内容 | - |
| `images[]` / `videos[]` | List | 图片/视频 | 字段名不变 |
| `specialAnswers` / `keywordTags` | JSON | 特殊问题 / 关键词标签 | - |
| `status` | String | ⚠️ 取值改，见第三节 | **改** |
| `rejectReason` | String | 拒绝原因 | - |
| `auditorName` / `auditedAt` | String / DateTime | 审核员/审核时间 | - |
| `adminReplyContent` / `adminReplyBy` / `adminReplyAt` | - | 管理员回复 | - |
| `likeCount` | Integer | 点赞数 | - |
| `createdAt` | DateTime | 创建时间 | ✅ **保持驼峰 `createdAt`**（虽然 DB 底层列改为 `create_time` 对齐 BaseDO，但 VO 对前端零变化） |
| `updatedAt` | DateTime | 更新时间 | ✅ 同上 |

### 小程序端 `MpReviewVO`（`/mp/review/**` 返回）

字段与 `ReviewVO` 核心一致，含 `reviewId / userName / userAvatar / productName / content / ratings* / images / videos / likeCount / createdAt / liked`（当前用户是否点赞）。

### `ReviewStatsVO`（统计接口返回）

| 字段 | 类型 | 说明 |
|---|---|---|
| `totalCount` | Long | 总评价数 |
| `avgRating` | Decimal(2,1) | 平均评分 |
| `goodCount` / `mediumCount` / `badCount` | Long | 好/中/差评数 |
| `goodRate` | Decimal(5,2) | 好评率（百分比） |
| `hasImageCount` / `hasVideoCount` | Long | 有图/有视频评价数 |

---

## 六、错误码对照

| HTTP | code | 说明 | 触发 |
|---|---|---|---|
| 200 | 200 | 成功 | - |
| 200 | 400 | 参数错误 | 评分不在 1-5 / 必填漏传 |
| 200 | 401 | 未登录 | `/mp/review/create` 等需 userId 的接口 |
| 200 | 403 | 无权限 | 管理端非管理员访问 `/admin/review/**` |
| 200 | 404 | 评价不存在 | review 已被软删或 ID 错误 |
| 200 | 409 | 状态非法转移 | ⚠️ **新增**：cola 状态机拒绝非法流转（例：对 `APPROVED` 再 `reject`） |
| 200 | 500 | 系统错误 | 保底 |
| 200 | 50001 | 机审服务暂不可用 | 阿里云绿网 SDK 超时/限流 |

> 本项目 HTTP status 始终返回 200，业务码在 `Result.code`。

---

## 七、前端 TODO（如有硬编码）

- [ ] **hl-ui 管理端**：搜索项目里硬编码的评价状态字符串（例如 `status === 'PENDING'`），按第三节对照表替换
- [ ] **hl-mp 小程序端**：
  - 大概率无需改（大部分地方直接取 `status === 'APPROVED'` 判断是否展示）
  - 如有"审核中"状态展示（例如"我的评价"列表），状态值需从 `PENDING` / `MACHINE_PENDING` 替换为 `PENDING_MODERATION` / `PENDING_MANUAL`
- [ ] **无其它改动**：网关路径、字段命名、认证头（`X-User-Id` / `X-Admin-Id`）全部不变

---

## 八、回归验证建议（前端自测）

- [ ] 提交评价 → 我的评价列表能看到（`status=PENDING_MODERATION` 或 `APPROVED`）
- [ ] 订单详情"已评价"标记立即刷新（不再有延迟窗口）
- [ ] 产品详情/精选评价列表加载正常
- [ ] 评价点赞/取消点赞能实时更新
- [ ] 管理端审核通过/拒绝 → 小程序端立即可见/隐藏
- [ ] 管理端覆盖通过（机审拒绝后）→ 评价重新可见

---

## 九、联系人

- 后端负责人：wx / AI 协作（管理者）
- 相关任务文档：`docs/tasks/20260421_设计_评价模块迁移到order-v2.md`
- 重启服务清单（后端）：hl-user-service / hl-order-service-v2 / hl-mp-service / hl-product-service-v2 / hl-gateway
