商城服务协议 — 技术方案
依据: 《商城服务协议功能需求.md》v1.0
关联: 《会员管理技术方案.md》v1.0.1、《商城入驻协议技术方案.md》v1.0、《首页banner设置技术方案.md》v1.0、《关联需求分析.md》v1.2
范围: 全平台 单条 买家服务协议配置;平台查看/保存;C 端只读拉取(注册/登录场景);无 会员/订单表 FK。
原则: 单表单行(config_id=1);启用时正文必填;C 端 @Anonymous;与入驻协议 同构分表;SP4 区分注册开放与登录是否须勾选。
1. 技术架构
| 项 |
选型 |
| 基础框架 |
RuoYi v3.9.2(springboot2 分支) |
| 数据库 |
MySQL 5.7.39 |
| ORM / 权限 / 响应 |
MyBatis;@PreAuthorize;AjaxResult |
| 富文本 |
库字段 LONGTEXT;AgreementContentSupport 判空 |
| 日志 |
保存配置 @Log |
1.1 模块落位
baqing-shop
├── com.ruoyi.web.modules.content
│ ├── controller.MallServiceAgreementController # 平台单页配置
│ ├── controller.MallServiceAgreementAppController # C 端只读
│ ├── domain.BizMallServiceAgreement
│ ├── mapper.BizMallServiceAgreementMapper
│ ├── service.IMallServiceAgreementService
│ ├── constant.MallServiceAgreementConstants
│ ├── facade.IMallServiceAgreementFacade
│ ├── facade.impl.MallServiceAgreementFacadeImpl
│ └── support.AgreementContentSupport # 与入驻协议共用
sql/
└── biz_mall_service_agreement.sql
与 入驻协议、Banner 同属 content 包;独立表 biz_mall_service_agreement。
1.2 业务链
biz_mall_service_agreement(config_id=1 · 单例)
│
├── 平台 GET/PUT /agri/mallServiceAgreement
│
├── C 端 GET /api/member/serviceAgreement(Anonymous)
│ ├── registrationOpen=false → 不可完成新注册
│ └── requireAgreementOnLogin=false → 登录页不展示勾选(SP4)
│
└── 会员模块 POST /api/member/register | /login
└── Facade 校验 agreementAccepted(见 §4.4、§5)
| 模块 |
关系 |
| 会员管理 |
register / login 注入 IMallServiceAgreementFacade;不 改 biz_member 结构 |
| 入驻协议 |
表、接口路径 独立;用户须 分别勾选(SP7) |
| 商户/店铺/订单 |
无依赖;改协议不级联(SP10) |
1.3 跨模块契约
| 接口 |
提供方 |
调用方 |
方法 |
IMallServiceAgreementFacade |
本模块 |
MemberAppController / MemberServiceImpl |
isRegistrationOpen() — 同 isEnabled() |
|
|
|
isAgreementRequiredOnLogin() — 同 isEnabled() |
|
|
|
assertAcceptedForRegister(boolean agreementAccepted) |
|
|
|
assertAcceptedForLogin(boolean agreementAccepted) |
|
|
|
getCheckboxLabel() |
| 场景 |
Facade 行为 |
| 注册 |
!isRegistrationOpen() →「会员注册暂未开放」;未勾选 →「请先阅读并同意商城服务协议」 |
| 登录 |
!isAgreementRequiredOnLogin() → 跳过 协议校验;已启用且未勾选 → 同上协议文案 |
1.4 状态联动
| 事件 |
biz_mall_service_agreement |
biz_member 等 |
| 保存协议/改启用 |
更新本表 |
不变 |
| 会员禁用/启用 |
— |
不变 协议配置 |
2. 数据库设计
2.1 本模块表
| 表名 |
说明 |
biz_mall_service_agreement |
商城服务协议 单例配置(买家会员注册/登录) |
2.2 字段
| 字段 |
类型 |
说明 |
| config_id |
bigint PK |
固定 1(CONFIG_ID=1) |
| agreement_title |
varchar(128) |
协议标题;非空 |
| version_label |
varchar(32) |
版本号;可空 |
| content |
longtext |
富文本 HTML;启用时非空 |
| enable_flag |
char(1) |
0 否 1 是;默认 0 |
| create_by, create_time, update_by, update_time, remark |
|
RuoYi 审计 |
无 del_flag、无 多版本行。
2.3 单例约定
| 规则 |
实现 |
| 仅一行 |
WHERE config_id = 1 |
| 首次保存 |
无记录 INSERT;有则 UPDATE |
| 禁止 DELETE |
不提供删除接口 |
2.4 DDL
脚本:sql/biz_mall_service_agreement.sql
CREATE TABLE `biz_mall_service_agreement` (
`config_id` bigint(20) NOT NULL COMMENT '固定为1',
`agreement_title` varchar(128) NOT NULL DEFAULT '' COMMENT '协议标题',
`version_label` varchar(32) DEFAULT NULL COMMENT '版本号',
`content` longtext COMMENT '富文本正文',
`enable_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0否1是',
`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 (`config_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商城服务协议配置';
INSERT INTO `biz_mall_service_agreement` (
`config_id`, `agreement_title`, `version_label`, `content`, `enable_flag`, `create_time`
)
SELECT 1, '农资商城用户服务协议', 'v1.0', '', '0', NOW()
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `biz_mall_service_agreement` WHERE `config_id` = 1);
2.5 字典
| dict_type |
用途 |
| sys_yes_no |
enable_flag 平台页展示 |
3. 平台端接口设计
基路径: /agri/mallServiceAgreement
权限: agri:mallServiceAgreement:query | edit
3.1 接口一览
| 方法 |
路径 |
权限 |
说明 |
| GET |
/ |
query |
获取当前配置 |
| PUT |
/ |
edit |
保存配置 |
3.2 获取配置 GET /
响应 data:
{
"configId": 1,
"agreementTitle": "农资商城用户服务协议",
"versionLabel": "v1.0",
"content": "<p>协议正文...</p>",
"enableFlag": "1",
"enableFlagLabel": "是",
"updateBy": "admin",
"updateTime": "2026-05-26 10:00:00"
}
| 场景 |
行为 |
| 表无记录 |
configId=1;标题/内容空;enableFlag=0 |
3.3 保存配置 PUT /
{
"agreementTitle": "农资商城用户服务协议",
"versionLabel": "v1.0",
"content": "<p>正文 HTML</p>",
"enableFlag": "1",
"remark": ""
}
| 字段 |
校验 |
| agreementTitle |
非空,≤128 |
| versionLabel |
可空,≤32 |
| enableFlag |
0 / 1 |
| content |
enableFlag=1 时非空且 isBlankHtml=false;上限 512KB(应用层) |
| enableFlag=0 |
content 可空 |
3.4 错误文案(Service)
| 场景 |
msg |
| 标题空 |
请输入协议标题 |
| 启用但内容空 |
启用状态下请填写协议内容 |
| 内容仅空白标签 |
协议内容不能为空 |
| 启用参数非法 |
是否启用参数无效 |
4. C 端接口设计
基路径: /api/member/serviceAgreement
鉴权: @Anonymous
4.1 获取服务协议 GET /api/member/serviceAgreement
| 项 |
说明 |
| 数据源 |
config_id=1 |
| 缓存 |
本期 不做 Redis |
协议已启用且正文有效(isEnabled()=true):
{
"code": 200,
"data": {
"enabled": true,
"registrationOpen": true,
"requireAgreementOnLogin": true,
"agreementTitle": "农资商城用户服务协议",
"versionLabel": "v1.0",
"content": "<p>HTML...</p>",
"checkboxLabel": "我已阅读并同意《农资商城用户服务协议》(v1.0)"
}
}
协议未启用或正文无效(SP4):
{
"code": 200,
"data": {
"enabled": false,
"registrationOpen": false,
"requireAgreementOnLogin": false,
"message": "会员注册暂未开放"
}
}
| 字段 |
含义 |
enabled |
是否对外展示 完整协议正文+勾选(与 isEnabled() 一致) |
registrationOpen |
是否允许 新会员注册(本期 = isEnabled()) |
requireAgreementOnLogin |
登录提交前是否 必须勾选(本期 = isEnabled();禁用时 false) |
message |
未开放时提示;不返回 content |
checkboxLabel 规则(与入驻协议一致):
我已阅读并同意《{agreementTitle}》
若 versionLabel 非空 → 我已阅读并同意《{agreementTitle}》({versionLabel})
4.2 开放状态 GET /api/member/serviceAgreement/status
{
"code": 200,
"data": {
"registrationOpen": true,
"requireAgreementOnLogin": true
}
}
供 C 端注册/登录页 单独拉取开关(可与 §4.1 合并,由前端只调 §4.1)。
4.3 与入驻协议路径区分
| 接口 |
用途 |
GET /api/member/serviceAgreement |
买家 服务协议(本模块) |
GET /api/merchant/entry/agreement |
商家入驻 协议 |
4.4 会员注册/登录对接(会员模块扩展)
在《会员管理技术方案》C 端接口上增加 Body 字段(实现期):
| 接口 |
新增字段 |
校验 |
POST /api/member/register |
agreementAccepted |
assertAcceptedForRegister(true) |
POST /api/member/login |
agreementAccepted |
assertAcceptedForLogin(...) — 协议未启用时 忽略 该字段 |
会员 status、密码、验证码等校验 先于或并行于 协议校验,顺序以实现为准;协议失败 不写入 biz_member。
5. Service 要点
getConfig() → 平台 VO
saveConfig(row) → validate → insert/update(1)
getForApp() → 拼 enabled / registrationOpen / requireAgreementOnLogin / checkboxLabel
isEnabled() → enable_flag=1 && !isBlankHtml(content)
isRegistrationOpen() → isEnabled()
isAgreementRequiredOnLogin() → isEnabled()
assertAcceptedForRegister(b) → !open → 会员注册暂未开放;!b → 请先阅读并同意商城服务协议
assertAcceptedForLogin(b) → !require → return;!b → 请先阅读并同意商城服务协议
5.1 富文本与安全
| 项 |
实现 |
| 存储 |
原样存 content |
| 空内容 |
AgreementContentSupport.isBlankHtml |
| 共用 |
与 biz_merchant_entry_agreement 同一 Support 类 |
6. 业务规则映射
| 编号 |
实现 |
| SP1 |
config_id=1 单表一行 |
| SP2 |
MallServiceAgreementController + 菜单权限 |
| SP3 |
assertAcceptedForRegister / assertAcceptedForLogin |
| SP4 |
requireAgreementOnLogin=false 且 assertAcceptedForLogin 直接 return |
| SP5 |
saveConfig 启用时校验 content |
| SP6 |
无历史表 |
| SP7 |
表 /api/member/serviceAgreement vs /api/merchant/entry/agreement |
| SP8 |
商户/平台后台 不引用 Facade |
| SP9 |
无发布步骤 |
| SP10 |
无级联 UPDATE 会员/订单等 |
7. 菜单权限
| 菜单(建议) |
权限标识 |
| 商城服务协议 |
agri:mallServiceAgreement:query |
| 保存 |
agri:mallServiceAgreement:edit |
上级:农资管理 → 内容管理(与入驻协议、Banner 并列)。
8. 关联方案摘要
【平台】PUT /agri/mallServiceAgreement
↓
【C 端】GET /api/member/serviceAgreement
↓
【C 端】POST /api/member/register + agreementAccepted
【C 端】POST /api/member/login + agreementAccepted(协议禁用时可不传/忽略)
↓
【并行】已是会员 → GET /api/merchant/entry/agreement → 入驻勾选(独立)
| 对比 |
入驻协议 |
本模块 |
| 表 |
biz_merchant_entry_agreement |
biz_mall_service_agreement |
| 平台路径 |
/agri/merchantEntryAgreement |
/agri/mallServiceAgreement |
| C 端路径 |
/api/merchant/entry/agreement |
/api/member/serviceAgreement |
| 禁用后老用户 |
仅拦 新入驻 |
拦 新注册;登录不要求勾选(SP4) |
| Facade |
assertAccepted 单方法 |
注册/登录 分拆 两个 assert |
9. 非本期实现
协议历史表、勾选存证、强制阅读时长、PDF、多语言、下单页二次协议、Redis 缓存、sys_config 存正文。
10. 文档索引
| 文档 |
版本 |
| 商城服务协议功能需求.md |
v1.0 |
| 商城入驻协议技术方案.md |
v1.0 |
| 会员管理技术方案.md |
v1.0.1 |
| 关联需求分析.md |
v1.2 |
文档版本:v1.0 · MySQL 5.7.39 · RuoYi v3.9.2-springboot2 · 关联《商城服务协议功能需求.md》v1.0