# 我的服务 — 技术方案（C 端）

> **依据：** 《我的服务功能需求.md》v1.1  
> **关联：** 平台《会员管理技术方案.md》v1.3、《商户入驻审核技术方案.md》v1.1、《商城入驻协议技术方案.md》v1.0、《关联需求分析.md》v1.6；C 端《会员注册登录技术方案.md》v1.3（登录前置）  
> **范围：** C 端 **个人资料、修改密码、收货地址、商家入驻申请** 的后端落位、表结构、接口契约；**不含** 前端页面与平台审核实现细节（平台审核见入驻审核专册）。  
> **原则：** 账号复用 **`sys_user`**（`member_id = user_id`）；鉴权 **`TokenService` + `MemberAuthInterceptor`**；入驻 **`form_json` 快照** + 平台审核链。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 响应 | MyBatis；`AjaxResult`（`code`、`msg`、`data`） |
| 密码 | BCrypt（`SecurityUtils.encryptPassword` / `matchesPassword`） |
| C 端鉴权 | `MemberWebConfig` → `MemberAuthInterceptor` → `TokenService.getLoginUser` → `MemberContext.setMemberId` |
| 文件上传 | RuoYi 通用上传（头像、证件照等）；URL 写入表单字段 |
| 入驻校验 | `EntryApplyFormValidator` + `IMerchantEntryAgreementFacade` |

### 1.1 模块落位

```text
baqing-shop/src/main/java/com/ruoyi/web/modules/
├── account/                              # 资料 · 地址
│   ├── controller/MemberAppController.java
│   ├── service/impl/MemberAppServiceImpl.java
│   ├── service/impl/MemberAddressServiceImpl.java
│   ├── domain/BizMember.java、BizMemberAddress.java
│   ├── vo/MemberAddressVO.java
│   ├── config/MemberWebConfig.java
│   └── support/MemberAuthInterceptor.java、MemberContext.java
├── entry/                                # 商家入驻
│   ├── controller/MerchantEntryApplyAppController.java
│   ├── service/impl/MerchantEntryApplyServiceImpl.java
│   ├── dto/EntryApplySubmitDTO.java
│   ├── vo/EntryApplyMyVO.java
│   └── support/EntryApplyFormValidator.java、EntryApplyFormMapper.java
└── content/                              # 入驻协议（只读）
    └── controller/MerchantEntryAgreementAppController.java

sql/
├── biz_member.sql                        # biz_member + biz_member_address
├── biz_merchant_entry_apply.sql
├── biz_merchant_entry_agreement.sql
└── biz_merchant_entry_form_json说明.sql  # form_json 字段对照
```

### 1.2 业务链

```text
【资料 / 密码 / 地址】
MemberAuthInterceptor
    → MemberAppController / MemberAddressService
        → sys_user（nick_name、avatar、email、sex、password…）
        → biz_member（member_id、扩展统计）
        → biz_member_address（1:N）

【商家入驻】
GET /api/merchant/entry/agreement（匿名）
    → 填写 subject/biz/shop
    → POST /api/merchant/entry/apply
        → biz_merchant_entry_apply（form_json + 冗余检索列）
            → 平台审核 / 公示 / 建档（entry 模块 · 平台 Controller）
                → biz_merchant + biz_shop + biz_merchant_account
                → biz_member_message（审核结果通知）
```

| 关联模块 | 协作 |
|----------|------|
| **会员注册登录** | 登录 Token；本模块 **全部须鉴权**（协议接口除外） |
| **平台会员管理** | 共用表；平台 **只读** 资料/地址 |
| **商户入驻审核** | C 端 **写申请**；平台 **approve/reject/completePublicity** |
| **商城入驻协议** | `assertAccepted` 提交前校验 |
| **订单**（待建设） | 结算页 **读** `biz_member_address`；本模块 **不写订单** |

### 1.3 鉴权范围

| 路径 | 鉴权 |
|------|------|
| `/api/member/register`、`/login`、`/serviceAgreement/**` | 匿名（**会员注册登录专册**） |
| `/api/member/profile`、`/address/**` | **会员 Token** |
| `/api/member/password`（建议） | **会员 Token** |
| `/api/merchant/entry/agreement`、`/status` | **`@Anonymous`** |
| `/api/merchant/entry/apply`、`/my` | **会员 Token**（`MemberWebConfig` 拦截） |

Header：`Authorization: Bearer {token}`（与平台 `TokenService` 一致）。

---

## 2. 数据库设计

> **不新建会员主表**；权威 DDL 见 `sql/` 目录。下列为 **本模块读写** 表摘要。

### 2.1 账号与资料（复用）

| 表 | 本模块用途 | 关键字段 |
|----|------------|----------|
| **`sys_user`** | 资料、密码 | `user_id`（= `member_id`）、`nick_name`、`email`、`sex`、`avatar`、`password` |
| **`biz_member`** | 会员扩展 | `member_id`（= `user_id`）、`register_time`、消费统计 |

**字段映射（C 端展示）：**

| 产品字段 | 存储 | 可编辑 |
|----------|------|:------:|
| 用户 ID | `member_id` | 否 |
| 会员名称 | `user_name` | 否（功能需求资料页未列；改名称 **非本期**） |
| 手机号 | `phonenumber` | 否 |
| 昵称 | `nick_name` | 是 |
| 头像 | `avatar` | 是 |
| 邮箱 | `email` | 是 |
| 性别 | `sex`（0男 1女 2未知） | 是 |
| 密码 | `password` | 仅 **PUT /password** |

> **更新口径：** `BizMemberMapper.updateAccount` 按 `member_id` 更新 **`sys_user`**（`WHERE user_id = #{memberId}`）；**不写入** `biz_member`。

### 2.2 收货地址 `biz_member_address`

| 字段 | 类型 | 说明 |
|------|------|------|
| address_id | bigint PK | 自增 |
| member_id | bigint | FK → `biz_member.member_id` |
| consignee_name | varchar(64) | 收货人 |
| mobile | varchar(20) | 联系电话 |
| region_code | varchar(64) | 省市区编码（区县级 code） |
| region_name | varchar(128) | 省市区名称（省/市/区 / 拼接） |
| detail_address | varchar(256) | 详细地址 |
| is_default | char(1) | **0** 否 **1** 是；同会员 **至多一条** 为 1 |
| del_flag | char(1) | **0** 存在 **2** 逻辑删除 |
| create_time / update_time | datetime | 排序用 |

**索引：** `idx_member (member_id, del_flag)`

**列表排序（已实现）：** `ORDER BY is_default DESC, update_time DESC`（默认置顶；其余按最近更新倒序）。

### 2.3 入驻申请 `biz_merchant_entry_apply`

| 字段 | 类型 | 说明 |
|------|------|------|
| apply_id | bigint PK | |
| apply_no | varchar(32) UK | 业务单号 |
| apply_status | char(1) | **0** 待审 **1** 已完成 **2** 未通过 **3** 公示中 |
| merchant_type | char(1) | **1** 个人 **2** 企业 |
| member_id | bigint | 申请人 |
| member_code | varchar(64) | 会员名称冗余 |
| subject_label | varchar(128) | 列表摘要（姓名/企业名） |
| contact_name / contact_phone | varchar | 联系人 |
| id_card_no / credit_code | varchar(32) | 唯一性校验 |
| merchant_name / shop_name | varchar(128) | 唯一性校验 |
| form_json | longtext | `{ subject, biz, shop, agreementAccepted }` 快照 |
| reject_reason | varchar(500) | 驳回原因 |
| merchant_id / shop_id | bigint | 公示完成回填 |
| apply_time | datetime | 提交时间 |
| publicity_start_time / publicity_end_time | datetime | 公示期 |
| del_flag | char(1) | 0/2 |

**索引：** `idx_member_status (member_id, apply_status)` — 阻塞重复提交（status in 0,3）。

### 2.4 协作表（只读 / 下游写入）

| 表 | 关系 |
|----|------|
| `biz_merchant_entry_agreement` | C 端读协议；`enable_flag=1` 才开放申请 |
| `biz_member_message` | 审核/公示/完成时 **entry** 模块写入 |
| `biz_merchant`、`biz_shop`、`biz_merchant_account` | **公示完成** 后由平台 Service 创建 |

### 2.5 ER（本模块视角）

```text
sys_user ──1:1── biz_member ──1:N── biz_member_address
                    │
                    └──1:N── biz_merchant_entry_apply ──►（审核后）biz_merchant / biz_shop
```

---

## 3. 接口设计

统一响应：`AjaxResult`；登录接口 `token` 在根级（见会员注册登录专册）。  
**基路径：** 资料/地址 `/api/member`；入驻 `/api/merchant/entry`。

### 3.1 个人资料

#### `GET /api/member/profile`

| 项 | 说明 |
|----|------|
| 鉴权 | 会员 Token |
| 响应 `data` | 建议 `MemberProfileAppVO` |

| 字段 | 说明 |
|------|------|
| memberId | 用户 ID（只读） |
| mobile | 手机号（只读） |
| memberCode | 会员名称（只读，登录账号） |
| nickName | 昵称 |
| avatar | 头像 URL |
| email | 邮箱 |
| sex | 性别 |

> **已实现：** `MemberProfileAppVO`；`updateAccount` 写 **sys_user**（email/sex）。

#### `PUT /api/member/profile`

| Body | 可写字段 |
|------|----------|
| nickName、avatar、email、sex | 更新 **`sys_user`**（`user_id = member_id`） |
| **不可写** | memberId、mobile、password |

| 校验 | 说明 |
|------|------|
| nickName | 非空 |
| email | 格式（可空） |

**Service：** `MemberAppServiceImpl.updateProfile` → `BizMemberMapper.updateAccount`（`sys_user`：nick_name、avatar、email、sex）。

---

### 3.2 修改密码

#### `PUT /api/member/password`（建议独立接口）

| Body | 必填 |
|------|:----:|
| oldPassword | 是 |
| newPassword | 是 |
| confirmPassword | 是 |

| 流程 | |
|------|--|
| 1 | `matchesPassword(oldPassword, sys_user.password)` |
| 2 | `newPassword === confirmPassword` |
| 3 | `encryptPassword(newPassword)` 更新 `sys_user.password` |

| 失败 msg（示例） | |
|----------------|--|
| 旧密码错误 | |
| 两次输入的密码不一致 | |

> **现状：** 密码可通过 `PUT /profile` 的 `password` 字段更新 **且无旧密码校验**；**须迁移** 至本接口以满足功能需求 MS-W1。

---

### 3.3 收货地址

| 方法 | 路径 | 说明 | 状态 |
|------|------|------|------|
| GET | `/address/list` | 当前会员地址列表 | **已实现** |
| POST | `/address` | 新增 | **已实现** |
| PUT | `/address` | 修改（Body 含 addressId） | **已实现** |
| DELETE | `/address/{addressId}` | 逻辑删除 | **已实现** |
| PUT | `/address/{addressId}/default` | 设默认 | **已实现** |

#### `GET /api/member/address/list`

响应 `data`：`MemberAddressVO[]`

| 字段 | 说明 |
|------|------|
| addressId | |
| consigneeName、mobile | |
| regionCode、regionName、detailAddress | |
| isDefault | 0/1 |
| fullAddress | regionName（去 `/`）+ 详细地址 |

#### `POST /api/member/address` / `PUT /api/member/address`

| Body 字段 | 必填 |
|-----------|:----:|
| addressId | PUT 必填 |
| consigneeName、mobile | 是 |
| regionCode、regionName | 是；须 **成对** 非空 |
| detailAddress | 是 |
| isDefault | 否；为 1 时 **同事务** `clearDefault` |

**事务规则：**

```text
isDefault = 1  →  UPDATE 同 member 其他行 is_default = 0  →  写当前行
```

---

### 3.4 商家入驻协议（只读）

| 方法 | 路径 | 鉴权 | 说明 |
|------|------|------|------|
| GET | `/api/merchant/entry/agreement` | 匿名 | 协议标题、版本、正文、`entryOpen` |
| GET | `/api/merchant/entry/status` | 匿名 | 仅 `entryOpen` 开关 |

---

### 3.5 提交入驻申请

#### `POST /api/merchant/entry/apply`

| 项 | 说明 |
|----|------|
| 鉴权 | 会员 Token |
| Body | `EntryApplySubmitDTO` |

```json
{
  "subject": { },
  "biz": { },
  "shop": { "shopName", "shopAvatar", "shopPhone", "shopDesc" },
  "agreementAccepted": true
}
```

- `subject` / `biz` 结构同 `BizMerchant`；字段清单见 `sql/biz_merchant_entry_form_json说明.sql`。
- `merchant_type` 由 `EntryApplyFormValidator.resolveMerchantType` 从表单推断（1 个人 / 2 企业）。

| 校验 | 实现 |
|------|------|
| 协议勾选 | `IMerchantEntryAgreementFacade.assertAccepted` |
| 待审/公示中 | `countBlockingByMemberId`（status in 0,3） |
| 完整字段 | `EntryApplyFormValidator.validateMobileSubmit` |
| 唯一性 | 证件号 / 信用代码 / merchant_name / shop_name |
| 会员昵称 | 绑定经营账号路径须 `nick_name` 非空 |

**成功 `data`：**

```json
{
  "applyId": 1,
  "applyNo": "EA202605260001",
  "applyStatus": "0"
}
```

**状态：** **已实现**。

---

### 3.6 我的入驻申请

#### `GET /api/member/entry/my` → 实际路径 **`GET /api/merchant/entry/my`**

| 项 | 说明 |
|----|------|
| 鉴权 | 会员 Token |
| 响应 | `EntryApplyMyVO[]`（按 apply_time 降序） |

| 字段 | 说明 |
|------|------|
| applyId、applyNo | |
| applyStatus | 0/1/2/3 |
| applyTime、auditTime | |
| rejectReason | status=2 时有值 |
| publicityStartTime、publicityEndTime | status=3/1 时 **建议扩展 VO** |
| merchantId、shopId | status=1 回填 |

**状态：** **已实现**（公示时间字段 **可补** 至 VO）。

---

### 3.7 错误码（业务 msg 示例）

| 场景 | msg |
|------|-----|
| 未登录 | 请先登录（401） |
| 会员不存在 | 会员不存在 |
| 地址不存在 | 地址不存在 |
| 待审申请已存在 | 常量 `MSG_PENDING_EXISTS` |
| 协议未勾选 | Facade 统一文案 |
| 旧密码错误 | 旧密码错误 |
| 证件/店名重复 | 对应唯一性提示 |

---

## 4. Service 要点

### 4.1 资料

```text
getProfile(memberId)
    → BizMemberMapper.selectById（JOIN sys_user）
    → 脱敏 password；组装 VO（含 email/sex）

updateProfile(memberId, dto)
    → 校验 nickName 等
    → updateAccount（sys_user：nick_name, avatar, email, sex）
```

### 4.2 修改密码

```text
changePassword(memberId, old, new, confirm)
    → 校验 confirm
    → matchesPassword(old)
    → updateAccount(password=encrypt(new))
```

### 4.3 地址

```text
addAddress / updateAddress / setDefaultAddress
    @Transactional
    → validateAddress
    → 若 isDefault=1 → clearDefault(memberId)
    → insert / update / setDefault
```

### 4.4 入驻提交

```text
submitApply(memberId, dto)
    @Transactional
    → agreementFacade.assertAccepted
    → countBlockingByMemberId == 0
    → requireMember（nick_name 非空）
    → EntryApplyFormValidator.validateMobileSubmit
    → assertUniqueOnSubmit
    → insert biz_merchant_entry_apply（form_json + 冗余列）
```

---

## 5. 与平台方案协作

| 平台文档 | C 端本模块关系 |
|----------|----------------|
| 《会员管理技术方案》§4 | 共用 `/api/member/profile`、`/address/**`；平台 `/agri/member` **只读** |
| 《商户入驻审核技术方案》§4 | C 端 **3.5～3.6** 对应其 **4.1～4.2**；审核 **4.6～4.8** 仅平台 |
| 《商城入驻协议技术方案》 | **3.4** 读配置；`enable_flag=0` 时 **apply** 阻断 |
| 《关联需求分析》§2.1 | 用户链账号 **≠** 经营账号；completePublicity 后 **bindAccountOnMerchantCreate** |

**入驻状态 C 端只读流转：**

```text
0 待审 ──平台 approve──► 3 公示中 ──期满 complete──► 1 已完成
  └──平台 reject──► 2 未通过（C 端展示 rejectReason，可再 submit 新单）
```

---

## 6. 实现状态

| 能力 | 状态 | 备注 |
|------|------|------|
| 鉴权 `MemberAuthInterceptor` + TokenService | **已实现** | |
| GET/PUT `/profile`（含 email/sex） | **已实现** | `MemberProfileAppVO` |
| PUT `/password`（旧密码校验） | **已实现** | |
| 地址 CRUD + 默认互斥 | **已实现** | |
| 入驻协议 GET | **已实现** | |
| POST `/apply`、GET `/my` | **已实现** | 含公示时间 |
| C 端前端 | **已实现**（见《我的服务前端技术方案.md》v1.2，资料无 birthday） |

---

## 7. 非本期

| 项 | 说明 |
|----|------|
| 手机号换绑 | — |
| 资料页改会员名称 | 功能需求未列 |
| 待审申请撤销/改资料 | 驳回后重提 |
| `GET /api/member/message/list` | 消息列表另册 |
| 入驻表单草稿保存 | — |

---

## 8. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.3** | `biz_member_address`：`province/city/district` → **`region_code`/`region_name`** |
| **v1.2** | 移除 **birthday**（个人资料不含出生日期） |
| **v1.1** | email/sex 存 **sys_user**；`PUT /password`；`MemberProfileAppVO` |
| **v1.0** | 首版 |

---

*文档版本：v1.3 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《我的服务功能需求.md》v1.1*
