商户入驻审核 — 技术方案
依据: 《商户入驻审核功能需求.md》v1.2
关联: 《商户管理技术方案.md》v1.5、《店铺管理技术方案.md》v1.2.5、《商城入驻协议技术方案.md》v1.0、《会员管理技术方案.md》v1.0.1、《关联需求分析.md》v1.2
范围: C 端 完整字段 申请提交;平台 审核→公示→完成入驻;公示完成后落库商户+首店+经营账号;站内消息。
原则: 申请状态与商户 cert_status 分离;平台 POST /agri/merchant 不写 申请单。
v1.1: apply_status 增加 3公示中;approve 仅进公示;completePublicity/processExpiredPublicity 建档;校验类 EntryApplyFormValidator;DDL 见 sql/ 目录。
v1.2: GET /api/merchant/entry/context;已有商户管理员 仅开店铺 提交与公示完成 复用商户 建档分支;form_json 扩展 shopOnlyMode、existingMerchantId。
1. 技术架构
| 项 |
选型 |
| 基础框架 |
RuoYi v3.9.2(springboot2 分支) |
| 数据库 |
MySQL 5.7.39 |
| ORM / 权限 |
MyBatis;@PreAuthorize;AjaxResult / TableDataInfo |
| 申请状态 |
apply_status char(1):0 待审 1 已完成入驻 2 未通过 3 公示中 |
| 校验 |
EntryApplyFormValidator.validateMobileSubmit(新主体);validateShopOnlySubmit(已有商户管理员) |
| 表单快照 |
form_json LONGTEXT(详情展示 + 审核落库映射) |
| 日志 |
审核 @Log |
1.1 模块落位
baqing-shop
├── com.ruoyi.web.modules.entry
│ ├── controller.MerchantEntryApplyController # 平台审核
│ ├── controller.MerchantEntryApplyAppController # C 端提交/我的申请
│ ├── domain.BizMerchantEntryApply
│ ├── mapper.BizMerchantEntryApplyMapper
│ ├── service.IMerchantEntryApplyService
│ ├── dto.EntryApplySubmitDTO / EntryApplyRejectDTO
│ ├── vo.EntryApplyListVO / EntryApplyDetailVO / EntryApplyContextVO
│ ├── constant.MerchantEntryApplyConstants
│ ├── support.EntryApplyFormMapper # JSON ↔ 商户/店铺 DTO
│ ├── support.EntryApplyFormValidator # C 端完整字段校验
│ ├── task.MerchantEntryPublicityTask # 公示期满自动建档(每 30 分钟)
│ └── support.MemberEntryMessageSupport # 站内消息
sql/
├── biz_merchant_entry_apply.sql
├── biz_merchant_entry_form_json说明.sql # form_json 字段对照
└── biz_member_message.sql # 可选:会员站内消息
包名 entry 与 content 下「入驻协议」区分:协议=配置;本模块=申请单。
1.2 业务链
biz_member
└── biz_merchant_entry_apply(apply_status)
├── C 端 GET /api/merchant/entry/context(是否仅开店铺)
├── C 端 POST /api/merchant/entry/apply
│ └── IMerchantEntryAgreementFacade.assertAccepted
│ └── 新主体:EntryApplyFormValidator.validateMobileSubmit
│ └── 仅开店铺:validateShopOnlySubmit + EntryApplyFormMapper.buildSubmitFromExistingMerchant
│
├── 平台 GET list/detail、PUT approve/reject/completePublicity
│
├── approve(事务)
│ └── apply_status=3;写 publicity_*;发「公示中」消息
│
└── completePublicity / processExpiredPublicity(事务)
├── shopOnlyMode=false:insert biz_merchant + bindAccount + createShop
├── shopOnlyMode=true:复用 existingMerchantId;bindAccount(若无);createShop
├── apply_status=1;回填 merchant_id/shop_id
└── biz_member_message(入驻完成通知)
| 模块 |
关系 |
| 商城入驻协议 |
C 端提交前 assertAccepted |
| 商户管理 |
公示完成后 insert 商户;不 经 insertMerchant 平台表单接口 |
| 店铺管理 |
公示完成后 createShop |
| 会员管理 |
member_id;消息收件人 |
| 商品/订单 |
无直接依赖 |
1.3 跨模块 Facade
| 接口 |
提供方 |
方法 |
用途 |
IMerchantEntryAgreementFacade |
content |
assertAccepted(boolean) |
C 端提交 |
IMerchantAccountBindService |
merchant |
bindAccountOnMerchantCreate(...) |
会员→经营账号 |
IShopService |
store |
createShop(ShopCreateDTO) |
首家店 |
IMerchantFacade |
merchant |
唯一性校验可复用 Mapper |
证件/信用代码 |
1.4 状态联动
| 事件 |
biz_merchant_entry_apply |
biz_merchant |
| approve |
status=3(公示中) |
不 新增 |
| completePublicity |
status=1;回填 merchant_id/shop_id |
新增 或 复用(shopOnlyMode) |
| reject |
status=2 |
不 新增 |
| 改入驻协议 |
不变 |
不变 |
| 平台代录入商户 |
无申请行 |
直接新增 |
2. 数据库设计
2.1 本模块表 biz_merchant_entry_apply
权威 DDL 与 COMMENT 见 sql/biz_merchant_entry_apply.sql。
| 字段 |
类型 |
说明 |
| apply_id |
bigint PK |
申请 ID |
| apply_no |
varchar(32) UK |
申请编号,如 EA+日期+序号 |
| apply_status |
char(1) |
0 待审 1 已完成入驻 2 未通过 3 公示中 |
| merchant_type |
char(1) |
1 个人 2 企业 |
| member_id |
bigint |
申请人会员 ID |
| member_code |
varchar(64) |
冗余检索:会员名称 |
| subject_label |
varchar(128) |
列表「申请信息」:姓名/企业名 |
| contact_name |
varchar(64) |
联系人 |
| contact_phone |
varchar(20) |
联系人手机 |
| id_card_no |
varchar(32) |
个人证件号(唯一校验) |
| credit_code |
varchar(32) |
企业信用代码(唯一校验) |
| merchant_name |
varchar(128) |
申请中商户名称(唯一校验) |
| shop_name |
varchar(128) |
申请中店铺名称(唯一校验) |
| form_json |
longtext |
完整表单 JSON 快照 |
| reject_reason |
varchar(500) |
驳回原因 |
| merchant_id |
bigint |
公示完成 后回填 |
| shop_id |
bigint |
公示完成 后回填 |
| apply_time |
datetime |
提交时间 |
| audit_by |
varchar(64) |
审核人(进入公示或驳回时) |
| audit_time |
datetime |
审核时间 |
| publicity_start_time |
datetime |
公示开始(approve 时) |
| publicity_end_time |
datetime |
公示结束(默认 start+7 天) |
| del_flag |
char(1) |
0 存在 2 删 |
| create_by/time, update_by/time |
|
审计 |
索引:
| 索引 |
用途 |
uk_apply_no |
编号 |
idx_status_time (apply_status, apply_time) |
待审列表 |
idx_member_status (member_id, apply_status) |
同会员待审校验 |
idx_member_code (member_code) |
关键词检索 |
idx_id_card (id_card_no) |
个人唯一 |
idx_credit_code (credit_code) |
企业唯一 |
2.2 会员站内消息 biz_member_message(建议)
| 字段 |
类型 |
说明 |
| message_id |
bigint PK |
|
| member_id |
bigint |
收件会员 |
| biz_type |
varchar(32) |
ENTRY_AUDIT |
| biz_id |
bigint |
apply_id |
| title |
varchar(128) |
如「入驻审核通过」 |
| content |
varchar(1000) |
含驳回原因摘要 |
| read_flag |
char(1) |
0 未读 1 已读 |
| create_time |
datetime |
|
若已有统一消息中心,可替换本表;接口契约不变。
2.3 form_json 结构(约定)
{
"subject": { },
"biz": { },
"shop": { "shopName", "shopAvatar", "shopPhone", "shopDesc" },
"agreementAccepted": true
}
subject / biz 字段名与 biz_merchant、商户管理 DTO 一致,便于 EntryApplyFormMapper.toMerchantFromJson() / toShopCreateDTO()。
- C 端必填字段清单见
sql/biz_merchant_entry_form_json说明.sql;提交时由 EntryApplyFormValidator 校验。
- 详情接口解析 JSON 填
EntryApplyDetailVO 各分区。
2.4 与既有表关系
| 表 |
关系 |
biz_merchant |
completePublicity 后 insert;merchant_id 回写 apply |
biz_shop |
completePublicity 后 insert;shop_id 回写 |
biz_merchant_account |
绑定会员时 insert |
biz_member |
member_id FK 逻辑 |
平台 biz_merchant 无 apply_id 字段:代录入商户 source_type 可选扩展(1 平台 / 2 C 端),非本期必做。
3. 状态机(Service)
| 操作 |
前置 |
结果 |
副作用 |
| submit |
无 blocking(0/3);协议已勾选;完整字段+唯一性通过 |
apply_status=0 |
— |
| approve |
status=0 |
status=3 |
写 publicity_*;「公示中」消息 |
| completePublicity |
status=3 且 now>=publicity_end_time |
status=1 |
商户+店+账号+完成消息 |
| processExpiredPublicity |
定时扫描 status=3 且已到期 |
同上 |
批量 completePublicity |
| reject |
status=0;rejectReason 非空 |
status=2 |
驳回消息 |
4. 接口设计
4.0 C 端 — 入驻上下文 GET /api/merchant/entry/context(v1.2)
| 项 |
说明 |
| 鉴权 |
会员 Token(MemberAuthInterceptor → MemberContext;/api/merchant/entry/** 已纳入 MemberWebConfig,排除 agreement/status) |
| 用途 |
进入申请页前判断是否 仅开店铺 模式 |
响应 EntryApplyContextVO:
| 字段 |
说明 |
| shopOnlyMode |
当前会员是否已是某商户 商户管理员 |
| canShopOnlyApply |
shopOnlyMode=true 且商户 可开店 时为 true |
| blockReason |
不可仅开店铺原因(经营未完善/冻结/注销) |
| merchantId、merchantType |
已有商户 ID、类型 |
| subjectLabel、merchantName |
列表/页头展示 |
| subject、biz |
主体/经营快照(BizMerchant 结构,供 C 端只读展示) |
canShopOnlyApply 条件: cert_status=正常 且 biz_complete=1。
4.1 C 端 — 提交申请 POST /api/merchant/entry/apply
| 项 |
说明 |
| 鉴权 |
会员 Token(MemberAuthInterceptor) |
| Body |
EntryApplySubmitDTO:subject + biz + shop + agreementAccepted(仅开店铺 时 Body 可仅含 shop + agreementAccepted) |
| 分支 |
校验 |
说明 |
| 新主体 |
validateMobileSubmit + 全量唯一性 |
同 v1.1 |
| 仅开店铺 |
会员已是商户管理员;assertMerchantEligibleForShopOnly;validateShopOnlySubmit;buildSubmitFromExistingMerchant 合并 DB |
form_json 含 shopOnlyMode=true、existingMerchantId;唯一性 跳过 本商户证件/信用代码/商户名称 |
| 校验 |
说明 |
| 协议 |
IMerchantEntryAgreementFacade.assertAccepted |
| 待审/公示中 |
同 member_id 无 status in (0,3)(countBlockingByMemberId) |
| 完整字段 |
新主体:EntryApplyFormValidator.validateMobileSubmit;仅开店铺:validateShopOnlySubmit |
| 唯一性 |
证件/信用代码/merchant_name/shop_name(仅开店铺时 exclude existingMerchantId) |
| 会员昵称 |
绑定路径须 nick_name 非空 |
成功: { applyId, applyNo, applyStatus: "0" }
4.2 C 端 — 我的申请 GET /api/merchant/entry/my(可选)
| 项 |
说明 |
| 鉴权 |
会员 Token |
| 响应 |
当前会员最近 N 条申请摘要(状态、时间、驳回原因) |
4.3 平台 — 列表 GET /agri/merchantEntryApply/list
| 项 |
说明 |
| 权限 |
agri:merchantEntryApply:list |
| Query |
keyword(member_code)、applyStatus、pageNum/pageSize |
| 排序 |
status=0 → apply_time ASC;否则 apply_time DESC |
4.4 平台 — 待审数量 GET /agri/merchantEntryApply/pendingCount
| 响应 | { "pendingCount": n } |
4.5 平台 — 详情 GET /agri/merchantEntryApply/{applyId}
| 权限 | agri:merchantEntryApply:query |
| 响应 | EntryApplyDetailVO:基础信息 + 解析 form_json 各分区 + 公示时间 + canApprove/canReject/canCompletePublicity |
4.6 平台 — 审核通过(进入公示) PUT /agri/merchantEntryApply/approve/{applyId}
| 权限 | agri:merchantEntryApply:audit |
| 日志 | title=商户入驻审核通过 |
| Body | 可选 { "confirmed": true } 二次确认 |
成功: { applyStatus: "3", publicityEndTime };不 返回 merchantId/shopId。
4.7 平台 — 完成公示 PUT /agri/merchantEntryApply/completePublicity/{applyId}
| 权限 | agri:merchantEntryApply:audit |
| 前置 | status=3 且公示期已满(publicity_end_time <= now) |
| 日志 | title=商户入驻公示完成 |
成功: { merchantId, shopId };申请 status=1。
定时任务 MerchantEntryPublicityTask(默认每 30 分钟)调用 processExpiredPublicity(),对到期公示单自动执行与 4.7 相同的建档逻辑(操作人 system)。
4.8 平台 — 审核驳回 PUT /agri/merchantEntryApply/reject
| 权限 | agri:merchantEntryApply:audit |
| Body | { "applyId", "rejectReason" } |
| 校验 | rejectReason 必填 |
4.9 C 端 — 消息 GET /api/member/message/list(可选独立模块)
| 说明 | 读取 biz_member_message;本方案可由 MemberEntryMessageSupport 写入 |
5. Service 要点
5.1 submitApply(memberId, dto)
assertAgreement → assertNoBlockingApply(memberId)
→ findMerchantByMemberAdmin(memberId)
├── 非空(仅开店铺)
│ → assertMerchantEligibleForShopOnly
│ → validateShopOnlySubmit(dto)
│ → buildSubmitFromExistingMerchant(merchant, dto.shop)
│ → assertUnique(..., existingMerchantId)
└── 空(新主体)
→ validateMobileSubmit(dto)
→ assertUnique(...)
→ build BizMerchantEntryApply + form_json(shopOnlyMode, existingMerchantId)
→ insert
5.2 resolveEntryContext(memberId)(v1.2)
findMerchantByMemberAdmin → 无则 shopOnlyMode=false
→ 有则 shopOnlyMode=true;填充 subject/biz 快照
→ resolveShopOnlyBlockReason → canShopOnlyApply / blockReason
5.3 approve(applyId, dto, operator)(@Transactional)
load apply(status=0) → re-validate unique
→ update apply: status=3, publicity_start/end, audit_*
→ memberMessageSupport.sendPublicity(...)
→ return { applyStatus, publicityEndTime }
5.4 completePublicity(applyId, operator) / finalizeEntry(@Transactional)
load apply(status=3) → assert publicity_end <= now
→ re-validate unique(含 existingMerchantId 排除)
→ 若 shopOnlyMode 或会员已是商户管理员
→ reconcileWithExistingMerchant(复用商户 + createShop)
→ 否则
→ validateMobileSubmit → insert merchant → bindAccount → createShop
→ update apply: status=1, merchant_id, shop_id
→ memberMessageSupport.sendPass(...)
5.5 reject(dto, operator)
load apply(status=0) → update status=2, reject_reason
→ memberMessageSupport.sendReject(memberId, reason)
5.6 公示配置
| 配置键 |
说明 |
默认 |
agri.merchant.entry.publicityDays |
公示天数 |
7 |
5.7 经营账号(会员路径)
| 项 |
规则 |
| 关联行 |
account_id=member_id(= sys_user.user_id),merchant_id,shop_id=NULL |
| 登录凭证 |
沿用会员既有 sys_user(user_name=会员名称,nick_name=会员昵称) |
| 角色 |
grantRoleIfAbsent(memberId, merchant) |
6. 业务规则映射(EA)
| 编号 |
实现 |
| EA1 |
仅 C 端 submitApply 写 apply 表 |
| EA2 |
apply_status 独立字段 |
| EA3 |
approve/reject 校验 status=0 |
| EA4 |
reject 校验 rejectReason |
| EA5 |
completePublicity 事务内商户+店+消息 |
| EA5a |
approve 仅 进公示,不 insert merchant/shop |
| EA6 |
reject 不 insert merchant/shop |
| EA7 |
EntryApplyFormMapper 字段对齐商户/店铺 |
| EA8 |
IMerchantEntryAgreementFacade |
| EA9 |
list 支持 applyStatus 筛选 |
| EA10 |
list keyword → member_code like |
| EA11 |
countBlockingByMemberId(status 0 或 3)> 0 阻断 submit |
| EA12 |
唯一:merchant 表 + apply 表 status in (0,1,3) |
| EA13 |
驳回后可新 apply_id |
| EA14 |
biz_member_message |
| EA15 |
不调用商品/订单 Service |
| EA16 |
resolveEntryContext + 仅开店铺 submit 分支 |
| EA17 |
form_json.shopOnlyMode / existingMerchantId |
| EA18 |
finalizeEntry → reconcileWithExistingMerchant 复用商户 |
7. 菜单权限(建议)
| 菜单 |
权限 |
| 商户入驻审核 |
agri:merchantEntryApply:list |
| 查看详情 |
agri:merchantEntryApply:query |
| 审核 |
agri:merchantEntryApply:audit |
8. 非本期实现
申请批量审核、审核 SLA、申请单修改、短信通知、游客免会员提交、申请单导出。
9. 文档索引
| 文档 |
版本 |
| 商户入驻审核功能需求.md |
v1.2 |
| 商户管理技术方案.md |
v1.5 |
| 店铺管理技术方案.md |
v1.2.5 |
| 商城入驻协议技术方案.md |
v1.0 |
10. 修订记录
| 版本 |
说明 |
| v1.0 |
首版:申请单表、平台/C 端接口、审核落库 |
| v1.0.1 |
曾用 shopEntryApply / biz_shop_entry_apply 命名 |
| v1.0.2 |
定名 商户入驻审核;/agri/merchantEntryApply、biz_merchant_entry_apply;规则 EA1~EA15 |
| v1.1 |
审核→公示→完成入驻;apply_status=3;completePublicity/定时任务;EntryApplyFormValidator;公示字段 DDL |
| v1.2 |
GET /context;validateShopOnlySubmit;buildSubmitFromExistingMerchant;reconcileWithExistingMerchant;EA16~EA18 |
文档版本:v1.2 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《商户入驻审核功能需求.md》v1.2