# 商品审核 — 技术方案

> **依据：** 《商品审核功能需求.md》v1.0  
> **关联：** 《商品管理技术方案.md》v1.3.3、《店铺管理技术方案.md》v1.2.1、《商户管理技术方案.md》v1.4、《商品分类技术方案.md》v1.1、《商品服务管理技术方案.md》v1.0.1、《关联需求分析.md》v1.2  
> **范围：** 平台 **审核 + 下架 + 监管列表/检索/详情**；**复用** 商品模块表与服务；**不新增** 商品状态机。  
> **原则：** `goods_status` 仅经 submit/audit/offShelf 变更（P17）；批量含非法状态 **整批失败**；平台列表 **不含** 未上架（status=0）。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 权限 | MyBatis；`@PreAuthorize`；`AjaxResult` / `TableDataInfo` |
| 状态 | `goods_status` char(1)：0～4（与商品管理一致） |
| 日志 | 审核、下架 `@Log` |

### 1.1 模块落位

```text
baqing-shop
├── com.ruoyi.web.modules.goods
│   ├── controller.GoodsController              # 平台商品（已实现 audit/offShelf/list）
│   ├── controller.GoodsAuditController           # 可选：商品审核菜单薄封装（见 §4.0）
│   ├── service.IGoodsService
│   │     ├── selectPlatformList / selectPlatformDetail
│   │     ├── auditGoods
│   │     └── offShelfPlatform
│   ├── dto.GoodsAuditDTO / GoodsOffShelfDTO
│   ├── facade.GoodsFacadeImpl                    # hasBlockingGoodsForShopDelete
│   └── constant.GoodsConstants
sql/
├── biz_goods.sql
└── biz_goods_service_snapshot.sql                # 详情快照
```

> **定稿：** 商品审核 **不单独建表**；与《商品管理》**同一套** `IGoodsService` 状态流转，避免双轨逻辑。

### 1.2 业务链

```text
biz_shop_global_config（default_audit_pass）
    └── biz_goods（goods_status）
            ├── 商家 PUT /agri/seller/goods/{id}/submit → 1 或 2
            │
            ├── 【本方案 · 平台】
            │     GET  list / detail（status≠0）
            │     PUT  audit（1→2|3）
            │     PUT  offShelf（2→4）
            │
            └── biz_goods_service_snapshot（详情只读）
```

| 模块 | 关系 |
|------|------|
| 商品管理 | **同源**；商家发品/submit 在 `SellerGoodsController` |
| 店铺管理 / 店铺设置 | **全局** `default_audit_pass` → submit 后 **1 或 2** |
| 商品分类 | 列表/检索 join `category_path` |
| 商品服务 | 详情读 **快照** |
| 订单 | 审核/下架 **不** 改订单 |

### 1.3 跨模块 Facade（消费方）

| 接口 | 提供方 | 方法 | 与本模块 |
|------|--------|------|----------|
| `IGoodsFacade` | 商品 | `hasBlockingGoodsForShopDelete(shopId)` | 删店：存在 status∈{1,2} 阻断 |
| `IShopGlobalConfigFacade` | 店铺设置 | `getDefaultAuditPass()` | 商家 submit 时用 |
| `ICategoryFacade` | 分类 | `listPlatformLevel2Options` | 检索联动分类下拉（平台二级；`shopId` 预留扩展） |

### 1.4 状态联动

| 事件 | `biz_goods.goods_status` |
|------|--------------------------|
| 平台 audit / offShelf | 变更本表 |
| 商户/店铺冻结、停业、改免审开关 | **不变** |
| 改服务目录 | **不变**（快照不变） |

---

## 2. 数据库设计

### 2.1 表复用说明

| 表名 | 本模块用途 |
|------|------------|
| `biz_goods` | 主数据；审核改 `goods_status`、`reject_reason`、`audit_time` |
| `biz_shop` | join 店铺名；`del_flag` 过滤；删店校验 |
| `biz_merchant` | join 商户名（可选列） |
| `biz_goods_category` | join 分类路径 |
| `biz_goods_service_snapshot` | 详情展示服务项快照 |

**无新增表、无新增字段**（字段已在《商品管理技术方案》定义）。

### 2.2 本模块相关字段（`biz_goods`）

| 字段 | 类型 | 说明 |
|------|------|------|
| goods_id | bigint PK | |
| shop_id | bigint | 店铺 |
| category_id | bigint | 二级分类 |
| goods_sn | varchar(32) UK | 编号 |
| goods_name, main_pic | | 列表/详情 |
| sale_price, stock, sales_count | | 列表（草稿列：售价、销量） |
| goods_status | char(1) | **0** 未上架 **1** 待审核 **2** 出售中 **3** 审核失败 **4** 已下架 |
| reject_reason | varchar(500) | 驳回原因；audit 驳回时写入 |
| submit_time | datetime | 首次 submit；待审列表排序 |
| audit_time | datetime | 审核通过/驳回时间 |
| off_shelf_time | datetime | 下架时间 |
| del_flag | char(1) | 0 存在 2 删除 |

### 2.3 平台列表数据范围（SQL 约定）

```sql
WHERE g.del_flag = '0'
  AND g.goods_status <> '0'          -- 不含未上架（GR1）
  AND s.del_flag = '0'               -- 店铺未逻辑删除
```

| 索引 | 用途 |
|------|------|
| `idx_shop_status` (shop_id, goods_status, del_flag) | 按店+状态 |
| `idx_submit_time` (submit_time) | 待审核排序 |
| `uk_goods_sn` | 编号检索 |

完整 DDL 见 `sql/biz_goods.sql` 及《商品管理技术方案》§2.5。

### 2.4 字典

| dict_type | 用途 |
|-----------|------|
| goods_status | 列表/详情状态展示 |

---

## 3. 状态机（Service · 本模块涉及）

> 完整机见《商品管理技术方案》§3。下表仅 **平台审核模块** 写入点。

| 操作 | 方法 | 前置 status | 结果 status | 写字段 |
|------|------|-------------|-------------|--------|
| 审核通过 | `auditGoods(pass=true)` | 1 | 2 | `audit_time`, `update_by` |
| 审核驳回 | `auditGoods(pass=false)` | 1 | 3 | `reject_reason`, `audit_time` |
| 平台下架 | `offShelfPlatform` | 2 | 4 | `off_shelf_time` |

| 校验 | 失败 msg（实现） |
|------|------------------|
| 非待审核参与 audit | 整批失败；`data.reasons` 明细 |
| 非出售中参与 offShelf | 整批失败 |
| 驳回无原因 | 请填写驳回原因 |

**批量策略：** 先 **全量校验** → 任一不通过则 `GoodsBatchOperationException`，**零行** 更新（GR5）。

---

## 4. 接口设计

### 4.0 路径与权限（两种落地，二选一）

| 方案 | 基路径 | 权限前缀 | 说明 |
|------|--------|----------|------|
| **A（已实现）** | `/agri/goods` | `agri:goods:list` `query` `audit` `offshelf` | 与商品管理 **共用** Controller |
| **B（可选菜单）** | `/agri/goodsAudit` | `agri:goodsAudit:list` `query` `audit` `offshelf` | `GoodsAuditController` **委托** `IGoodsService`，逻辑与 A 相同 |

下文路径以 **方案 A** 为准；方案 B 仅路径/权限不同。

---

### 4.1 商品列表 `GET /agri/goods/list`

| 项 | 说明 |
|----|------|
| 权限 | `agri:goods:list` |
| 分页 | `pageNum` / `pageSize` |
| 默认排序 | `goods_status=1` 建议 `submit_time ASC`；其余 `update_time DESC`（Mapper 可参数化） |

**Query：**

| 参数 | 说明 |
|------|------|
| goodsSn | 编号模糊/精确 |
| goodsName | 名称模糊 |
| categoryId | 二级分类 ID |
| shopName | 店铺名模糊（join `biz_shop`） |
| goodsStatus | 1/2/3/4；**不传** 则查 status≠0 全部 |

**`rows`（GoodsListVO）：**

| 字段 | 说明 |
|------|------|
| goodsId, goodsSn, goodsName, mainPic | |
| salePrice, salesCount | 售价、销量（对齐需求；含 stock 可选） |
| goodsStatus, goodsStatusLabel | |
| shopName | 店铺名称 |
| categoryPath | 一级 > 二级 |
| merchantName | 可选 |

---

### 4.2 待审核数量（可选）`GET /agri/goods/pendingCount`

| 项 | 说明 |
|----|------|
| 权限 | `agri:goods:list` |
| 用途 | 菜单/Tab 角标 |
| 响应 | `{ "pendingCount": 12 }` |

> 未实现时可由前端 `list?goodsStatus=1&pageSize=1` 读 `total`。

---

### 4.3 商品详情 `GET /agri/goods/{goodsId}`

| 项 | 说明 |
|----|------|
| 权限 | `agri:goods:query` |
| 范围 | status **可为** 1/2/3/4（不含 0） |

**`data`（GoodsDetailVO 摘要）：**

| 字段 | 说明 |
|------|------|
| 基础 | goodsSn, goodsName, mainPic, detailContent, salePrice, stock, salesCount |
| 归属 | shopId, shopName, merchantName, categoryId, categoryPath |
| 状态 | goodsStatus, goodsStatusLabel |
| 审核 | submitTime, auditTime, rejectReason |
| 下架 | offShelfTime |
| 服务 | `serviceSnapshots[]`（快照） |

---

### 4.4 分类筛选项（检索联动）`GET /agri/goodsAudit/categoryOptions`

| 项 | 说明 |
|----|------|
| 权限 | `agri:goodsAudit:query` |
| Query | `shopId` 必填（**当前实现**仍返回全平台二级，参数预留按店扩展） |
| 响应 | 平台二级分类下拉（`label` = `一级 > 二级`） |
| 实现 | `categoryFacade.listPlatformLevel2Options()` |

> 平台商品管理列表（`/agri/goods`）若提供同类接口，口径与本节一致。

---

### 4.5 审核商品 `PUT /agri/goods/audit`

| 项 | 说明 |
|----|------|
| 权限 | `agri:goods:audit` |
| 日志 | title=商品审核 |

**Body：**

```json
{
  "goodsIds": [1001, 1002],
  "pass": true,
  "rejectReason": null
}
```

驳回示例：

```json
{
  "goodsIds": [1003, 1004],
  "pass": false,
  "rejectReason": "主图不清晰，请更换后重新提交"
}
```

| 字段 | 校验 |
|------|------|
| goodsIds | 非空 |
| pass | true → 1→2；false → 1→3 |
| rejectReason | `pass=false` 时 **必填** |

**成功：** `code=200`，`msg=操作成功`。

**整批失败：**

```json
{
  "code": 500,
  "msg": "部分商品不满足操作条件，操作已取消",
  "data": {
    "reasons": ["商品非待审核状态：G202605260001"]
  }
}
```

---

### 4.6 下架商品 `PUT /agri/goods/offShelf`

| 项 | 说明 |
|----|------|
| 权限 | `agri:goods:offshelf` |
| 日志 | title=商品下架 |

**Body：**

```json
{
  "goodsIds": [2001, 2002]
}
```

| 校验 | 说明 |
|------|------|
| goodsIds | 非空 |
| 状态 | 全部 `goods_status=2`，否则整批失败 |

**成功：** `code=200`。

---

### 4.7 本模块不提供的能力

| 能力 | 归属 |
|------|------|
| POST/PUT 商品、submit | `/agri/seller/goods` |
| 直接改 `goods_status` | **禁止**（商品管理 P17） |
| DELETE 商品 | 非审核模块 |

---

## 5. Service 要点

```text
selectPlatformList(query)
    → goods_status <> '0' AND shop 未删
    → join shop / category / merchant

auditGoods(dto, operator)
    → 校验 reasons 为空
    → pass: updateAuditPass
    → reject: updateAuditReject(rejectReason)

offShelfPlatform(dto, operator)
    → 校验全部 status=2
    → updateOffShelf
```

### 5.1 与全局「免审」关系

| **全局** `default_audit_pass`（`biz_shop_global_config`） | 商家 submit 后 | 本模块待审队列 |
|----------------------------------------------------------|----------------|----------------|
| 0（默认） | goods_status=1 | **有** |
| 1 | goods_status=2 | **一般无**；列表仍可查 status=2 并 offShelf |

---

## 6. 业务规则映射（GR）

| 编号 | 实现 |
|------|------|
| GR1 | `selectPlatformList`：`goods_status <> '0'` |
| GR2 | `auditGoods`：仅 status=1 |
| GR3 | `offShelfPlatform`：仅 status=2 |
| GR4 | `auditGoods`：`!pass` 校验 `rejectReason` |
| GR5 | `GoodsBatchOperationException` 先校验后更新 |
| GR6～GR7 | Mapper `updateAuditPass/Reject`、`updateOffShelf` |
| GR8 | `selectPlatformDetail` 任意 status≠0 |
| GR9 | 详情与列表共用 Service 方法 |
| GR10 | 商家 `updateSeller`：status=1 保持 1 |
| GR11 | submit 时读 shop 免审（商家端） |
| GR12 | 无 PUT 改 status 接口 |
| GR13 | 不 join 更新商户/店铺状态 |
| GR14 | `IGoodsFacade.hasBlockingGoodsForShopDelete` |
| GR15 | 与商品管理共用 Service |

---

## 7. 菜单权限（建议）

| 菜单 | 权限 |
|------|------|
| 商品审核 | `agri:goods:list` |
| 查看详情 | `agri:goods:query` |
| 审核 | `agri:goods:audit` |
| 下架 | `agri:goods:offshelf` |

方案 B 可将上表前缀替换为 `agri:goodsAudit:*`，接口路径改为 `/agri/goodsAudit/*`。

---

## 8. 关联方案摘要

```text
【商家】submit → goods_status=1（或免审=2）
    ↓
【平台 · 本模块】GET list（建议默认 goodsStatus=1）
    → GET detail
    → PUT audit → 2 | 3
    → PUT offShelf（对 2）→ 4
    ↓
【C 端】canPurchase（出售中+库存+店铺开业…）
```

| 对比 | 商品管理（平台） | 商品审核（本方案） |
|------|------------------|-------------------|
| 表 | biz_goods | **同表** |
| 能力 | list/detail/audit/offShelf + 无商家端 | **仅** list/detail/audit/offShelf |
| 菜单 | 可合一或拆分 | 产品定 |

---

## 9. 非本期实现

待审派单、多级审批、审核 SLA 超时自动通过、平台代改价库、独立 `biz_goods_audit_log` 表（审计走 `@Log` + `audit_time` 即可）。

---

## 10. 文档索引

| 文档 | 版本 |
|------|------|
| 商品审核功能需求.md | v1.0 |
| 商品管理技术方案.md | v1.3.3 |
| 店铺管理技术方案.md | v1.2.1 |
| 关联需求分析.md | v1.2 |

---

*文档版本：v1.1 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 实现复用 `com.ruoyi.web.modules.goods` · 关联《商品审核功能需求.md》v1.0 · v1.1 对齐发品分类口径（平台二级）*
