# 商家端店铺上下文 — 接口说明

> **代码位置：** `com.ruoyi.web.modules.store.controller.SellerShopContextController`  
> **用途：** 商家端登录后获取/切换 **当前店铺**，前端将 `shopId` 写入后续请求的 **`X-Shop-Id`** 请求头。  
> **关联模块：** 角色管理、员工管理、店铺商品分类等 `/agri/seller/**` 接口均依赖此上下文。  
> **前端封装：** `ruoyi-ui/src/api/agri/seller/context.js`、`ruoyi-ui/src/utils/sellerShop.js`

---

## 1. 通用说明

### 1.1 响应格式（RuoYi AjaxResult）

| 字段 | 类型 | 说明 |
|------|------|------|
| code | number | `200` 成功；`500` 业务失败 |
| msg | string | 提示信息 |
| data | object | 业务数据（`SellerContextVO`） |

### 1.2 认证

- 两个接口均需 **已登录** 商家端 Token（`Authorization: Bearer …`）。
- **不需要** 携带 `X-Shop-Id`（拦截器对 `/agri/seller/context` 路径放行）。

### 1.3 请求头 `X-Shop-Id`

| 项 | 说明 |
|----|------|
| 名称 | `X-Shop-Id` |
| 值 | 当前操作的店铺 ID（Long 字符串） |
| 设置时机 | 调用 `GET /agri/seller/context` 成功后，或 `PUT /agri/seller/context/shop` 切换成功后 |
| 使用范围 | 其他 `/agri/seller/**` 业务接口（如 `/agri/seller/role`、`/agri/seller/employee`） |

---

## 2. 获取当前店铺上下文

### 2.1 基本信息

| 项 | 值 |
|----|-----|
| 方法 | **GET** |
| 路径 | `/agri/seller/context` |
| 权限 | 无单独 `@PreAuthorize`；须登录且账号已绑定商户 |

### 2.2 请求参数

无 Query / Body。

### 2.3 业务逻辑

```text
requireSellerAccount(userId)     → biz_merchant_account 须存在
resolveDefaultShopId(account)
    ├── 员工（shop_id 非空）→ 绑定店铺 shop_id
    └── 经营账号（shop_id 为空）→ 该商户 create_time 最早的有效店铺
buildContext(account, shopId)    → 组装 SellerContextVO
```

### 2.4 成功响应 `data`（SellerContextVO）

| 字段 | 类型 | 说明 |
|------|------|------|
| merchantId | Long | 当前账号所属商户 ID |
| shopId | Long | **默认当前店铺 ID**（前端写入 X-Shop-Id） |
| shopName | String | 当前店铺名称 |
| accountType | String | `ADMIN` = 商户经营账号；`EMPLOYEE` = 店铺员工 |
| switchable | Boolean | 是否允许切换店铺；**仅经营账号为 true** |
| shops | Array | 可切换店铺列表；**仅经营账号有数据**，员工为空数组 `[]` |

**shops[] 元素（MerchantShopBriefVO）：**

| 字段 | 类型 | 说明 |
|------|------|------|
| shopId | Long | 店铺 ID |
| shopName | String | 店铺名称 |
| shopStatus | String | `0` 开业 / `1` 停业 |

### 2.5 响应示例

**经营账号（多店）：**

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "merchantId": 1,
    "shopId": 201,
    "shopName": "一号店",
    "accountType": "ADMIN",
    "switchable": true,
    "shops": [
      { "shopId": 201, "shopName": "一号店", "shopStatus": "0" },
      { "shopId": 202, "shopName": "二号店", "shopStatus": "1" }
    ]
  }
}
```

**店铺员工（单店绑定）：**

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "merchantId": 1,
    "shopId": 101,
    "shopName": "一号店",
    "accountType": "EMPLOYEE",
    "switchable": false,
    "shops": []
  }
}
```

### 2.6 失败场景

| msg | 触发条件 |
|-----|----------|
| 当前账号未绑定商户 | `biz_merchant_account` 无有效绑定（`MSG_NOT_SELLER_USER`） |

> 若商户无任何有效店铺，`shopId` 可能为 `null`，`shopName` 为空字符串。

---

## 3. 切换当前店铺

### 3.1 基本信息

| 项 | 值 |
|----|-----|
| 方法 | **PUT** |
| 路径 | `/agri/seller/context/shop` |
| 权限 | 须登录；**仅商户经营账号** 可调用 |

### 3.2 请求体（SellerShopSwitchDTO）

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| shopId | Long | 是 | 目标店铺 ID |

**示例：**

```json
{
  "shopId": 202
}
```

### 3.3 业务逻辑

```text
requireSellerAccount(userId)
assert isMerchantAdmin(account)           → 员工禁止切换
assert shopId 非空且 > 0
assertShopAccessible(account, shopId)     → 店铺须属于当前商户
buildContext(account, shopId)             → 返回新上下文（结构同 GET）
```

**店铺可访问校验（assertShopAccessible）：**

- 员工：`shopId` 必须等于绑定 `account.shop_id`；
- 经营账号：`shopId` 须在该 `merchant_id` 下存在且未删除。

### 3.4 成功响应

与 **§2.4** 相同，`data` 为切换后的 `SellerContextVO`（`shopId` / `shopName` 已更新）。

**示例：**

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "merchantId": 1,
    "shopId": 202,
    "shopName": "二号店",
    "accountType": "ADMIN",
    "switchable": true,
    "shops": [
      { "shopId": 201, "shopName": "一号店", "shopStatus": "0" },
      { "shopId": 202, "shopName": "二号店", "shopStatus": "1" }
    ]
  }
}
```

### 3.5 失败场景

| msg | 触发条件 |
|-----|----------|
| 员工账号不可切换店铺 | 非经营账号调用（`MSG_SHOP_SWITCH_FORBIDDEN`） |
| 请先选择当前店铺 | `shopId` 为空或未传（`MSG_SHOP_CONTEXT_REQUIRED`） |
| 当前店铺无效或不属于该商户 | 店铺不存在或不属于当前商户；员工切换至非绑定店（`MSG_SHOP_CONTEXT_INVALID`） |
| 当前账号未绑定商户 | 同 GET |

---

## 4. 账号类型与默认店铺规则

| accountType | 绑定特征（biz_merchant_account） | 默认 shopId | switchable | shops |
|-------------|----------------------------------|-------------|:----------:|-------|
| **ADMIN** | `shop_id IS NULL`（经营账号） | 商户下 **最早创建** 的有效店铺 | true | 商户全部有效店铺 |
| **EMPLOYEE** | `shop_id IS NOT NULL`（员工） | 绑定 `shop_id` | false | `[]` |

常量定义见 `ShopConstants.ACCOUNT_TYPE_ADMIN` / `ACCOUNT_TYPE_EMPLOYEE`。

---

## 5. 前端对接建议

### 5.1 推荐流程

```text
商家端登录成功
    → GET /agri/seller/context
    → 缓存 data.shopId、data.shopName（session）
    → 后续 /agri/seller/** 请求头附加 X-Shop-Id: {shopId}
经营账号切换店铺
    → PUT /agri/seller/context/shop { shopId }
    → 更新缓存 → Navbar 刷新页面（location.reload）
```

### 5.2 已实现前端

| 文件 | 说明 |
|------|------|
| `api/agri/seller/context.js` | `getSellerContext`、`switchSellerShop` |
| `utils/sellerShop.js` | `setSellerShopContext`、`sellerShopHeaders()` |
| `layout/components/Navbar.vue` | **唯一** 展示「当前店铺」及下拉切换（`merchant` 角色） |
| `views/agri/seller/**/index.vue` | 仅 `loadShopContext` 写缓存；**不** 展示店铺 UI |

### 5.3 注意事项

- `GET /agri/seller/context` **不要** 带 `X-Shop-Id`（也不需要）。
- Navbar 切换店铺后整页刷新，各业务列表自动按新店 `X-Shop-Id` 重新请求。
- **业务页面禁止** 重复实现店铺选择器（见 `doc/前端设计/前端设计.md` §5）。

---

## 6. 修订记录

| 版本 | 日期 | 说明 |
|------|------|------|
| v1.0 | 2026-06 | 首版：依据 SellerShopContextController / Service 源码整理 |

---

*文档版本：v1.0 · 关联《员工管理功能需求.md》§13、《角色管理功能需求.md》§1.2*
