# 会员管理 — 技术方案

> **依据：** 《会员管理功能需求.md》v1.0  
> **关联：** 《订单管理功能需求.md》v1.0.1、《商品管理功能需求.md》v1.3.3、《店铺管理功能需求.md》v1.3.2、《关联需求分析.md》v1.2  
> **范围：** 本文以 **会员主数据、收货地址、平台端查询/启用** 及 **订单只读聚合** 为主；`biz_order` 由订单模块维护，本文给出 **ER、Facade 契约与会员侧接口**；C 端注册/登录接口单列 §4（与平台共用表）。  
> **原则：** 会员与 `biz_merchant_account` **分表**；手机号 `del_flag=0` 唯一；消费统计 **O11** 与订单一致。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 权限 / 响应 | MyBatis；`@PreAuthorize`；`AjaxResult` / `TableDataInfo` |
| 密码 | C 端 `SecurityUtils.encryptPassword`（BCrypt）；存于 **`sys_user.password`** |
| 账号 | **与 `sys_user` 复用**：`member_id = user_id`；会员角色 **role_key=member** |
| 脱敏 | 列表手机号中间 4 位 `*`（如 `177****9136`） |
| 日志 | 启用状态变更 `@Log` |

### 1.1 模块落位

```text
baqing-shop/src/main/java/com/ruoyi/web/modules/account/
├── controller/MemberController.java           # 平台 /agri/member
├── controller/MemberAppController.java        # C 端 /api/member
├── service/impl/MemberServiceImpl.java
├── service/impl/MemberAppServiceImpl.java
├── service/impl/MemberAddressServiceImpl.java
├── support/MemberRegistrationSupport.java     # C 端注册 + 平台添加（共用）
├── support/MemberSysUserSupport.java          # 解析 role_key=member
├── domain/BizMember.java、BizMemberAddress.java
└── mapper/BizMemberMapper.xml、BizMemberAddressMapper.xml

resources/mapper/account/BizMemberMapper.xml   # JOIN sys_user；列表过滤 member 角色
sql/biz_member.sql                             # biz_member 扩展表 + member 角色初始化
```

### 1.2 业务链

```text
sys_user（账号：user_name/phonenumber/nick_name/password/avatar/status）
    └── biz_member（member_id = user_id；等级/消费统计/注册时间）
            ├── biz_member_address（1:N）
            │
            ├── IMemberFacade ──► 订单模块
            └── biz_order.member_id
```

| 字段映射 | sys_user | biz_member（业务层别名） |
|----------|----------|-------------------------|
| 主键 | user_id | member_id |
| 手机号 | phonenumber | mobile |
| 会员名称 | user_name | memberCode |
| 昵称 | nick_name | nickName |
| 密码 | password | password |
| 头像 | avatar | avatar |
| 状态 | status（0正常 1停用） | status |

**注册（事务）：** `insertUser`（角色 **member**）→ `insert biz_member`（`member_id = userId`）。

| 模块 | 表/职责 |
|------|---------|
| **会员（本文）** | `biz_member`、`biz_member_address`；平台列表/详情/启用 |
| **订单管理 v1.0** | `biz_order` 等；履约写操作；向会员回写消费汇总 |
| 商户/店铺/商品 | 无会员表；经营账号 **不关联** `member_id` |

### 1.3 跨模块 Facade

| 接口 | 提供方 | 调用方 | 方法 |
|------|--------|--------|------|
| **`IMemberFacade`** | **本模块** | 订单、C 端下单 | `getMember`、`isMemberEnabled`、`existsByMobile`、`refreshConsumption` |
| **`IOrderFacade`** | 订单（**目标包** `com.ruoyi.web.modules.order`，**待实现**） | **本模块** | `pageOrdersByMember`、`getOrderDetail`、`hasUnfinishedOrdersByShop/Merchant`（见《订单管理技术方案》§1.3） |

> 订单未实现前：`pageOrdersByMember` 由 **`account.facade.DefaultOrderFacade`** 桩返回空页；实现后迁入 `order.facade` 并以《订单管理技术方案》v1.0.2 为准。

**消费统计（O11）实现二选一（推荐 A）：**

| 方案 | 说明 |
|------|------|
| **A 冗余字段** | `biz_member.order_count`、`total_amount`；订单 **已完成** 时订单模块调用 `IMemberFacade.refreshConsumption(memberId)` **同事务** |
| B 实时聚合 | 列表/详情每次 `SUM` `biz_order`；数据量大时再改 A |

### 1.4 状态联动

| 事件 | `biz_member` | `biz_order` | 商户/店铺 |
|------|--------------|-------------|-----------|
| 禁用会员 | `status=1` | **不变**（M15） | 不变 |
| 订单已完成 | 刷新 `order_count`/`total_amount` | 状态→已完成 | — |
| 商户冻结/店停业 | 不变 | 仅禁 **新** 下单 | 各模块规则 |

---

## 2. 数据库设计

### 2.1 本模块表

| 表名 | 说明 |
|------|------|
| `biz_member` | C 端买家主档 |
| `biz_member_address` | 收货地址簿 |

### 2.2 关联表（订单模块，ER 引用）

| 表名 | 说明 |
|------|------|
| `biz_order` | `member_id` FK；DDL 见《订单管理技术方案.md》v1.0 |

### 2.3 `biz_member` + `sys_user`

> 权威 DDL：[`sql/biz_member.sql`](../../../sql/biz_member.sql)。账号列在 **`sys_user`**；查询通过 `biz_member` JOIN `sys_user`。

**`biz_member`（扩展表）**

| 字段 | 类型 | 说明 |
|------|------|------|
| member_id | bigint PK | **= sys_user.user_id**（非自增） |
| member_level | tinyint | 选填；字典 `biz_member_level` |
| order_count | int | 已完成订单笔数 |
| total_amount | decimal(12,2) | 已完成实付合计 |
| register_time | datetime | 注册时间 |
| create_by/time, update_by/time, remark | | 审计 |

**`sys_user`（账号，注册时写入）**

| 字段 | 说明 |
|------|------|
| user_name | **会员名称**（C 端可随机生成） |
| nick_name | 昵称 |
| phonenumber | 手机号；**del_flag=0 唯一** |
| password | BCrypt |
| avatar | 头像 URL |
| status | 0 启用 1 禁用 |
| 角色 | 注册时分配 **role_key=member** |

### 2.4 `biz_member_address`

| 字段 | 类型 | 说明 |
|------|------|------|
| address_id | bigint PK | |
| member_id | bigint | FK |
| 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 是；每会员至多一条默认 |
| del_flag | char(1) | 0 存在 2 删除 |
| create_time, update_time | | |

### 2.5 索引与约束

| 表 | 索引/约束 | 说明 |
|----|-----------|------|
| biz_member | PRIMARY (`member_id`) | `member_id` **非自增**，= `sys_user.user_id` |
| biz_member | KEY `idx_register` (`register_time`) | 列表默认排序 |
| sys_user | 应用层 `checkPhoneUnique` | `phonenumber` 在 `del_flag='0'` 时唯一（M2） |
| sys_user | 应用层 `checkUserNameUnique` | `user_name`（会员名称）唯一 |
| sys_user_role | — | 注册/平台添加时写入 `role_id`（`role_key=member`） |
| biz_member_address | PRIMARY、`idx_member` | `member_id`, `del_flag` |

### 2.6 DDL（权威脚本）

> 完整建表与 **member 角色初始化** 见 [`sql/biz_member.sql`](../../../sql/biz_member.sql)。  
> **勿** 在 `biz_member` 存 `mobile`/`password`/`status` 等账号列；查询通过 `biz_member` **INNER JOIN** `sys_user`。

```sql
-- biz_member：仅扩展字段（账号在 sys_user）
CREATE TABLE `biz_member` (
  `member_id` bigint(20) NOT NULL COMMENT '与 sys_user.user_id 一致',
  `member_level` tinyint(4) DEFAULT NULL,
  `order_count` int(11) NOT NULL DEFAULT '0',
  `total_amount` decimal(12,2) NOT NULL DEFAULT '0.00',
  `register_time` datetime NOT NULL,
  `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 (`member_id`),
  KEY `idx_register` (`register_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='C端会员扩展表';
```

**唯一约束（应用层）：** `sys_user.phonenumber` 在 `del_flag='0'` 时不可重复（M2）；`MemberRegistrationSupport` / `ISysUserService.checkPhoneUnique`。

### 2.7 字典

| dict_type | 用途 |
|-----------|------|
| biz_member_status | `status`：0 启用 / 1 禁用 |
| biz_member_level | `member_level`（tinyint；如 0=普通；选填） |

---

## 3. 平台端接口设计

**基路径：** `/agri/member`  
**权限前缀：** `agri:member`

### 3.1 接口一览

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/list` | list | 会员分页列表 |
| POST | `/` | add | **平台添加会员**（无需短信验证码） |
| GET | `/{memberId}` | query | 会员详情（基本 + 地址列表） |
| PUT | `/{memberId}/status` | edit | 启用/禁用 |
| GET | `/{memberId}/orders` | query | 该会员订单分页（**只读**） |
| GET | `/{memberId}/orders/{orderId}` | query | 订单详情（**只读**；校验 `member_id`） |

> **不提供** 导入、改等级、余额积分（需求 §13）。

### 3.2a 新增 `POST /`

| Body | 说明 |
|------|------|
| mobile | 必填；11 位手机号 |
| password | 必填；BCrypt 入库 |
| memberCode | 选填；`sys_user.user_name`；未填则 8 位随机唯一名 |
| nickName | 选填；未填同会员名称 |
| avatar | 选填；`sys_user.avatar` |
| memberLevel | 选填 |
| status | 选填；默认 `0` 启用 |
| remark | 选填 |

**流程：** 同事务 `insertUser`（角色 **member**）→ `insert biz_member`（`member_id=user_id`）。  
**成功：** 返回 `memberId`。

### 3.2 列表 `GET /list`

| Query | 说明 |
|-------|------|
| pageNum, pageSize | 分页 |
| keyword | 可选；**会员名称、昵称或手机号** 模糊（M11） |
| status | 可选；0/1 |

**过滤：** `u.del_flag='0'`；`EXISTS` 子查询要求 `sys_user_role` + `sys_role.role_key='member'`（`MemberServiceImpl` 注入 `memberRoleKey`，排除仅平台/商户账号）。  
**排序：** `biz_member.register_time DESC`。  
**实现：** `BizMemberMapper.selectList` → `selectVo` JOIN `sys_user`，`memberRoleExists` 片段。

**rows：**

| 字段 | 说明 |
|------|------|
| memberId, memberCode, nickName | memberCode 为会员名称 |
| mobile | 脱敏 |
| memberLevel, memberLevelLabel | memberLevel 为 tinyint 字典值，可空；label 为展示文案 |
| status, statusLabel | 字典 |
| orderCount, totalAmount | 来自冗余字段（与 O11 一致） |
| registerTime | |

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

```json
{
  "memberId": 1,
  "memberCode": "用户A",
  "nickName": "用户A",
  "mobile": "17712349136",
  "memberLevel": null,
  "status": "0",
  "orderCount": 3,
  "totalAmount": 1280.50,
  "registerTime": "2026-05-01 10:00:00",
  "addressList": [
    {
      "addressId": 10,
      "consigneeName": "张三",
      "mobile": "17712349136",
      "regionCode": "540622",
      "regionName": "西藏自治区/那曲市/比如县",
      "detailAddress": "某某路1号",
      "isDefault": "1",
      "fullAddress": "西藏自治区那曲市比如县某某路1号"
    }
  ]
}
```

| 规则 | 说明 |
|------|------|
| 不存在 | `code!=200`，`msg` 会员不存在 |
| 手机号 | 平台 `query` 权限可看完整号（需求 §10 权限） |

### 3.4 启用/禁用 `PUT /{memberId}/status`

```json
{ "status": "1" }
```

| 校验 | 说明 |
|------|------|
| status | 仅 `0` / `1` |
| 禁用 | 记操作日志；**不** 调订单关闭 |

### 3.5 会员订单列表 `GET /{memberId}/orders`

委托 **`IOrderFacade.pageOrdersByMember`**；Query 对齐需求 §6.3：

| Query | 说明 |
|-------|------|
| pageNum, pageSize | |
| orderNo | 订单编号 |
| goodsName | 商品名称 |
| payType, payStatus | 支付 |
| shipStatus | 发货/订单履约状态 |
| consigneeName, consigneeMobile, address | 收货 |
| logisticsCompany | 物流公司 |
| beginTime, endTime | 下单时间 |

**rows：** 与《订单管理》列表字段对齐子集；`operateType=readonly`（前端隐藏发货/关闭按钮，M6）。

### 3.6 会员订单详情 `GET /{memberId}/orders/{orderId}`

- 校验 `biz_order.member_id = memberId`，否则 403/业务异常。  
- 含商品明细、物流节点时间轴；**无** 写操作字段。

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

| 场景 | msg |
|------|-----|
| 会员不存在 | 会员不存在 |
| 订单不属于该会员 | 无权查看该订单 |
| 禁用成功 | 操作成功 |

统一包装：`AjaxResult` / `TableDataInfo`（`code`、`msg`、`data`/`rows`/`total`）。

---

## 4. C 端接口（已实现 · `MemberAppController`）

> **C 端专册：** 注册/登录见 **[《会员注册登录技术方案》](../../../消费者APP/会员注册登录/会员注册登录技术方案.md) v1.2**；本节保留摘要。

**基路径：** `/api/member`  
**鉴权：** `MemberWebConfig` 拦截；`register`/`login`/`sms/send`/`serviceAgreement/**` 匿名；其余须会员 Token（`MemberContext`）。

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/sms/send` | 发送验证码 |
| POST | `/register` | 手机号+密码+确认密码；**无验证码** |
| POST | `/login` | 手机号或**会员名称**+密码；返回 Token |
| GET | `/profile` | 当前会员（密码脱敏） |
| PUT | `/profile` | 改 `user_name`/`nick_name`/`avatar`/`password`（`updateAccount`） |
| GET | `/address/list` | 地址列表 |
| POST | `/address` | 新增地址 |
| PUT | `/address` | 修改地址 |
| DELETE | `/address/{addressId}` | 删除地址 |
| PUT | `/address/{addressId}/default` | 设默认 |

**协作接口（content 模块 · 同基路径前缀）：**

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/serviceAgreement` | 买家服务协议（`MallServiceAgreementAppController`） |
| GET | `/serviceAgreement/status` | 注册/登录开放状态 |

| 规则 | 说明 |
|------|------|
| M2 | 注册/添加前 `countByMobile`（JOIN `sys_user`） |
| M4 | `sys_user.status=1` 禁止登录 |
| M7 | **不** 校验 `biz_merchant_account` 绑定（分表、无 FK） |
| 注册事务 | `insertUser`（角色 **member**）→ `insert biz_member`（`member_id=user_id`） |

---

## 5. 与订单模块协作

### 5.1 `biz_order` 字段（权威 DDL）

完整定义见 [`sql/biz_order.sql`](../../../sql/biz_order.sql) 及《订单管理技术方案》§2（v1.0.1）。会员模块主要消费：

| 字段 | 说明 |
|------|------|
| order_id, order_no | 主键与展示编号 |
| member_id | 关联 `biz_member.member_id` |
| shop_id | 店铺归属 |
| order_status | `3`=已完成（O11 统计）；`0/1/2`=未完成 O10 |
| pay_amount | 实付；**O11** `SUM` 字段 |
| del_flag | 固定 `0`；删单用 `order_status=5` |

明细、物流见 `biz_order_item`、`biz_order_logistics_trace`。

### 5.2 消费汇总刷新（O11）

```text
订单状态 → 已完成
    → IOrderFacade 内或监听
    → IMemberFacade.refreshConsumption(memberId)
    → UPDATE biz_member SET order_count=?, total_amount=?
          WHERE 统计条件 status=已完成 AND del_flag=0
```

`refreshConsumption` 建议 **按 member_id 重算**（避免并发漏加）：

```sql
SELECT COUNT(1), IFNULL(SUM(pay_amount),0)
FROM biz_order
WHERE member_id = #{memberId} AND order_status = '3' AND del_flag = '0'
```

### 5.3 会员订单只读

平台 **会员管理** 与 **订单管理** 共用 `IOrderFacade.getOrderDetail`；会员 Controller **禁止** 暴露发货/关闭 URL（M6）。

---

## 6. 菜单与权限（RuoYi）

| 菜单 | 权限标识 |
|------|----------|
| 会员管理 | `agri:member:list` |
| 添加会员 | `agri:member:add` |
| 查看详情 | `agri:member:query` |
| 启用/禁用 | `agri:member:edit` |

订单只读挂在 `query`；不写 `agri:member:remove`（本期无删会员需求，逻辑删除预留）。

---

## 7. 非本期（库表/接口不建）

| 项 | 说明 |
|----|------|
| 余额、积分、标签、导入 | 需求 §13 |
| 商家端查会员 | 不做 |
| 会员等级自动升级 | 仅 `member_level` 展示 |

> **本期已实现：** 平台 `POST /agri/member` 添加会员（无短信验证码）；C 端注册/登录/资料/地址；列表仅 **role_key=member** 用户。

---

## 8. 源文档对照

| 需求规则 | 技术落点 |
|----------|----------|
| M1～M3 买家、只读订单 | §1.2、§3.5～3.6 |
| M2 手机号唯一 | `sys_user.phonenumber` + `checkPhoneUnique` / `countByMobile` |
| M4 启用 | `sys_user.status` + `IMemberFacade.isMemberEnabled` |
| M5/O11 消费统计 | `biz_member.order_count`/`total_amount` + §5.2 |
| M6 订单不写 | 无发货类接口 |
| M7 与经营账号隔离 | 分表、无 FK |
| M9 订单仅当前会员 | `member_id` 强制过滤 |
| M11 检索 | `keyword` 匹配 `user_name` / `nick_name` / `phonenumber`（API 字段仍为 memberCode 等） |
| 列表范围 | 仅 `role_key=member`（`memberRoleExists`） |
| M12 订单检索 | §3.5 Query |
| M14 地址只读（平台） | 详情 `addressList`；C 端 §4 维护 |

---

*文档版本：v1.4 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 地址表 `region_code`/`region_name` · C 端注册/登录见《消费者APP/会员注册登录/会员注册登录技术方案.md》v1.2 · 关联《会员管理功能需求.md》v1.1、《会员管理测试用例.md》v1.1*
