# 商户管理 — 技术方案

> **依据：** 《商户管理功能需求.md》v1.7  
> **v1.7：** 省市区三级数据 **`GET /agri/region/tree`**（平台）、**`GET /api/region/tree`**（C 端）；商户 **注册地址** `reg_region_*`、**经营地址** `biz_region_*` 落库约定见 §9.9。  
> **v1.7：** 对齐 `biz_merchant_account` 新结构：`account_id=sys_user.user_id`；绑定仅 insert 关联行，登录凭证在 `sys_user`。
> **v1.6：** 角色 **role_key=merchant/member**；`MerchantBindMapper` 严格按 `role_key` 过滤；会员绑定复用会员 `sys_user` 并追加 merchant 角色；`sysUserInitPassword` DTO 字段 **后端未使用**。  
> **v1.5：** 同步 `sql/biz_merchant.sql` 全字段 COMMENT；新增 `id_card_type`、`legal_id_card_type`、`reg_region_*`、`corp_bank_account`；平台企业新增仅校验法人姓名+企业名称。  
> **关联：** 《店铺管理》《商品分类》《商品管理》功能需求 v1.3.6 / v1.3.1 / v1.3.3（`doc/农资商城web/`）  
> **范围：** 本文以 **商户模块** 的数据库、接口为主；关联模块给出 **表关系、Facade 契约、状态联动**，不展开店铺/商品/分类/订单的完整表结构与接口。  
> **原则：** 平台 **新增商户** 时通过 `IMerchantAccountBindService` 创建首条 **商户经营账号**；C 端入驻经 **入驻审核+公示** 后由 `MerchantEntryApplyService` 建档并绑会员。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 权限 / 响应 | MyBatis；Spring Security + `@PreAuthorize`；`AjaxResult` / `TableDataInfo` |
| 文件 | 证照走 RuoYi `CommonController` 上传，库内存 URL |
| 操作日志 | 增删改、改认证状态使用 `@Log`（RuoYi 惯例） |

### 1.1 模块包路径（已实现）

```text
baqing-shop
├── com.ruoyi.web.modules.merchant.controller.MerchantController
├── com.ruoyi.web.modules.merchant.service.impl.MerchantServiceImpl
├── com.ruoyi.web.modules.merchant.service.impl.MerchantAccountBindServiceImpl
├── com.ruoyi.web.modules.merchant.mapper.MerchantBindMapper
└── resources/mapper/merchant/MerchantBindMapper.xml
```

### 1.2 业务链与数据归属

```text
biz_merchant（商户）
    └── biz_shop（店铺，merchant_id）── shop_count 回写商户
            ├── biz_merchant_account（经营账号，同商户多店同登录名；店铺模块维护）
            ├── biz_goods_category（分类，shop_id）
            └── biz_goods（商品，shop_id + category_id）
                    └── biz_order（订单，shop_id；删商户/店时校验）
```

| 模块 | 核心表（关联设计用） | 与商户关系 |
|------|---------------------|------------|
| 商户管理 | `biz_merchant` | — |
| 店铺管理 | `biz_shop` | `merchant_id`；维护 `shop_count` |
| 经营账号 | `biz_merchant_account` | `merchant_id`；**入驻时商户模块插入首条**；店铺模块 CRUD 其余 |
| 商品分类 | `biz_goods_category` | 经 `shop_id` 间接 |
| 商品管理 | `biz_goods` | 经 `shop_id` 间接；商户冻结 **不** 改商品状态 |
| 订单 | `biz_order` | 删商户/店校验 **未完成订单** O10；DDL [`sql/biz_order.sql`](../../../sql/biz_order.sql) |

### 1.3 跨模块 Service 契约（Java Facade）

| 接口 | 提供方 | 调用方 | 方法 |
|------|--------|--------|------|
| `IMerchantShopFacade` | 店铺 | 商户 | `incrementShopCount` / `decrementShopCount`（**与店铺写同事务**） |
| `IOrderFacade` | 订单（**目标包** `com.ruoyi.web.modules.order`，**待实现**） | 商户、店铺 | `hasUnfinishedOrdersByMerchant(Long merchantId)` |
| `IMerchantFacade` | 商户 | 店铺 | `checkOpenShop`、`listSelectableMerchants` |

> 订单未实现前，`hasUnfinishedOrdersByMerchant` 由 **`store.facade.DefaultShopOrderFacade`** 桩返回 `false`。

### 1.4 状态联动（技术侧不落库自动任务）

| 商户事件 | 店铺 | 商品 | C 端 |
|----------|------|------|------|
| 冻结 / 注销 | 不可新开店；已有店 **不自动** 停业 | 状态 **不变** | 不因商户冻结单独拦单 |
| 恢复正常 | `openShopCheck` 通过后可开店 | — | — |
| 逻辑删除 | 须 `shop_count=0` | — | — |

---

## 2. 数据库设计（商户模块）

### 2.1 本模块表

| 表名 | 说明 |
|------|------|
| `biz_merchant` | 主体资质 + 经营信息 + 认证状态 + `shop_count` |

### 2.2 需求字段与库字段对照

| 需求（功能/C 端） | 库字段 | 备注 |
|------------------|--------|------|
| 证件类型 | `id_card_type` / `legal_id_card_type` | 1大陆身份证 2来往内地通行证 |
| 注册地址省市区 | `reg_region_code`、`reg_region_name` | 企业 |
| 经营地址省市区 | `biz_region_code`、`biz_region_name` | 个人/企业 |
| 对公银行账号 | `corp_bank_account` | 企业法人 |
| 证件/营业「长期有效」 | `*_valid_type=2` | `*_valid_end` 可 NULL |
| 经营账号 | `biz_merchant_account` 关联行 + `sys_user` 登录凭证 | 见 `sql/biz_shop.sql`；`account_id=user_id`，`shop_id=NULL` 为商户管理员 |

### 2.3 字段摘要

| 分组 | 关键字段 |
|------|----------|
| 主键/状态 | `merchant_id`, `merchant_type`, `cert_status`, `cert_time`, `biz_complete`, `shop_count` |
| 个人主体 | `person_name`, `id_card_type`, `id_card_no`, `id_valid_*`, `id_card_front/back` … |
| 企业法人 | `legal_*`, `corp_bank_account`, `account_permit` |
| 企业信息 | `company_name`, `credit_code`, `reg_region_*`, `license_valid_*` … |
| 经营/结算 | `merchant_name`, `service_phone`, `biz_region_*`, `contact_*`, `bank_*`, `business_license` |

※ `id_card_no`、`credit_code` 入驻后不可 UPDATE；`merchant_name` 非空时唯一（应用层）。

### 2.4 计算与维护规则

**`biz_complete`：** 见 §9.2 字段清单，全部有值且校验通过 → `1`。

**`cert_time`：** 仅入驻成功、改 `cert_status` 时更新。

**`shop_count`：** 店铺增删时 Facade 维护；见 §9.7。

### 2.5 DDL

> **完整 DDL 及逐字段 COMMENT：** 见仓库 [`sql/biz_merchant.sql`](../../../../sql/biz_merchant.sql)。下文仅列结构要点，**不以本文为准覆盖 SQL 文件**。

```sql
-- 见 sql/biz_merchant.sql（含 id_card_type、reg_region_*、corp_bank_account 等 v1.5 字段）
```

### 2.6 字典（sys_dict）

| dict_type | 值 |
|-----------|-----|
| merchant_type | 1个人 2企业 |
| merchant_cert_status | 0正常 1已冻结 2已注销 |
| valid_period_type | 1区间 2长期 |

---

## 3. 接口设计（商户模块）

**路径：** `/agri/merchant`  
**权限：** `agri:merchant:list|query|add|edit|remove|cert`

### 3.1 接口一览

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/list` | 分页列表 |
| GET | `/{merchantId}` | 详情 |
| POST | `/` | 新增（主体资质 + 绑定账号，`MerchantCreateDTO`） |
| GET | `/adminUserOptions` | 入驻可选平台管理员（role_key=merchant） |
| GET | `/memberOptions` | 入驻可选会员 |
| PUT | `/` | 编辑 |
| PUT | `/{merchantId}/certStatus` | 改认证状态（支持两步 confirm） |
| GET | `/{merchantId}/deleteCheck` | 删除预检 |
| DELETE | `/{merchantIds}` | 逻辑删除（批量，**任一条失败则整批失败**） |
| GET | `/selectList` | 开店选商户 |
| GET | `/{merchantId}/openShopCheck` | 是否可开店 |
| GET | `/{merchantId}/shops` | 商户下店铺简要列表 |

**省市区（注册/经营地址选择，独立模块）：** `GET /agri/region/tree` — 见 §9.9；C 端同源 `GET /api/region/tree`。

### 3.2 列表 `GET /list`

| Query | 说明 |
|-------|------|
| merchantName | 模糊 `merchant_name` / `person_name` / `company_name` |
| certStatus | 0/1/2 |

| 响应 rows | 说明 |
|-----------|------|
| merchantId, merchantType | |
| unitName, merchantName | 名称空→前端「待完善」 |
| contactName, contactPhone | 未完善为 null，前端显示「—」 |
| shopCount, certStatus, certTime | |
| canEdit | cert_status∈{0,1} |
| canDelete | 同 deleteCheck 全部通过 |

### 3.3 详情 `GET /{merchantId}`

| 字段 | 说明 |
|------|------|
| 全表字段 | 主体 + 经营 |
| bizComplete | 0/1 |
| pendingBizFields | 未完整时的 **中文标签列表**（§9.2） |
| allowedCertStatuses | `[{value,label,needConfirm,confirmMessage}]`（§9.1） |
| canEdit, canDelete | |
| shopCount, shops | `[{shopId,shopName,shopStatus}]` |
| shopListUrl | `/agri/shop/list?merchantName={merchantName或unitName}` |

### 3.4 新增 `POST /`

- Body：`MerchantCreateDTO` = 主体字段（§9.3）+ 绑定字段（§9.8）。拒绝经营字段入库。  
- **平台主体校验**（`MerchantServiceImpl.validateSubjectOnInsert`）：个人 **仅姓名** 必填；企业 **仅法人姓名 + 企业名称** 必填；信用代码有值才验唯一；经营信息 **非必填**。  
- 成功：`cert_status=0`, `cert_time=now()`, `biz_complete=0`；同事务调用 `IMerchantAccountBindService.bindAccountOnMerchantCreate`。  
- 过期：`data.warnExpired=true`, `data.expiredItems=[...]`，**仍 200 成功**（绑定已执行）。  
- 响应 msg：「请尽快在编辑中完善商户经营信息后再开设店铺」。

### 3.4.1 管理员选项 `GET /adminUserOptions`

| Query | 说明 |
|-------|------|
| keyword | 可选；模糊 `user_name` / `phonenumber` |

| 响应 data[] | 说明 |
|-------------|------|
| userId, userName, nickName, phonenumber | 仅含 `sys_user` 且拥有 **role_key=merchant** 的用户；最多 50 条 |

权限：`agri:merchant:add`

### 3.4.2 会员选项 `GET /memberOptions`

| Query | 说明 |
|-------|------|
| keyword | 可选；模糊 `user_name`（会员名称）/ `phonenumber` |

| 响应 data[] | 说明 |
|-------------|------|
| memberId, memberCode, nickName, mobile | `sys_user` 且 **role_key=member**；`memberId=userId`；`memberCode`= `user_name`（会员名称）；mobile 脱敏；最多 50 条 |

权限：`agri:merchant:add`

### 3.5 编辑 `PUT /`

- 前置：`cert_status`∈{0,1}；拒收 `certStatus`。  
- 不可改：`merchantType`, `idCardNo`, `creditCode`。  
- 重算 `biz_complete`；`bizCompleteChanged:true` 时 msg 可带「已可开设店铺」。

### 3.6 认证状态 `PUT /{merchantId}/certStatus`

**两步确认协议（`confirm`）：**

| confirm | 行为 |
|---------|------|
| `false` 或未传 | 仅校验是否允许流转；**不持久化**；返回 `code=200`, `data.needConfirm=true`, `data.confirmMessage` |
| `true` | 持久化并更新 `cert_time` |

状态机见 **§9.1**。

### 3.7 删除

**预检 `GET /{merchantId}/deleteCheck`**

```json
{
  "code": 200,
  "data": {
    "canDelete": false,
    "reasons": ["存在未完成订单", "已绑定店铺数量为 2"]
  }
}
```

**执行 `DELETE /{merchantIds}`**

| 规则 | 说明 |
|------|------|
| 前置 | cert_status∈{0,1}；shop_count=0；无未完成订单 |
| 批量 | **任一条不通过 → 整批失败**，`del_flag` 均不变 |
| 前端 | 列表二次确认文案：「删除后不可恢复，是否继续？」 |

### 3.8 协作接口

**`GET /selectList`**：`del_flag=0 AND cert_status=0 AND biz_complete=1`（**不含** `shop_count` 上限条件）

**`GET /{id}/openShopCheck`**：`allowed` + `reason`（经营信息未完善 / 商户已冻结 / 商户已注销 / 商户已删除）

---

## 4. 端到端关联流程

（同 v1.1：入驻 → 补全经营 → 开店 → 分类/商品；删店 → 删商户。）

---

## 5. 业务规则映射

| 编号 | 实现 |
|------|------|
| R1 | **不设** 店铺数量上限；`shop_count` 仅统计；selectList / openShopCheck **不校验** 店数 |
| R2 | `MerchantAccountBindServiceImpl`；`MerchantBindMapper` |
| R12 | `BIND_SYS_USER`：insert `(account_id=user_id, merchant_id, shop_id=NULL)`；登录名/姓名取自既有 `sys_user` |
| R13 | `BIND_MEMBER`：追加 role_key=merchant + insert 同上；须会员 `user_name`/`nick_name` 非空 |
| R3 | insert `cert_status=0` + `cert_time` |
| R4 | update 忽略 `merchantType` |
| R5 | `certStatus` 仅 `PUT .../certStatus` |
| R6 | selectList 三条件过滤 |
| R7 | 编辑校验 `merchant_name` 唯一；入驻校验 `credit_code` 唯一 |
| R8 | deleteCheck + DELETE + OrderFacade |
| R9 | 无级联改 shop/goods |
| R10 | POST 主体白名单 |
| R11 | SQL 不更新 `id_card_no`、`credit_code` |

---

## 6. 菜单权限

| 权限 | 标识 |
|------|------|
| 列表 | agri:merchant:list |
| 查询 | agri:merchant:query |
| 新增 | agri:merchant:add |
| 修改 | agri:merchant:edit |
| 删除 | agri:merchant:remove |
| 改认证 | agri:merchant:cert |

---

## 7. 文档索引

| 文档 | 版本 |
|------|------|
| 商户管理功能需求.md | v1.7 |
| 商户管理测试用例.md | v1.4 |
| 店铺管理功能需求/技术方案 | v1.3.6 / v1.2.5 |
| 商品分类功能需求/技术方案 | v1.3.1 / v1.1 |
| 商品管理功能需求/技术方案 | v1.3.3 / v1.2 |

---

## 8. 非本期实现

会员注册/等级运营、OCR、三方实名、导出 Excel、商户冻结自动停业。

---

## 9. 实现补充（联调契约）

### 9.1 认证状态机

| 当前 | 目标 | 允许 | needConfirm | confirmMessage（示例） |
|:----:|:----:|:----:|:-----------:|------------------------|
| 0 正常 | 1 冻结 | ✓ | 是 | 冻结后不可新开店铺，是否继续？ |
| 0 正常 | 2 注销 | ✓ | 是 | 注销后资料不可编辑，是否继续？ |
| 1 冻结 | 0 正常 | ✓ | 是 | 确认恢复为正常？ |
| 1 冻结 | 2 注销 | ✓ | 是 | 注销后资料不可编辑，是否继续？ |
| 2 注销 | 0 正常 | ✓ | 是 | 恢复后可编辑、可开店（须经营信息完整），是否继续？ |
| 2 注销 | 1 冻结 | ✗ | — | — |
| 同态 | 同态 | ✗ | — | — |

`allowedCertStatuses` 由上表按当前 `cert_status` 过滤生成。已注销 **仅** 可目标 `0`。

### 9.2 经营信息完整度与 `pendingBizFields`

| 字段 key | 中文标签 | 个人 | 企业 |
|----------|----------|:----:|:----:|
| merchantName | 商户名称 | ✓ | ✓ |
| servicePhone | 客服电话 | ✓ | ✓ |
| bizRegion | 经营地址 | ✓ | ✓ |
| bizDetailAddress | 经营详细地址 | ✓ | ✓ |
| contactName | 联系人姓名 | ✓ | ✓ |
| contactPhone | 联系人手机 | ✓ | ✓ |
| contactEmail | 联系人邮箱 | ✓ | ✓ |
| bankName | 开户银行 | ✓ | ✓ |
| bankBranch | 支行名称 | ✓ | ✓ |
| bankAccount | 银行账号 | ✓ | ✓ |
| businessLicense | 营业执照电子版 | — | ✓ |
| accountPermit | 开户许可证 | — | ✓ |

`bizRegion` 判定：`biz_region_code` 与 `biz_region_name` 均有值。  
详情接口：`pendingBizFields` = 上表未填项的中文标签数组。

### 9.3 请求 JSON 样例

**个人 — 新增 `POST /`（仅主体）**

```json
{
  "merchantType": "1",
  "personName": "张三",
  "idCardNo": "110101199001011234",
  "birthDate": "1990-01-01",
  "idValidType": "1",
  "idValidStart": "2020-01-01",
  "idValidEnd": "2030-01-01",
  "residenceAddress": "北京市朝阳区xxx",
  "gender": "0",
  "idCardFront": "/profile/upload/2025/01/a.jpg",
  "idCardBack": "/profile/upload/2025/01/b.jpg"
}
```

**企业 — 新增 `POST /`**

```json
{
  "merchantType": "2",
  "legalName": "李四",
  "legalIdCardNo": "110101198001011234",
  "legalBirthDate": "1980-01-01",
  "legalIdValidType": "2",
  "legalIdValidStart": "2015-01-01",
  "legalIdValidEnd": null,
  "legalResidence": "北京市海淀区xxx",
  "legalGender": "0",
  "legalIdCardFront": "/profile/upload/legal_f.jpg",
  "legalIdCardBack": "/profile/upload/legal_b.jpg",
  "legalExtraPhoto": null,
  "companyName": "某某农资有限公司",
  "creditCode": "91110000MA01234567",
  "regAddress": "北京市西城区xxx",
  "companyDetailAddress": "xx大厦10层",
  "businessScope": "农资销售",
  "licenseValidType": "1",
  "licenseValidStart": "2020-01-01",
  "licenseValidEnd": "2030-12-31"
}
```

**编辑 `PUT /`（个人，含经营信息片段）**

```json
{
  "merchantId": 1,
  "personName": "张三",
  "idCardNo": "110101199001011234",
  "merchantName": "张三农资店",
  "servicePhone": "400-800-1234",
  "bizRegionCode": "110105",
  "bizRegionName": "北京市/朝阳区",
  "bizDetailAddress": "xx路1号",
  "contactName": "张三",
  "contactPhone": "13800138000",
  "contactEmail": "zhang@example.com",
  "bankName": "中国工商银行",
  "bankBranch": "朝阳支行",
  "bankAccount": "622202xxxxxxxxxxxx"
}
```

### 9.4 字段校验规则（Service / JSR303）

| 字段 | 规则 |
|------|------|
| idCardNo / legalIdCardNo | 18 位身份证格式（含 X） |
| creditCode | 18 位统一社会信用代码 |
| contactPhone, servicePhone | 手机或固话（项目统一正则） |
| contactEmail | 邮箱格式 |
| bankAccount | 数字 8～30 位 |
| idValidType=1 | start、end 必填，end≥start |
| idValidType=2 | end 可空；start 建议必填 |
| 图片 URL | 非空；后缀 jpg/png/jpeg；单张 ≤5MB（与 RuoYi 上传配置一致） |
| 主体必填 | 按 merchantType 分组校验（入驻/编辑共用 Validator） |

### 9.5 业务错误文案（`AjaxResult.msg`）

| 场景 | msg |
|------|-----|
| 信用代码重复 | 该统一社会信用代码已入驻 |
| 证件号重复 | 该证件号码已入驻 |
| 商户名称重复 | 商户名称已存在 |
| 已注销不可编辑 | 商户已注销，不可编辑 |
| 不可改认证 | 请在查看详情中修改认证状态 |
| 删除-有店铺 | 请先删除该商户下全部店铺 |
| 删除-有订单 | 存在未完成订单，无法删除 |
| 删除-已注销 | 已注销商户不可删除 |
| 批量删除失败 | 部分商户不满足删除条件，操作已取消 |
| 状态不允许 | 不允许的认证状态变更 |
| 未选绑定 | 请选择并绑定管理员账号或会员账号 |
| 用户无 merchant 角色 | 所选用户未分配商户经营角色 |
| 登录名重复 | 该账号已绑定商户经营账号（`countByAccountId`） |
| 会员无昵称 | 会员昵称不能为空，无法作为经营账号管理员姓名 |
| 会员无名称 | 会员名称不能为空，无法作为经营账号登录名 |

### 9.8 入驻绑定请求字段（`MerchantCreateDTO`）

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| bindType | String | ✓ | `SYS_USER` 或 `MEMBER` |
| bindUserId | Long | SYS_USER 时 ✓ | `sys_user.user_id`（role_key=merchant） |
| bindMemberId | Long | MEMBER 时 ✓ | `member_id = sys_user.user_id`（须具 role_key=member） |

> **说明：** `MerchantCreateDTO.sysUserInitPassword` 为历史遗留字段，**后端绑定会员时不读取**；密码始终复制会员 `sys_user.password`（空则回退系统初始密码配置）。

**`BIND_SYS_USER` / `BIND_MEMBER` 落库 `biz_merchant_account`**

| 列 | 值 |
|----|-----|
| account_id | `sys_user.user_id`（绑会员时 = `member_id`） |
| merchant_id | 新商户 ID |
| shop_id | **NULL**（商户管理员） |

登录名、管理员姓名、密码 **不在本表维护**；商家端登录走 `sys_user`（绑会员时沿用会员账号；绑管理员时沿用平台用户）。

**`BIND_MEMBER` 额外步骤**

1. `grantRoleIfAbsent(userId, merchant)` → `sys_user_role` 追加 **role_key=merchant**  
2. 校验会员 `user_name`（会员名称）、`nick_name`（会员昵称）非空  
3. `insert biz_merchant_account`；同一 `account_id` 不可重复绑定（PK）

实现类：`com.ruoyi.web.modules.merchant.service.impl.MerchantAccountBindServiceImpl`  
SQL：`mapper/merchant/MerchantBindMapper.xml`

**个人 — 新增 `POST /`（含绑定管理员）**

```json
{
  "merchantType": "1",
  "personName": "张三",
  "idCardNo": "110101199001011234",
  "birthDate": "1990-01-01",
  "idValidType": "1",
  "idValidStart": "2020-01-01",
  "idValidEnd": "2030-01-01",
  "residenceAddress": "北京市朝阳区xxx",
  "gender": "0",
  "idCardFront": "/profile/upload/2025/01/a.jpg",
  "idCardBack": "/profile/upload/2025/01/b.jpg",
  "bindType": "SYS_USER",
  "bindUserId": 10
}
```

**个人 — 新增 `POST /`（含绑定会员）**

```json
{
  "merchantType": "1",
  "personName": "李四",
  "idCardNo": "110101199002021234",
  "birthDate": "1990-02-02",
  "idValidType": "1",
  "idValidStart": "2020-01-01",
  "idValidEnd": "2030-01-01",
  "residenceAddress": "北京市海淀区xxx",
  "gender": "0",
  "idCardFront": "/profile/upload/f.jpg",
  "idCardBack": "/profile/upload/b.jpg",
  "bindType": "MEMBER",
  "bindMemberId": 5
}
```

> 前端表单仍保留「初始密码」输入框时，提交可带 `sysUserInitPassword`，**后端忽略**。

### 9.6 `shop_count` 与批量删除

```text
店铺 Service 创建/逻辑删除店铺（同一 @Transactional）
    → insert/update biz_shop
    → IMerchantShopFacade.increment/decrementShopCount(merchantId)
```

- 禁止在商户编辑接口修改 `shop_count`。  
- 可选运维：`SELECT merchant_id, shop_count FROM biz_merchant` 与 `COUNT(*)` 对账任务（低频）。  
- `DELETE /{merchantIds}`：循环预检，**任一失败立即返回**，不执行部分删除。

### 9.7 展示脱敏（建议）

| 字段 | 列表/详情展示 |
|------|----------------|
| idCardNo, legalIdCardNo | 前3后4掩码 |
| bankAccount | 后4位 |
| contactPhone | 中间4位掩码 |

存储仍明文；可使用 RuoYi `@Sensitive` 或 VO 转换。

### 9.9 省市区接口与落库（`biz_region`）

**数据源：** 表 `biz_region`（`sql/biz_region.sql`）；Service `RegionServiceImpl`（内存缓存树）。

| 端 | 方法 | 路径 | 权限 |
|----|------|------|------|
| 平台 | GET | `/agri/region/tree` | 登录即可 |
| C 端 | GET | `/api/region/tree` | `@Anonymous` |

**响应节点（`RegionTreeVO`）：**

| 字段 | 说明 |
|------|------|
| id, pid | 表主键与父 id（省级 pid=0） |
| name | 区域名称 |
| type | 1省 2市 3区县 |
| code | 行政区划编码（如 110101） |
| children | 下级节点数组 |

**商户表单落库（选定区县 leaf 节点）：**

| 场景 | code 字段 | name 字段 | 详细地址 |
|------|-----------|-----------|----------|
| 企业注册地址 | `reg_region_code` = `String.valueOf(区县.code)` | `reg_region_name` = 省/市/区县 name 用 `/` 连接 | `company_detail_address` |
| 经营地址（个人/企业） | `biz_region_code` | `biz_region_name` | `biz_detail_address` |

**示例：**

```json
{
  "regRegionCode": "110101",
  "regRegionName": "北京市/市辖区/东城区",
  "companyDetailAddress": "xx路1号",
  "bizRegionCode": "110101",
  "bizRegionName": "北京市/市辖区/东城区",
  "bizDetailAddress": "yy大厦"
}
```

**校验：** 与 §9.2 一致，`biz_region_code` 与 `biz_region_name` 须同时非空方计入 `biz_complete`；C 端入驻 `EntryApplyFormValidator.requireRegion` 同理。

---

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