# 我的服务 — 技术方案(C 端) > **依据:** 《我的服务功能需求.md》v1.1 > **关联:** 平台《会员管理技术方案.md》v1.3、《商户入驻审核技术方案.md》v1.2、《商城入驻协议技术方案.md》v1.0、《关联需求分析.md》v1.6;C 端《会员注册登录技术方案.md》v1.3(登录前置) > **范围:** C 端 **个人资料、修改密码、收货地址、商家入驻申请** 的后端落位、表结构、接口契约;**不含** 前端页面与平台审核实现细节(平台审核见入驻审核专册)。 > **v1.4:** 已有商户管理员 **仅开店铺** 入驻:`GET /api/merchant/entry/context`;`submitApply` shopOnly 分支。 > **原则:** 账号复用 **`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、EntryApplyContextVO.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(匿名) GET /api/merchant/entry/context(会员 · 是否仅开店铺) → 新主体:填写 subject/biz/shop → 已有商户管理员:仅填写 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/context`、`/apply`、`/my` | **会员 Token**(`MemberWebConfig` → `/api/merchant/entry/**`,排除 agreement/status) | **`MemberWebConfig` 拦截范围(摘要):** ```java .addPathPatterns("/api/member/**", "/api/merchant/entry/**", "/api/shop/*/follow", "/api/cart/**", "/api/checkout/**", "/api/order/**") .excludePathPatterns("/api/member/register", "/api/member/login", "/api/member/sms/send", "/api/member/serviceAgreement", "/api/member/serviceAgreement/**", "/api/merchant/entry/agreement", "/api/merchant/entry/status"); ``` > **`/api/member/login` 不在拦截器内**;登录成功后客户端 **带 Token** 访问 `/context` 等接口,由 `MemberAuthInterceptor` 注入 `MemberContext`。 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, shopOnlyMode?, existingMerchantId? }` 快照 | | 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 入驻上下文(v1.4) #### `GET /api/merchant/entry/context` | 项 | 说明 | |----|------| | 鉴权 | 会员 Token | | 响应 | `EntryApplyContextVO` | | 字段 | 说明 | |------|------| | shopOnlyMode | 是否已是商户管理员 | | canShopOnlyApply | 是否可仅填店铺提交 | | blockReason | 不可时的原因 | | merchantId、merchantType、subjectLabel、merchantName | 展示用 | | subject、biz | 只读快照(`BizMerchant` 结构) | **`canShopOnlyApply=true` 条件:** 商户 `cert_status=正常` 且 `biz_complete=1`。 **状态:** **已实现**。 --- ### 3.6 提交入驻申请 #### `POST /api/merchant/entry/apply` | 项 | 说明 | |----|------| | 鉴权 | 会员 Token | | Body | `EntryApplySubmitDTO` | ```json { "subject": { }, "biz": { }, "shop": { "shopName", "shopAvatar", "shopPhone", "shopDesc" }, "agreementAccepted": true } ``` **仅开店铺(v1.4):** Body 可仅含 `shop` + `agreementAccepted`;后端 `buildSubmitFromExistingMerchant` 合并 DB 商户快照;`form_json` 写入 `shopOnlyMode=true`、`existingMerchantId`。 - `subject` / `biz` 结构同 `BizMerchant`;字段清单见 `sql/biz_merchant_entry_form_json说明.sql`。 - `merchant_type` 由 `EntryApplyFormValidator.resolveMerchantType` 从表单推断(1 个人 / 2 企业);仅开店铺时取自已有商户。 | 校验 | 实现 | |------|------| | 协议勾选 | `IMerchantEntryAgreementFacade.assertAccepted` | | 待审/公示中 | `countBlockingByMemberId`(status in 0,3) | | 完整字段 | 新主体:`validateMobileSubmit`;仅开店铺:`validateShopOnlySubmit` | | 唯一性 | 证件/信用代码/merchant_name/shop_name(仅开店铺 exclude `existingMerchantId`) | | 会员昵称 | 绑定经营账号路径须 `nick_name` 非空 | | 商户就绪 | 仅开店铺:`assertMerchantEligibleForShopOnly`(正常 + biz_complete=1) | **成功 `data`:** ```json { "applyId": 1, "applyNo": "EA202605260001", "applyStatus": "0" } ``` **状态:** **已实现**。 --- ### 3.7 我的入驻申请 #### `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.8 错误码(业务 msg 示例) | 场景 | msg | |------|-----| | 未登录 | 请先登录(401) | | 会员不存在 | 会员不存在 | | 地址不存在 | 地址不存在 | | 待审申请已存在 | 常量 `MSG_PENDING_EXISTS` | | 协议未勾选 | Facade 统一文案 | | 旧密码错误 | 旧密码错误 | | 证件/店名重复 | 对应唯一性提示 | | 商户经营未完善 | 商户经营信息未完善,请联系平台完善后再申请开店 | | 商户冻结/注销 | 对应 `MSG_MERCHANT_FROZEN` / `MSG_MERCHANT_CANCELLED` | --- ## 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 非空) → findMerchantByMemberAdmin ├── 有:validateShopOnlySubmit + buildSubmitFromExistingMerchant + assertUnique(exclude merchantId) └── 无:validateMobileSubmit + assertUnique → insert biz_merchant_entry_apply(form_json + shopOnlyMode/existingMerchantId) ``` --- ## 5. 与平台方案协作 | 平台文档 | C 端本模块关系 | |----------|----------------| | 《会员管理技术方案》§4 | 共用 `/api/member/profile`、`/address/**`;平台 `/agri/member` **只读** | | 《商户入驻审核技术方案》§4 | C 端 **3.5~3.7** 对应其 **4.0~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 | **已实现** | | | GET `/context`、POST `/apply` shopOnly | **已实现** | v1.4 | | POST `/apply`、GET `/my` | **已实现** | 含公示时间 | | C 端前端 | **已实现**(见《我的服务前端技术方案.md》v1.4,含 shopOnly 分步) | --- ## 7. 非本期 | 项 | 说明 | |----|------| | 手机号换绑 | — | | 资料页改会员名称 | 功能需求未列 | | 待审申请撤销/改资料 | 驳回后重提 | | `GET /api/member/message/list` | 消息列表另册 | | 入驻表单草稿保存 | — | --- ## 8. 修订记录 | 版本 | 说明 | |------|------| | **v1.4.1** | `MemberWebConfig` 统一拦截 `/api/merchant/entry/**`;`/login` 不设置 `MemberContext` | | **v1.4** | 入驻 **仅开店铺**:`GET /context`;`validateShopOnlySubmit`;`form_json.shopOnlyMode` | | **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.4.1 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《我的服务功能需求.md》v1.2*