# 店铺管理 — 技术方案

> **依据：** 《店铺管理功能需求.md》v1.3.4  
> **关联：** 《商户管理》《商品分类》《商品管理》功能需求（`doc/农资商城web/`）、《商户管理技术方案.md》v1.4、《店铺设置技术方案.md》v1.1.1    
> **范围：** 本文以 **店铺模块** 的数据库、接口为主；商品/分类/订单/商家端登录给出 **表关系、Facade、联动**；不展开商品/C 端完整设计。  
> **原则：** **不考虑会员**；**店铺经营账号** 由本模块维护（平台账号体系，BCrypt 密码）。  
> **v1.2.3：** 商家端 **`GET/PUT /agri/seller/shop`** 维护店资料，与平台列表 **同源** `biz_shop`。  
> **v1.2.2：** `biz_shop` **移除** `default_audit_pass`、`max_sub_admin`；策略见 **店铺设置** 全局表。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 权限 / 响应 | MyBatis；`@PreAuthorize`；`AjaxResult` / `TableDataInfo` |
| 文件 | 店铺头像走 `CommonController` 上传，存 URL |
| 密码 | `SecurityUtils.encryptPassword`（BCrypt，与 `sys_user` 一致） |
| 日志 | 增删改、账号/设置变更 `@Log` |

### 1.1 模块落位

```text
baqing-shop
├── com.ruoyi.web.modules.store.controller.ShopController          # 平台 /agri/shop
├── com.ruoyi.web.modules.store.controller.SellerShopController    # 商家 /agri/seller/shop
└── service / facade / mapper ...
```

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

```text
biz_merchant
    └── biz_merchant_account（1 商户 1 套经营账号）
            └── biz_shop（1 商户多店，merchant_id 创建后不可改；本期不设店数上限）
                    ├── biz_goods_category（shop_id）
                    ├── biz_goods（shop_id；submit 读 **全局** default_audit_pass）
                    └── biz_order（shop_id；删店校验）
```

| 模块 | 核心表 | 与店铺关系 |
|------|--------|------------|
| 商户管理 | `biz_merchant` | 上游；`shop_count` 回写 |
| **店铺管理** | `biz_shop`；`biz_merchant_account`（表归商户维度，**本模块维护**） | **本文** |
| 商品管理 | `biz_goods` | 删店前置：无出售中/待审核 |
| 商品分类 | `biz_goods_category` | 归属 `shop_id` |
| 订单 | `biz_order` | 删店校验 `IOrderFacade.hasUnfinishedOrdersByShop`（见《订单管理技术方案》v1.0） |
| 商家端 | `biz_shop` 资料 | `SellerShopController`；`X-Shop-Id` 上下文；改资料后平台 list **同源** |

### 1.3 跨模块 Facade

| 接口 | 提供方 | 调用方 | 方法 |
|------|--------|--------|------|
| `IMerchantFacade` | 商户 | 店铺 | `checkOpenShop(merchantId)`、`listSelectableMerchants()` |
| `IMerchantShopFacade` | **店铺** | 商户 | `incrementShopCount` / `decrementShopCount`（**与店铺同事务**） |
| `IGoodsFacade` | 商品 | 店铺 | `hasBlockingGoodsForShopDelete(shopId)` — 存在 **待审核(1)** 或 **出售中(2)** |
| `IOrderFacade` | 订单 | 店铺、商户 | `hasUnfinishedOrdersByShop(shopId)` |
| `IShopFacade` | 店铺 | 商品/C端 | `getShopStatus(shopId)`、`isShopOpenForOrder(shopId)` — 开业且未逻辑删除 |

> 订单未实现前，`IOrderFacade` 可桩返回 `false`（不阻塞删除）。

### 1.4 状态联动

| 事件 | 店铺 | 商品 | 商户 | C 端 |
|------|------|------|------|------|
| 新建店 | 默认 **开业(0)** | — | shop_count+1 | — |
| 开业→停业 | 二次确认后保存 | **不改** 商品状态 | 不改认证 | **禁止下单** |
| 停业→开业 | 直接保存 | — | — | 可下单（还须商品等条件） |
| 商户冻结/注销 | 不可 **新开店**；已有店不自动停业 | 不改状态 | — | — |
| 逻辑删店 | 列表隐藏 | 历史保留 | shop_count-1 | 店不可用 |
| 改全局 `default_audit_pass` | —（见《店铺设置》） | **仅此后** 新 submit 生效 | — | — |

---

## 2. 数据库设计

### 2.1 本模块表

| 表名 | 说明 |
|------|------|
| `biz_shop` | 店铺主数据（名称、头像、状态、描述、商家电话） |
| `biz_merchant_account` | 商户级经营账号（**同商户多店共用**；原命名 `biz_shop_account` 已统一为本表名） |

### 2.2 `biz_merchant_account`（商户维度，非每店一条）

| 字段 | 类型 | 说明 |
|------|------|------|
| account_id | bigint PK | |
| merchant_id | bigint | **一商户一条**（`del_flag=0` 时唯一） |
| login_name | varchar(20) | 登录名（通常手机号）；**`del_flag=0` 时全平台唯一**（与需求 v1.3.1 一致） |
| admin_name | varchar(64) | 管理员姓名 |
| password | varchar(100) | BCrypt |
| del_flag | char(1) | 0 存在 2 删除（随商户/运营策略，一般随商户生命周期） |
| create_by/time, update_by/time | | |

> 第 2、3 家店 **不新建** 账号行；仅校验 `login_name` 与已有账号一致。  
> **末店逻辑删除后：** 账号行 **保留** `del_flag=0`，同商户再开店可沿用/重置（需求 §11.5）。

### 2.3 `biz_shop`

| 字段 | 类型 | 说明 |
|------|------|------|
| shop_id | bigint PK | |
| merchant_id | bigint | FK；**创建后不可 UPDATE** |
| shop_name | varchar(128) | 未删除店铺内 **唯一** |
| shop_avatar | varchar(512) | 头像 URL |
| shop_status | char(1) | **0 开业 1 停业**；新建默认 0 |
| shop_desc | varchar(1000) | 选填 |
| shop_phone | varchar(20) | 商家电话，必填 |
| del_flag | char(1) | 逻辑删除 |
| 审计字段 | | |

列表「经营账号」：`JOIN biz_merchant_account ON merchant_id`，展示 `admin_name` + 脱敏 `login_name`。

### 2.4 索引

| 索引 | 字段 |
|------|------|
| PK | shop_id |
| idx_merchant | merchant_id, del_flag |
| idx_status | shop_status, del_flag |
| idx_create | create_time |
| uk_shop_name | shop_name + del_flag（或应用层唯一） |
| uk_merchant_account | merchant_id + del_flag（account 表） |
| uk_login_name | login_name + del_flag（account 表） |

### 2.5 DDL

```sql
CREATE TABLE `biz_merchant_account` (
  `account_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账号ID',
  `merchant_id` bigint(20) NOT NULL COMMENT '商户ID',
  `login_name` varchar(20) NOT NULL COMMENT '登录名',
  `admin_name` varchar(64) NOT NULL COMMENT '管理员姓名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `del_flag` char(1) NOT NULL DEFAULT '0',
  `create_by` varchar(64) DEFAULT '',
  `create_time` datetime DEFAULT NULL,
  `update_by` varchar(64) DEFAULT '',
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`account_id`),
  KEY `idx_merchant_id` (`merchant_id`,`del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商户经营账号（商家端登录）';

CREATE TABLE `biz_shop` (
  `shop_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '店铺ID',
  `merchant_id` bigint(20) NOT NULL COMMENT '商户ID',
  `shop_name` varchar(128) NOT NULL COMMENT '店铺名称',
  `shop_avatar` varchar(512) NOT NULL COMMENT '店铺头像',
  `shop_status` char(1) NOT NULL DEFAULT '0' COMMENT '0开业 1停业',
  `shop_desc` varchar(1000) DEFAULT NULL COMMENT '店铺描述',
  `shop_phone` varchar(20) NOT NULL COMMENT '商家电话',
  `del_flag` char(1) NOT NULL DEFAULT '0',
  `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 (`shop_id`),
  KEY `idx_merchant_id` (`merchant_id`,`del_flag`),
  KEY `idx_shop_status` (`shop_status`,`del_flag`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店铺';
```

### 2.6 字典

| dict_type | 值 |
|-----------|-----|
| shop_status | 0 开业 / 1 停业 |

### 2.7 关联表（他模块）

```text
biz_goods.goods_status  0未上架 1待审核 2出售中 3审核失败 4已下架
biz_goods.shop_id       → biz_shop.shop_id
```

---

## 3. 接口设计

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

> **店铺设置** 接口已迁至 `/agri/shopSetting`，见《店铺设置技术方案.md》。

### 3.1 接口一览

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/list` | list | 分页列表 + 检索 |
| GET | `/{shopId}` | query | 详情 |
| POST | `/` | add | 添加店铺（含经营账号） |
| PUT | `/` | edit | 编辑店铺（**不含**账号、不含 merchantId） |
| GET | `/{shopId}/deleteCheck` | query | 删除预检 |
| DELETE | `/{shopIds}` | remove | 逻辑删除（批量整批失败） |
| GET | `/merchant/{merchantId}/account` | account | 店铺账号管理-查询 |
| PUT | `/merchant/{merchantId}/account` | account | 改登录名/姓名/密码 |

**复用商户接口（选商户）：** `GET /agri/merchant/selectList`（S1：正常 + 经营完整；**不含** 店数上限）。  
**开店校验（店铺侧）：** 创建前调用 `IMerchantFacade.checkOpenShop(merchantId)`，与 `GET /agri/merchant/{id}/openShopCheck` **同一套 reason**（§7.7）。

### 3.2 列表 `GET /list`

| Query | 说明 |
|-------|------|
| pageNum, pageSize | 分页 |
| keyword | 模糊：shop_name、商户名称、login_name、admin_name |
| shopStatus | 0/1，空=全部 |
| merchantName | 模糊商户名（商户详情跳转带入） |

| rows 字段 | 说明 |
|-----------|------|
| shopId, shopName, shopAvatar | |
| merchantName | 商户 merchant_name，空则 unitName |
| shopStatus | |
| accountAdminName, accountLoginName | 脱敏手机号 |
| 操作 | 编辑/账号管理/设置/删除（前端按权限） |

默认：`del_flag=0`，`create_time DESC`。

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

返回店铺字段 + `merchantId`/`merchantName`（只读）+ 经营账号摘要（只读）+ `canDelete`（同 deleteCheck）。

### 3.4 添加店铺 `POST /`

**Body 要点**

```json
{
  "shopName": "张三农资店",
  "shopAvatar": "/profile/upload/xx.jpg",
  "shopPhone": "010-88886666",
  "merchantId": 1,
  "loginName": "13800138000",
  "adminName": "张三",
  "password": "初始密码"
}
```

| 步骤 | 逻辑 |
|------|------|
| 1 | `IMerchantFacade.checkOpenShop(merchantId)` 不通过则阻断 |
| 2 | 店名唯一；头像/电话必填 |
| 3 | 账号：若商户 **无** 账号 → insert `biz_merchant_account`；**已有** → `loginName` 必须与现有一致，否则「同一商户下须使用相同经营账号」 |
| 4 | `loginName` 平台唯一、手机号格式；**不校验会员** |
| 5 | insert `biz_shop`：`shop_status=0` |
| 6 | 同事务 `incrementShopCount(merchantId)` |

**同商户第 2、3 家店：** 可先 `GET .../merchant/{id}/account` 带出 login/admin；`password` 可空表示不修改已有密码。

### 3.5 编辑店铺 `PUT /`

| 可改 | shopName, shopAvatar, shopStatus, shopDesc, shopPhone |
| 不可改 | merchantId；经营账号字段（走账号接口） |

**开业→停业（S12）：** 与商户认证类似，支持 `confirm`：

| confirm | 行为 |
|---------|------|
| false | `needConfirm=true`，`confirmMessage=停业后用户端将无法下单，是否继续？` |
| true | 持久化 `shop_status=1` |

**停业→开业：** 直接保存，无需 confirm。

**编辑 Body 样例：**

```json
{
  "shopId": 1,
  "shopName": "张三农资店",
  "shopAvatar": "/profile/upload/xx.jpg",
  "shopPhone": "010-88886666",
  "shopDesc": "主营农资",
  "shopStatus": "1",
  "confirm": false
}
```

`shopStatus` 由 `0→1` 时先 `confirm:false` 取 `needConfirm`；再 `confirm:true` 提交。`0→0` 或 `1→0` 不需 confirm。

### 3.6 店铺账号管理

**`GET /merchant/{merchantId}/account`**

```json
{
  "adminName": "张三",
  "loginName": "138****8000",
  "shops": [
    { "shopId": 1, "shopName": "店A", "shopStatus": "0" }
  ]
}
```

**`PUT /merchant/{merchantId}/account`**

| 字段 | 规则 |
|------|------|
| loginName | 新登录名平台唯一；改登录名须 `confirm=true`，文案含「同步至该商户下全部店铺」 |
| adminName | 直接更新 |
| password | 非空则重置（BCrypt） |

保存后：更新 `biz_merchant_account` 一行；列表展示同步（同 merchant_id 下所有店）；**不** 改商品/商户认证状态。

### 3.7 店铺设置（已迁出）

全平台策略 **`GET/PUT /agri/shopSetting`**、表 **`biz_shop_global_config`** 见 **《店铺设置技术方案.md》**；本模块 **无** `/{shopId}/settings` 接口。

### 3.10 商家端店铺资料（草稿第 7 条）

**路径：** `/agri/seller/shop`  
**上下文：** 请求头 `X-Shop-Id`（与分类/商品商家端一致）  
**权限：** `agri:seller:shop:query` / `agri:seller:shop:edit`

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/` | 当前店铺资料 |
| PUT | `/` | 修改名称、头像、描述、商家电话 |

**Body（PUT）：** `SellerShopProfileDTO`

| 字段 | 规则 |
|------|------|
| shopName | 必填；未删除店铺内唯一 |
| shopAvatar | 必填 |
| shopPhone | 必填；格式同平台 |
| shopDesc | 选填，≤1000 |

**不可改：** `shop_status`、`merchant_id`、经营账号。  
**同步：** 更新 `biz_shop` 后平台 `GET /agri/shop/list` **直接读新值**，无离线同步。

### 3.11 删除

**`GET /{shopId}/deleteCheck`**

```json
{
  "canDelete": false,
  "reasons": ["请先下架或处理完商品后再删除店铺", "存在未完成订单，无法删除"]
}
```

| # | 条件 |
|---|------|
| 1 | `del_flag=0` |
| 2 | `!IGoodsFacade.hasBlockingGoodsForShopDelete` |
| 3 | `!IOrderFacade.hasUnfinishedOrdersByShop` |

**`DELETE /{shopIds}`：** 任一条失败 **整批不删**；成功 `del_flag=2` + `decrementShopCount`（同事务）。

### 3.12 对外协作（商品/C 端）

| 能力 | 说明 |
|------|------|
| `IShopFacade.isShopOpenForOrder(shopId)` | `shop_status=0` 且 `del_flag=0` |
| 新商品 submit 初始状态 | 读 **`IShopGlobalConfigFacade.getDefaultAuditPass()`**：0→待审核(1)，1→出售中(2) |
| C 端下单 | 商品出售中 + 库存&gt;0 + `isShopOpenForOrder`；停业返回「店铺已停业」 |

---

## 4. 端到端关联流程

### 4.1 入驻 → 开店 → 发品

```text
商户：主体入驻 → 编辑补全经营（biz_complete=1）
    ↓
GET /agri/merchant/selectList → POST /agri/shop（账号+店）
    ↓
商家端：经营账号登录 → 选当前 shop_id → 分类/商品
    ↓
上架：按 **全局** default_audit_pass 进待审核或出售中
```

### 4.2 删店 → 删商户

```text
商品：无出售中/待审核 → DELETE /agri/shop/{id} → shop_count-1
    ↓
商户：shop_count=0 且无未完成订单 → DELETE /agri/merchant/{id}
```

### 4.3 商户详情跳转

商户详情 `shops` / 前端跳转：`/agri/shop/list?merchantName=xxx`。

---

## 5. 业务规则映射

| 编号 | 实现 |
|------|------|
| S1 | `checkOpenShop` + selectList |
| S2 | **不校验** 店铺数量上限；`shop_count` 仅 increment/decrement 统计 |
| S3 | insert `shop_status=0` |
| S4 | POST 必填账号；同商户 login 一致 |
| S5 | 账号仅 `.../account` 接口；改 login 同步展示 |
| S6 | 无会员校验 |
| S7 | UPDATE 排除 `merchant_id` |
| S8 | 店名唯一校验 |
| S9 | 无级联 UPDATE goods/merchant |
| S10 | deleteCheck + DELETE + Facade |
| S11 | **全局** `default_audit_pass` 默认 0（见《店铺设置》） |
| S12 | 开业→停业 `confirm` |
| S13 | `IShopFacade.isShopOpenForOrder` 供 C 端 |
| S14 | 商家端 `updateSellerShopProfile` → 平台 list 同源 |

---

## 6. 菜单权限

| 权限 | 标识 |
|------|------|
| 列表 | agri:shop:list |
| 查询 | agri:shop:query |
| 新增 | agri:shop:add |
| 修改 | agri:shop:edit |
| 删除 | agri:shop:remove |
| 账号管理 | agri:shop:account |

> **店铺设置** 权限 `agri:shop:setting` 见《店铺设置技术方案》。

| 商家端-查询资料 | agri:seller:shop:query |
| 商家端-改资料 | agri:seller:shop:edit |

---

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

### 7.1 业务错误 msg

| 场景 | msg |
|------|-----|
| 店名重复 | 店铺名称已存在 |
| 登录名重复 | 登录名已存在 |
| 同商户账号不一致 | 同一商户下店铺须使用相同经营账号 |
| 商户不可开店 | 请先完善该商户的经营与结算信息 |
| 删店-商品 | 请先下架或处理完商品后再删除店铺 |
| 删店-订单 | 存在未完成订单，无法删除 |
| 批量删除失败 | 部分店铺不满足删除条件，操作已取消 |

### 7.2 添加店铺 JSON（同商户第二家店）

```json
{
  "shopName": "张二店",
  "shopAvatar": "/profile/upload/y.jpg",
  "shopPhone": "010-88886667",
  "merchantId": 1,
  "loginName": "13800138000",
  "adminName": "张三",
  "password": null
}
```

`password=null`：不修改已有账号密码。

### 7.3 账号改登录名 confirm

```json
{ "loginName": "13900139000", "adminName": "张三", "password": null, "confirm": false }
```

→ `needConfirm=true`，`confirmMessage=将同步至该商户下全部店铺，是否继续？`

### 7.4 列表 login 脱敏

中间 4 位 `*`（如 `138****8000`）。

### 7.5 事务边界与 shop_count 对账

```text
@Transactional
createShop()  → insert shop [+ insert account] → incrementShopCount
deleteShop()  → 预检 → del_flag=2 → decrementShopCount（账号行不删，见 §2.2）
```

| 项 | 说明 |
|----|------|
| 并发 | 开店/删店与 `increment/decrement` **同一事务**；禁止绕过 Service 直接改 `shop_count` |
| 对账（可选） | 低频任务：`COUNT(biz_shop WHERE merchant_id=? AND del_flag=0)` 与 `biz_merchant.shop_count` 比对告警 |

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

| 字段 | 规则 |
|------|------|
| shopName | 非空，长度 ≤128；未删除店铺内唯一 |
| shopAvatar | 非空 URL；jpg/png/jpeg；≤5MB（与 RuoYi 上传一致） |
| shopPhone | 非空；手机或固话（项目统一正则） |
| loginName | 大陆手机号 11 位（项目统一）；`del_flag=0` 全平台唯一 |
| adminName | 非空，≤64 |
| password | 首家店必填，长度 ≥6（与平台密码策略一致）；**非首家** 可 null |
| shopDesc | 选填，≤1000 |

### 7.7 `checkOpenShop` / `openShopCheck`（与商户方案对齐）

店铺 `POST /` 与商户 `GET /agri/merchant/{id}/openShopCheck` 共用 `IMerchantFacade.checkOpenShop`：

| allowed | reason（data.reason 或 msg） | 触发条件 |
|:-------:|------------------------------|----------|
| false | 经营信息未完善 | `biz_complete=0` |
| false | 商户已冻结 | `cert_status=1` |
| false | 商户已注销 | `cert_status=2` |
| false | 商户已删除 | `del_flag=2` |
| true | null | 上述均不满足 |

### 7.8 经营账号生命周期（需求 §11.5）

| 事件 | `biz_merchant_account` |
|------|-------------------|
| 创建首家店 | insert，del_flag=0 |
| 删店但仍有店 | 不删账号 |
| 删末店 | **保留**账号行，便于再开店 |
| 改登录名/密码 | update 同一 merchant_id 行 |

---

## 8. 文档索引

| 文档 | 版本 |
|------|------|
| 店铺管理功能需求.md | v1.3.2 |
| 店铺管理测试用例.md | v1.0（表名已同步为 `biz_merchant_account`） |
| 商户管理功能需求/技术方案 | v1.3.1 / v1.3 |
| 商品管理功能需求/技术方案 | v1.3.3 / v1.2 |
| 商品分类功能需求/技术方案 | v1.3.1 / v1.1 |

---

## 9. 非本期实现

商家端完整登录与 `shop_id` 切换、子管理员 CRUD、店铺逻辑删除恢复、会员打通、导出。

---

## 12. v1.2.1 修订摘要

| 项 | 内容 |
|----|------|
| 表名 | `biz_shop_account` → **`biz_merchant_account`**（与 `merchant_id` 维度一致） |
| 实体类 | `BizShopAccount` → **`BizMerchantAccount`**；`IShopAccountService` → **`IMerchantAccountService`** |
| 能力归属 | 仍由 **店铺模块** 维护 CRUD 与开店事务；仅表名/实体名调整 |

---

*文档版本：v1.2.1 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《店铺管理功能需求.md》v1.3.2、《店铺管理测试用例.md》v1.0*
