# 会员注册登录 — 技术方案（C 端）

> **依据：** 平台《会员管理功能需求.md》v1.1 §2.1、《商城服务协议功能需求.md》v1.0  
> **关联：** 平台《会员管理技术方案.md》v1.3（表结构/平台端）、《商城服务协议技术方案.md》v1.0、《商品详情内页功能需求.md》v1.1（GD3 登录引导）、《关联需求分析.md》v1.6  
> **范围：** C 端 **注册、登录** 及注册/登录页依赖的 **`/api/member/serviceAgreement/**`**；**资料/地址** 见同模块 `MemberAppController` 另节或「我的」专册。  
> **原则：** **无短信验证码**；**手机号 + 密码 + 确认密码** 注册；**手机号或会员名称 + 密码** 登录；账号复用 **`sys_user`**（`member_id = user_id`）；Token 与平台 Token **隔离**。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 响应 | MyBatis；`AjaxResult`（`code`、`msg`、`data`） |
| 密码 | BCrypt（`SecurityUtils.encryptPassword`）；存 **`sys_user.password`** |
| C 端鉴权 | `MemberWebConfig` + `MemberAuthInterceptor`；登录成功后框架 **`TokenService#createToken(LoginUser)`**（与 `SysLoginController` 一致，JWT + Redis） |
| 服务协议 | `IMallServiceAgreementFacade` 注册/登录勾选校验 |

### 1.1 术语（C 端登录）

| 产品用语 | 技术字段 | 存储 |
|----------|----------|------|
| **会员名称** / **会员 ID**（登录账号） | `memberCode` | `sys_user.user_name` |
| 手机号（登录账号） | `mobile` | `sys_user.phonenumber` |
| 系统主键（**非** 登录账号） | `memberId` | `sys_user.user_id` = `biz_member.member_id` |

> **说明：** 本文及 C 端 UI 所称 **「会员 ID」= 会员名称**（`user_name`），**不是** 数字型 `member_id`。

### 1.2 模块落位

```text
baqing-shop/src/main/java/com/ruoyi/web/modules/account/
├── controller/MemberAppController.java       # POST /register、/login
├── service/impl/MemberAppServiceImpl.java
├── support/MemberRegistrationSupport.java    # 注册事务
├── dto/MemberRegisterDTO、MemberLoginDTO
└── mapper/BizMemberMapper.xml                # selectByMobile、selectByMemberCode

content 模块：
└── controller/MallServiceAgreementAppController.java   # GET /api/member/serviceAgreement
```

### 1.3 业务流程

```text
【注册 · 无验证码】
    用户输入 mobile + password + confirmPassword [+ memberCode 可选]
        → 协议勾选（若启用）
        → 校验两次密码一致、手机号未注册
        → insertUser（role=member；user_name=memberCode）+ insert biz_member
        → 返回 memberId（系统主键）

【登录】
    用户输入 account（手机号 或 会员名称）+ password
        → 协议勾选（协议启用时）
        → 解析 account → selectByMobile / selectByMemberCode
        → status=0、密码匹配
        → createLoginUser + TokenService.createToken（同 SysLoginController）
        → 返回根级 token + memberId
```

| 关联模块 | 协作 |
|----------|------|
| **商城服务协议** | 注册/登录页拉协议；`agreementAccepted` |
| **商品详情 / 购物车** | 未登录加购 → 跳转本模块注册/登录 |
| **平台会员管理** | 平台禁用 → C 端 login 失败 |

---

## 2. 数据库设计

复用平台会员表，**不新建表**。权威 DDL：[`sql/biz_member.sql`](../../../sql/biz_member.sql)。

| 表 | 注册/登录相关字段 |
|----|-------------------|
| `sys_user` | `user_name`（**会员名称**，登录账号）、`phonenumber`（手机号）、`password`、`status` |
| `biz_member` | `member_id`（系统主键，**非** C 端登录账号）、`register_time` |
| `sys_user_role` | 注册时绑定 **`role_key=member`** |

| 规则 | 说明 |
|------|------|
| M2 | 手机号 `del_flag=0` **唯一** |
| 会员名称 | `user_name` **唯一**（`checkUserNameUnique`） |
| M4 | `status=1` **禁止登录** |

---

## 3. C 端接口设计

**基路径：** `/api/member`

### 3.1 鉴权

| 路径 | 鉴权 |
|------|------|
| `POST /register`、`POST /login` | **`@Anonymous`** |
| `GET /serviceAgreement`、`/serviceAgreement/status` | **`@Anonymous`** |
| `/profile`、`/address/**` | 须 Token（**非本文**） |

### 3.2 接口一览（注册/登录）

| 方法 | 路径 | 说明 | 状态 |
|------|------|------|------|
| POST | `/register` | 手机号 + 密码 + 确认密码注册 | **已实现** |
| POST | `/login` | 手机号或 **会员名称** + 密码登录 | **已实现** |
| GET | `/serviceAgreement` | 买家服务协议 | **已实现** |
| GET | `/serviceAgreement/status` | 注册/登录开放状态 | **已实现** |

| 项 | 说明 |
|----|------|
| **`POST /sms/send`** | **非本期**；注册 **不依赖** 短信验证码 |

---

### 3.3 注册 `POST /api/member/register`

| Body 字段 | 必填 | 说明 |
|-----------|:----:|------|
| mobile | 是 | 11 位手机号 |
| password | 是 | 登录密码 |
| confirmPassword | 是 | 确认密码；须与 `password` **一致** |
| memberCode | 否 | **会员名称**（`user_name`）；未填则注册逻辑 **自动生成** 唯一名称 |
| nickName | 否 | 昵称 |
| agreementAccepted | 是* | 协议 **启用** 时须 `true` |

**流程：**

```text
validateRegister（mobile、password、confirmPassword 非空；两次密码一致）
    → assertAcceptedForRegister
    → countByMobile（已注册 → 失败）
    → MemberRegistrationSupport.createMember（无 verifyCode）
```

| 场景 | msg |
|------|-----|
| 缺手机号 | 请输入手机号 |
| 缺密码 | 请输入密码 |
| 缺确认密码 | 请输入确认密码 |
| 两次密码不一致 | 两次输入的密码不一致 |
| 手机号已注册 | 手机号已注册 |
| 协议未勾选 | Facade 统一文案 |
| 注册关闭 | 会员注册暂未开放 |

**成功 `data`：** `memberId`（long，**系统主键**；登录请使用 **手机号** 或注册的 **会员名称**）。

**请求示例：**

```json
{
  "mobile": "13800138000",
  "password": "Pass1234",
  "confirmPassword": "Pass1234",
  "memberCode": "农资买家001",
  "agreementAccepted": true
}
```

---

### 3.4 登录 `POST /api/member/login`

| Body 字段 | 必填 | 说明 |
|-----------|:----:|------|
| account | 是 | **手机号** 或 **会员名称**（`user_name` / `memberCode`） |
| password | 是 | 登录密码 |
| agreementAccepted | 条件 | 协议 **启用** 时须 `true`；未启用 **忽略**（SP4） |

> **兼容：** 若请求仍传 `mobile` 且 `account` 为空，Service **等同** `account=mobile`（旧客户端过渡）。

**account 解析规则：**

```text
trim(account)
    → 若匹配大陆手机号格式（11 位、1 开头）→ selectByMobile
    → 否则 → selectByMemberCode（精确匹配 sys_user.user_name）
    → 未命中 → 统一提示「账号或密码错误」
```

**流程：**

```text
校验 account、password 非空
    → assertAcceptedForLogin
    → resolveMemberByAccount
    → status=0；matchesPassword
    → createToken
```

**成功响应（与平台 `/login` 一致，`token` 在根级）：**

```json
{
  "code": 200,
  "msg": "操作成功",
  "token": "eyJhbGciOiJIUzUxMiJ9...",
  "memberId": 1001
}
```

> `memberId` 为系统主键；后续请求 Header：`Authorization: Bearer {token}`（`token.header` 配置，默认 30 分钟有效，不足 20 分钟自动续期）。

| 场景 | msg |
|------|-----|
| 缺账号/密码 | 请输入账号和密码 |
| 账号不存在或密码错 | 账号或密码错误 |
| 禁用 | 账号已禁用，请联系客服 |

**请求示例（手机号）：**

```json
{
  "account": "13800138000",
  "password": "Pass1234",
  "agreementAccepted": true
}
```

**请求示例（会员名称 / 产品所称会员 ID）：**

```json
{
  "account": "农资买家001",
  "password": "Pass1234"
}
```

---

### 3.5 服务协议（注册/登录页）

> 详述见《商城服务协议技术方案.md》§4。

| 方法 | 路径 | 用途 |
|------|------|------|
| GET | `/api/member/serviceAgreement` | 协议正文 + `registrationOpen` / `requireAgreementOnLogin` |
| GET | `/api/member/serviceAgreement/status` | 仅拉开关 |

---

## 4. Service 要点

```text
MemberAppServiceImpl.register
    → 校验 password === confirmPassword
    → 不调用 IMemberSmsService.verifyCode

MemberAppServiceImpl.login
    → resolveMemberByAccount(account)
    → SysUser + UserDetailsServiceImpl.createLoginUser
    → TokenService.createToken(loginUser)
```

---

## 5. 业务规则

| 编号 | 规则 |
|------|------|
| **RL1** | 注册 **仅需手机号 + 密码 + 确认密码**；**无** 短信验证码 |
| **RL2** | 确认密码与密码 **必须一致** |
| **RL3** | 登录账号 = **手机号** 或 **会员名称**（`user_name`）；产品 **「会员 ID」即会员名称** |
| **RL4** | 登录失败 **统一** 提示「账号或密码错误」 |
| **RL5** | 禁用会员 **不可** 登录 |
| **RL6** | 协议启用时注册/登录须 **勾选** `agreementAccepted` |
| **RL7** | 与商户经营账号 **分表**，互不影响（M7） |
| **RL8** | 响应中的 `memberId` 为 **系统主键**；**不可** 当作登录账号 |

---

## 6. 前端调用建议

| 场景 | 调用 |
|------|------|
| 注册/登录页初始化 | `GET /api/member/serviceAgreement` |
| 注册提交 | `POST /register`（mobile、password、confirmPassword、agreementAccepted） |
| 登录提交 | `POST /login`（account、password） |
| 登录成功 | 存根级 `token`；Header `Authorization: Bearer {token}` |
| 跳转资料/地址 | 登录后调 `/profile`、`/address/*` |

**注册页表单：**

```text
手机号 → 密码 → 确认密码 → [会员名称 可选] → [协议勾选] → 注册
（无「获取验证码」步骤）
```

**登录页表单：**

```text
账号（手机号或会员名称/会员ID）→ 密码 → [协议勾选] → 登录
```

---

## 7. 实现状态

| 项 | 状态 |
|----|------|
| `POST /register`（无验证码 + confirmPassword） | **已实现** |
| `POST /login`（account：手机号/会员名称） | **已实现** |
| `BizMemberMapper.selectByMemberCode` | **已实现** |
| `POST /sms/send` | **保留代码、非本期** |
| C 端注册/登录 **前端** | **已实现**（见《会员注册登录前端技术方案.md》v1.1，含根级 token 解析） |

---

## 8. 非本期

| 项 | 说明 |
|------|------|
| 短信验证码注册/登录 | **明确不做** |
| 以数字 `member_id` 作为登录账号 | **不做** |
| 微信/支付宝 OAuth | — |
| 忘记密码 / 换绑手机 | — |

---

## 9. 与平台文档关系

| 文档 | 关系 |
|------|------|
| [平台《会员管理技术方案》](../../平台后台/会员管理/会员管理技术方案.md) | 表结构、平台端、Facade |
| [《商城服务协议技术方案》](../../平台后台/内容管理/商城服务协议/商城服务协议技术方案.md) | 协议接口 |

---

## 10. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版（含短信验证码） |
| **v1.1** | 取消短信验证码；注册 confirmPassword；登录 account |
| **v1.2** | **会员 ID = 会员名称**（`user_name`）；登录 `selectByMemberCode`；**非** 数字 member_id |
| **v1.3** | Token 与平台一致：`TokenService` + `LoginUser`；响应根级 `token` |

---

*文档版本：v1.3 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2*
