# 商品服务管理 — 技术方案

> **依据：** 《商品服务管理功能需求.md》v1.0.1  
> **关联：** 《商品管理功能需求》v1.3.3、《商品管理技术方案.md》v1.2；《商户管理》《店铺管理》《商品分类》功能需求 v1.3.1 / v1.3.2 / v1.3.1；《关联需求分析.md》v1.1  
> **范围：** 本文以 **商品服务模块** 库表与 **平台端** CRUD 接口为主；快照表 `biz_goods_service_snapshot` 由 **商品模块** 维护，本文给出 ER 与 Facade 契约。  
> **原则：** 全平台一份目录（非 `shop_id`）；逻辑删除；批量删除 **整批失败**；不考虑会员。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 权限 / 响应 | MyBatis；`@PreAuthorize`；`AjaxResult` / `TableDataInfo` |
| 图标 | `CommonController` 上传，库内存 URL |
| 日志 | 增删改 `@Log` |

### 1.1 模块落位

```text
baqing-shop
└── com.ruoyi.web.controller.agri.GoodsServiceController
ruoyi-agri
├── domain.BizGoodsService
├── service.IGoodsServiceCatalogService
└── facade.GoodsServiceFacadeImpl  → IGoodsServiceFacade
```

> **无** 商家端 `GoodsServiceController`（GS2）；发品下拉挂在商品模块（§4.6）。

### 1.2 业务链

```text
biz_goods_service（本模块 · 平台目录）
        │
        ├── IGoodsServiceFacade ──► 商品模块（保存快照、详情展示、删前校验）
        │
        └── biz_goods_service_snapshot（商品模块表，保存商品时写入）
                └── biz_goods.goods_id
```

| 模块 | 表/职责 |
|------|---------|
| **商品服务（本文）** | `biz_goods_service` CRUD |
| **商品管理** | `biz_goods_service_snapshot`；POST/PUT 商品写快照；详情读快照（P18～P21） |
| 商户/店铺/分类 | 无直接表关联 |

### 1.3 跨模块 Facade

| 接口 | 提供方 | 调用方 | 方法 |
|------|--------|--------|------|
| **`IGoodsServiceFacade`** | **本模块** | 商品 Service | `listAllEnabled()` — `del_flag=0`，`sort_no ASC` |
| | | | `listDefaultShow()` — `default_show_flag=1` 子集 |
| | | | `isServiceEnabled(Long serviceId)` |
| | | | `existsGoodsRef(Long serviceId)` — 删前校验（GS7） |
| **`IGoodsFacade`** | 商品 | — | 本模块 **不实现** |

**`existsGoodsRef` SQL（与商品方案 v1.2 一致）：**

```sql
SELECT 1
FROM biz_goods_service_snapshot s
INNER JOIN biz_goods g ON g.goods_id = s.goods_id AND g.del_flag = '0'
WHERE s.service_id = #{serviceId}
LIMIT 1
```

商品模块未建快照表前，可桩返回 `false`。

### 1.4 状态联动

| 事件 | `biz_goods_service` | `biz_goods.goods_status` | 快照 |
|------|---------------------|--------------------------|------|
| 增/改/删服务项 | 变更本表 | **不变** | **不 UPDATE** 已有行（P19） |
| 逻辑删服务项 | `del_flag=2` | 不变 | C 端查询过滤（P20） |
| 商户/店铺/分类变更 | 不变 | 按各模块 | 不变 |

---

## 2. 数据库设计

### 2.1 本模块表

| 表名 | 说明 |
|------|------|
| `biz_goods_service` | 平台商品服务主数据 |

### 2.2 关联表（商品模块，ER 引用）

| 表名 | 说明 |
|------|------|
| `biz_goods_service_snapshot` | 商品保存时服务展示快照；DDL 见《商品管理技术方案》§8.11 |

### 2.3 `biz_goods_service` 字段

| 字段 | 类型 | 说明 |
|------|------|------|
| service_id | bigint PK | |
| service_name | varchar(64) | 非空；`del_flag=0` 时全表唯一（应用层） |
| service_intro | varchar(500) | 简介 |
| service_icon | varchar(512) | 图标 URL |
| default_show_flag | char(1) | 0 否 1 是；默认 **1**；商家发品默认勾选 |
| sort_no | int | 升序；默认 0 |
| del_flag | char(1) | 0 存在 2 删除 |
| create_by/time, update_by/time, remark | | RuoYi 审计 |

### 2.4 索引

| 索引 | 字段 |
|------|------|
| PRIMARY | service_id |
| idx_sort | sort_no, create_time |

`service_name` 唯一：**应用层** `(service_name)` WHERE `del_flag='0'`。

### 2.5 DDL

```sql
CREATE TABLE `biz_goods_service` (
  `service_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '服务ID',
  `service_name` varchar(64) NOT NULL COMMENT '服务名称',
  `service_intro` varchar(500) NOT NULL DEFAULT '' COMMENT '简介',
  `service_icon` varchar(512) NOT NULL COMMENT '图标URL',
  `default_show_flag` char(1) NOT NULL DEFAULT '1' COMMENT '0否1是默认显示',
  `sort_no` int(11) NOT NULL DEFAULT '0' COMMENT '排序升序',
  `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0存在2删除',
  `create_by` varchar(64) DEFAULT '',
  `create_time` datetime DEFAULT NULL,
  `update_by` varchar(64) DEFAULT '',
  `update_time` datetime DEFAULT NULL,
  `remark` varchar(500) DEFAULT NULL,
  PRIMARY KEY (`service_id`),
  KEY `idx_sort` (`sort_no`,`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品服务';
```

### 2.6 字典

| dict_type | 值 |
|-----------|-----|
| yes_no | `default_show_flag` |

---

## 3. 平台端接口设计

**基路径：** `/agri/goodsService`  
**权限：** `agri:goodsService:list|add|edit|remove`

### 3.1 接口一览

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/list` | list | 分页列表 |
| GET | `/{serviceId}` | list | 详情（编辑回显） |
| POST | `/` | add | 新增 |
| PUT | `/` | edit | 编辑 |
| DELETE | `/{serviceIds}` | remove | 逻辑删除；批量逗号分隔 |

响应：`AjaxResult` / `TableDataInfo`（`code`、`msg`、`data`/`rows`/`total`）。

### 3.2 列表 `GET /list`

| Query | 说明 |
|-------|------|
| pageNum, pageSize | 分页 |
| serviceName | 可选，名称模糊 |

**排序：** `sort_no ASC`, `create_time ASC`。  
**过滤：** `del_flag='0'`。

**rows：**

| 字段 | 说明 |
|------|------|
| serviceId, serviceName, serviceIntro, serviceIcon | |
| defaultShowFlag, defaultShowFlagLabel | 字典 yes_no |
| sortNo | 列表可不展示列 |

### 3.3 新增 `POST /`

```json
{
  "serviceName": "正品保证",
  "serviceIntro": "平台认证正品",
  "serviceIcon": "/profile/upload/s1.png",
  "defaultShowFlag": "1",
  "sortNo": 0
}
```

| 字段 | 校验 |
|------|------|
| serviceName | 非空 ≤64；未删唯一 |
| serviceIntro | 非空 ≤500 |
| serviceIcon | 非空 URL |
| defaultShowFlag | `0` / `1` |
| sortNo | 整数 ≥0 |

### 3.4 编辑 `PUT /`

Body 同新增 + `serviceId`；名称唯一（排除自身）。

### 3.5 删除 `DELETE /{serviceIds}`

```text
foreach serviceId:
  if existsGoodsRef(serviceId) → 加入 reasons
if reasons 非空 → 500，整批不删
else → 二次确认（前端）→ UPDATE del_flag='2'
```

```json
{
  "code": 500,
  "msg": "整批操作已取消",
  "data": {
    "reasons": ["服务「正品保证」已被商品使用，无法删除"]
  }
}
```

### 3.6 协作接口（商品模块实现）

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/agri/seller/goods/serviceOptions` | 商家发品；`listAllEnabled` + 标 `defaultShow` |
| GET | `/agri/goods/serviceOptions` | 平台可选（若需要） |

**serviceOptions 响应示例：**

```json
{
  "code": 200,
  "data": [
    {
      "serviceId": 1,
      "serviceName": "正品保证",
      "serviceIntro": "平台认证正品",
      "serviceIcon": "/profile/upload/s1.png",
      "defaultShowFlag": "1",
      "sortNo": 0
    }
  ]
}
```

**商品保存 Body 扩展（商品模块）：**

```json
{
  "goodsName": "复合肥 50kg",
  "categoryId": 10,
  "serviceIds": [1, 2]
}
```

保存时：校验 `serviceIds` ⊆ `listAllEnabled()` → 重写快照（见商品方案 §8.11）。

---

## 4. Service 要点

```text
insert  → 唯一名校验 → default_show_flag 默认 1
update  → 唯一名校验（排除自身）→ 不触碰 snapshot 表
remove  → 循环 existsGoodsRef → 全通过则同事务 UPDATE del_flag=2
```

| 场景 | msg |
|------|-----|
| 名称重复 | 服务名称已存在 |
| 不存在 | 商品服务不存在或已删除 |
| 有关联商品 | 该服务已被商品使用，无法删除 |
| 批量失败 | 整批操作已取消 |

---

## 5. 端到端流程

```text
【平台】POST /agri/goodsService → 维护目录
    ↓
【商家】GET .../serviceOptions → defaultShowFlag=1 预勾选
    ↓
【商家】POST/PUT /agri/seller/goods + serviceIds → 写 snapshot
    ↓
【平台】GET /agri/goods/{id} → 详情读 snapshot（只读）
    ↓
【C 端】商品详情 → snapshot；JOIN catalog 过滤 del_flag=2
```

**平台改服务文案：** 仅改 `biz_goods_service`；已售商品展示 **不变**（P19）。  
**平台删服务：** `existsGoodsRef` 阻塞；若历史快照存在且商品未删，须商家保存商品剔除（P21）或运营改商品取消勾选。

---

## 6. 业务规则映射

| 编号 | 实现 |
|------|------|
| GS1～GS2 | 单表无 shop_id；仅平台 Controller |
| GS3 | insert/update 唯一名校验 |
| GS4 | list/order by sort_no |
| GS5 | `default_show_flag`；serviceOptions 带给前端 |
| GS6 | 改 default_show 不 UPDATE snapshot |
| GS7 | `existsGoodsRef` |
| GS8 | DELETE 整批失败 |
| GS9～GS12 | 不级联商品状态 |
| GS13～GS15 | 快照逻辑在商品 Service（§3.6、商品 §8.11） |
| P18～P21 | 商品模块 |

---

## 7. 菜单权限

| 菜单 | 权限标识 |
|------|----------|
| 商品服务管理 | `agri:goodsService:list` |
| 新增 | `agri:goodsService:add` |
| 修改 | `agri:goodsService:edit` |
| 删除 | `agri:goodsService:remove` |

---

## 8. 非本期实现

服务分组、多语言、恢复已删、导出、按服务检索全平台商品、`biz_goods_service_rel` 中间表（本期仅用 snapshot + serviceIds）。

---

## 9. 文档索引

| 文档 | 版本 |
|------|------|
| 商品服务管理功能需求.md | v1.0.1 |
| 商品管理功能需求/技术方案 | v1.3.3 / v1.2 |
| 关联需求分析.md | v1.1 |
| 商品服务管理测试用例.md | v1.0 |
| 文档索引.md | v1.6 |

---

*文档版本：v1.0.1 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《商品服务管理功能需求.md》v1.0.1、《商品服务管理测试用例.md》v1.0*
