# 商城入驻协议 — 技术方案

> **依据：** 《商城入驻协议功能需求.md》v1.0  
> **关联：** 《商户管理技术方案.md》v1.4、《会员管理技术方案.md》v1.0、《首页banner设置技术方案.md》v1.0、《关联需求分析.md》v1.2；内容管理 **商城服务协议**（草稿，表结构可复用本模式）  
> **范围：** 全平台 **单条** 入驻协议配置；平台查看/保存；C 端只读拉取；**无** 商户/店铺表 FK。  
> **原则：** 单表单行（`config_id=1`）；启用时正文必填；C 端 `@Anonymous`；平台代录入商户 **不调用** 本校验。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 权限 / 响应 | MyBatis；`@PreAuthorize`；`AjaxResult` |
| 富文本 | 库字段 `LONGTEXT`；展示层 XSS 过滤（见 §5） |
| 日志 | 保存配置 `@Log` |

### 1.1 模块落位

```text
baqing-shop
├── com.ruoyi.web.modules.content
│   ├── controller.MerchantEntryAgreementController      # 平台单页配置
│   ├── controller.MerchantEntryAgreementAppController    # C 端只读
│   ├── domain.BizMerchantEntryAgreement
│   ├── mapper.BizMerchantEntryAgreementMapper
│   ├── service.IMerchantEntryAgreementService
│   ├── constant.MerchantEntryAgreementConstants
│   └── support.AgreementContentSupport                   # 空内容/HTML 校验
sql/
└── biz_merchant_entry_agreement.sql
```

> 与 **首页 Banner** 同属 `content` 包；**不** 与 `banner` 共表。

### 1.2 业务链

```text
biz_merchant_entry_agreement（config_id=1 · 单例）
        │
        ├── 平台 GET/PUT /agri/merchantEntryAgreement
        │
        ├── C 端 GET /api/merchant/entry/agreement（Anonymous）
        │       └── enabled=false → 入驻入口/提交阻断（前端 + 后续入驻 API）
        │
        └── （预留）C 端入驻提交 API
                └── 校验 agreementAccepted=true（AP3，见 §4.4）
```

| 模块 | 关系 |
|------|------|
| 商户管理 | `POST /agri/merchant` **不校验** 本协议（AP8） |
| 会员 / 服务协议 | 独立表、独立接口（草稿：`biz_mall_service_agreement` 等，可同构） |
| 店铺 / 商品 / 订单 | 无依赖；改协议不级联 |

### 1.3 跨模块契约（预留）

| 接口 | 提供方 | 调用方 | 方法 |
|------|--------|--------|------|
| **`IMerchantEntryAgreementFacade`** | **本模块** | 未来 C 端「商家入驻」Service | `isEnabled()` |
| | | | `assertAccepted(boolean agreementAccepted)` — 未勾选抛业务异常 |
| | | | `getCheckboxLabel()` — 拼接标题+版本 |

入驻提交 API **未实现前**，Facade 可先落地供单测；商户模块 **不引用**。

### 1.4 状态联动

| 事件 | `biz_merchant_entry_agreement` | `biz_merchant` 等 |
|------|-------------------------------|-------------------|
| 保存协议/改启用 | 更新本表 | **不变** |
| 商户冻结、开店 | — | **不变** |

---

## 2. 数据库设计

### 2.1 本模块表

| 表名 | 说明 |
|------|------|
| `biz_merchant_entry_agreement` | 商家入驻协议 **单例配置**（全平台一条有效记录） |

### 2.2 字段

| 字段 | 类型 | 说明 |
|------|------|------|
| config_id | bigint PK | 固定 **1**（应用层常量 `CONFIG_ID=1`） |
| agreement_title | varchar(128) | 协议标题；非空 |
| version_label | varchar(32) | 版本号；可空 |
| content | longtext | 富文本 HTML；启用时非空 |
| enable_flag | char(1) | `0` 否 `1` 是；默认 **0** |
| create_by, create_time, update_by, update_time, remark | | RuoYi 审计 |

> **无** `del_flag`、**无** 多版本行；历史审计靠 `update_time` / 操作日志。

### 2.3 单例约定

| 规则 | 实现 |
|------|------|
| 仅一行 | `SELECT ... WHERE config_id = 1` |
| 首次保存 | 无记录则 `INSERT config_id=1`；有则 `UPDATE` |
| 禁止 DELETE | 不提供删除接口 |

### 2.4 DDL

脚本：`sql/biz_merchant_entry_agreement.sql`

```sql
CREATE TABLE `biz_merchant_entry_agreement` (
  `config_id` bigint(20) NOT NULL COMMENT '固定为1',
  `agreement_title` varchar(128) NOT NULL DEFAULT '' COMMENT '协议标题',
  `version_label` varchar(32) DEFAULT NULL COMMENT '版本号',
  `content` longtext COMMENT '富文本正文',
  `enable_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0否1是',
  `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 (`config_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家入驻协议配置';

INSERT INTO `biz_merchant_entry_agreement` (
  `config_id`, `agreement_title`, `version_label`, `content`, `enable_flag`, `create_time`
) VALUES (
  1, '农资商城商家入驻协议', 'v1.0', '', '0', NOW()
);
```

### 2.5 字典

| dict_type | 用途 |
|-----------|------|
| sys_yes_no | `enable_flag` 平台页展示 |

---

## 3. 平台端接口设计

**基路径：** `/agri/merchantEntryAgreement`  
**权限：** `agri:merchantEntryAgreement:query` | `edit`

### 3.1 接口一览

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/` | query | 获取当前配置（无记录时返回默认空壳） |
| PUT | `/` | edit | 保存配置（insert or update） |

### 3.2 获取配置 `GET /`

**响应 `data`：**

```json
{
  "configId": 1,
  "agreementTitle": "农资商城商家入驻协议",
  "versionLabel": "v1.0",
  "content": "<p>协议正文...</p>",
  "enableFlag": "1",
  "enableFlagLabel": "是",
  "updateBy": "admin",
  "updateTime": "2026-05-26 10:00:00"
}
```

| 场景 | 行为 |
|------|------|
| 表无记录 | `configId=1`；标题/内容空；`enableFlag=0` |

### 3.3 保存配置 `PUT /`

```json
{
  "agreementTitle": "农资商城商家入驻协议",
  "versionLabel": "v1.0",
  "content": "<p>正文 HTML</p>",
  "enableFlag": "1",
  "remark": ""
}
```

| 字段 | 校验 |
|------|------|
| agreementTitle | 非空，≤128 |
| versionLabel | 可空，≤32 |
| enableFlag | `0` / `1` |
| content | `enableFlag=1` 时非空且去 HTML 标签后非空；`LONGTEXT` 建议应用层上限 **512KB** |
| enableFlag=0 | `content` 可空 |

**成功：** `code=200`，`msg=操作成功`；**立即** 对 C 端下一请求生效（AP9）。

### 3.4 错误文案（Service）

| 场景 | msg |
|------|-----|
| 标题空 | 请输入协议标题 |
| 启用但内容空 | 启用状态下请填写协议内容 |
| 内容仅空白标签 | 协议内容不能为空 |
| 启用参数非法 | 是否启用参数无效 |

---

## 4. C 端接口设计

**基路径：** `/api/merchant/entry`  
**鉴权：** `@Anonymous`

### 4.1 获取入驻协议 `GET /api/merchant/entry/agreement`

| 项 | 说明 |
|----|------|
| 数据源 | `config_id=1` |
| 缓存 | 本期 **不做** Redis；读库即时 |

**`enable_flag=1` 且内容有效：**

```json
{
  "code": 200,
  "data": {
    "enabled": true,
    "agreementTitle": "农资商城商家入驻协议",
    "versionLabel": "v1.0",
    "content": "<p>HTML...</p>",
    "checkboxLabel": "我已阅读并同意《农资商城商家入驻协议》v1.0"
  }
}
```

**`checkboxLabel` 规则：**

```text
我已阅读并同意《{agreementTitle}》
若 versionLabel 非空 → 我已阅读并同意《{agreementTitle}》（{versionLabel}）
```

**`enable_flag=0` 或内容无效：**

```json
{
  "code": 200,
  "data": {
    "enabled": false,
    "message": "商家入驻暂未开放"
  }
}
```

> C 端 **不返回** 禁用态下的历史正文（AP4）。

### 4.2 与会员注册的关系

| 接口 | 用途 |
|------|------|
| `GET /api/member/...` | 会员/服务协议（另模块） |
| `GET /api/merchant/entry/agreement` | **仅** 商家入驻 |

### 4.3 入驻开放状态（可选便捷接口）

`GET /api/merchant/entry/status`

```json
{
  "code": 200,
  "data": {
    "entryOpen": true
  }
}
```

`entryOpen` = `isEnabled()`；供 C 端控制入口显隐（**可选**，可与 §4.1 合并由前端读 `enabled`）。

### 4.4 未来：C 端提交入驻 `POST /api/merchant/entry`（预留）

| 字段 | 校验 |
|------|------|
| `agreementAccepted` | 必须为 `true`；否则「请先阅读并同意商家入驻协议」 |
| | 且 `isEnabled()` 为 true |

主体字段与 `biz_merchant` 对齐 **另册**；落库 **不经过** `POST /agri/merchant` 时代录路径。

---

## 5. Service 要点

```text
getConfig()     → selectById(1) or 默认空壳 VO
saveConfig(dto) → validate → insert(1) or update(1)
getForApp()     → enabled? 拼 checkboxLabel : {enabled:false}
isEnabled()     → enable_flag=1 && content 非空
assertAccepted  → !accepted → ServiceException
```

### 5.1 富文本与安全

| 项 | 实现建议 |
|----|----------|
| 存储 | 原样存 `content` |
| C 端/平台预览输出 | 可选 `HtmlUtil.clean` 白名单（与 RuoYi XSS 配置一致） |
| 空内容判定 | `AgreementContentSupport.isBlankHtml(content)` — 去标签后 `trim` 为空视为无内容 |

---

## 6. 业务规则映射

| 编号 | 实现 |
|------|------|
| AP1 | 单表单行 `config_id=1` |
| AP2 | 仅 `MerchantEntryAgreementController` + 菜单权限 |
| AP3 | `assertAccepted`（入驻 API / Facade） |
| AP4 | `getForApp`：`enable_flag=0` → `enabled=false` |
| AP5 | `saveConfig` 启用时校验 content |
| AP6 | 无历史表；不更新已入驻商户 |
| AP7 | 独立表；路径 `/merchant/entry` vs 会员 `/member` |
| AP8 | 商户 `MerchantController` 不注入 Facade |
| AP9 | 无发布表；读写同一行 |
| AP10 | 无级联 UPDATE 商户/店铺 |

---

## 7. 菜单权限

| 菜单（建议） | 权限标识 |
|--------------|----------|
| 商城入驻协议 | `agri:merchantEntryAgreement:query` |
| 保存 | `agri:merchantEntryAgreement:edit` |

上级：**农资管理 → 内容管理**（与 Banner、服务协议并列）。

---

## 8. 关联方案摘要

```text
【平台】PUT /agri/merchantEntryAgreement → 维护正文与启用
    ↓
【C 端】GET /api/merchant/entry/agreement → 展示 + checkboxLabel
    ↓
【C 端】（未来）POST /api/merchant/entry + agreementAccepted=true
    ↓
【平台】审核/代录入 → 商户管理（不要求勾选本协议）

【并行】POST /agri/merchant → 平台代入驻，无协议校验
```

| 对比 | 首页 Banner | 本模块 |
|------|-------------|--------|
| 数据形态 | 多行列表 | **单例一行** |
| 平台 API | CRUD + 排序 | **GET + PUT** |
| C 端 | 列表展示 | 富文本 + 勾选语义 |
| 启用 | enable_flag | enable_flag + content 有效 |

---

## 9. 非本期实现

协议版本历史表、勾选存证、强制阅读时长、PDF、多语言、`sys_config` 存正文、Redis 缓存。

---

## 10. 文档索引

| 文档 | 版本 |
|------|------|
| 商城入驻协议功能需求.md | v1.0 |
| 商户管理功能需求/技术方案 | v1.4 |
| 首页banner设置技术方案.md | v1.0 |
| 关联需求分析.md | v1.2 |

---

*文档版本：v1.0 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《商城入驻协议功能需求.md》v1.0*
