feat: add request.js utility, service page, and fix hospital image URLs

- Add request.js for unified API calls with loading/error handling
- Add service page (service type listing)
- Fix hospital list to load from API with proper image URL construction
- Add service types dynamic loading on index page
- Various mini program improvements (order, care, wallet pages)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-06-07 10:13:26 +08:00
parent d0d69387fb
commit 1d0a0b9b04
79 changed files with 3152 additions and 1539 deletions

View File

@@ -0,0 +1,68 @@
# Spring Security anonymous() vs permitAll() 踩坑记录
## 现象
后台管理系统登录后,访问 `业务管理 > 医院管理` 页面提示"系统接口异常",后端返回:
```json
{
"timestamp": "2026-05-25T03:08:28.944+08:00",
"status": 403,
"error": "Forbidden",
"path": "/system/hospital/list"
}
```
直接用 curl 不带 token 调用接口正常返回 200,但带上 JWT token 就返回 403。
## 根因
`SecurityConfig.java` 中 `/system/hospital/list` 被配置为 `.anonymous()`:
```java
.antMatchers("/login", ..., "/system/hospital/list", ...).anonymous()
```
**Spring Security 中两种访问控制的区别:**
| 配置 | 含义 | 匿名用户 | 已认证用户 |
|------|------|----------|------------|
| `.permitAll()` | 所有用户均可访问 | 允许 | 允许 |
| `.anonymous()` | 仅匿名用户可访问 | 允许 | **拒绝(403)** |
当后台管理员登录后,JWT 过滤器为请求注入了认证信息(不再是匿名用户),Spring Security 判定已认证用户无权访问 `.anonymous()` 资源,返回 403。
而小程序端调用该接口时不带 token(匿名),所以一直正常。
## 修复
将需要同时被"小程序(匿名)"和"后台(已认证)"访问的接口,从 `.anonymous()` 移至 `.permitAll()`:
```java
// 仅匿名用户 —— 登录、注册、验证码等
.antMatchers("/login","/appLogin","/system/msm","/smsLogin",
"/register","/captchaImage","/weixinLogin","/getUserToken",
"/system/weixin/wxPayNotify","/system/weixin/wxRefundNotify")
.anonymous()
// 所有用户均可访问 —— 被小程序和后台共同使用的数据接口
.antMatchers("/system/user/getAppIndexInfo","/system/hospital/list",
"/system/config/configKey/**","/system/nursing/published",
"/system/nursing/view/**","/system/serviceType/enabled")
.permitAll()
```
## 经验总结
1. **`.anonymous()` 是排他性的** —— 已登录用户访问会直接 403,不要用于需要同时支持前台和后台的 API
2. **`.permitAll()` vs `.anonymous()`** 选择标准:
- 登录/注册页面等"仅限未登录用户" → `.anonymous()`
- 公开数据接口(小程序+后台共用) → `.permitAll()`
3. **排查 403 时先看 SecurityConfig** —— 即使 API 直接 curl 正常,带 token 就可能因认证状态不同而变化
4. **排查思路**:逐层验证 API → 数据库 → 菜单路由 → Nginx 代理 → Security 权限配置
## 相关文件
- `rlz/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java`
- `rlz-ui/src/views/system/hospital/index.vue`
- `rlz-ui/src/utils/request.js` (前端错误拦截器)

View File

@@ -0,0 +1,19 @@
-- 服务类型表增加 icon 字段
ALTER TABLE rlz_service_type ADD COLUMN IF NOT EXISTS icon VARCHAR(255) DEFAULT '' COMMENT '服务图标URL';
-- 插入默认服务数据(如无数据)
INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order)
SELECT '全程陪诊', '2', '1', 200.00, '服务内容:排队 预约 挂号 缴费 取药 记录医嘱', '0', 1
WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '1');
INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order)
SELECT '诊前约号', '2', '2', 20.00, '服务内容:协助办理完成挂号', '0', 2
WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '2');
INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order)
SELECT '取送结果', '2', '3', 50.00, '服务内容:包括给病人或者家属取检查报告化验单等', '0', 3
WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '3');
INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order)
SELECT '代办问诊', '2', '4', 100.00, '服务内容:使用病人或者家属取检查报告化验单向医生问明情况', '0', 4
WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '4');

View File

@@ -0,0 +1,195 @@
# 瑞来健康陪诊平台 — 商业化评估报告
> 评估日期:2026-05-25
> 评估范围:后端(Spring Boot)、管理后台(Vue2)、Android App、微信小程序
---
## 一、项目现状总览
| 维度 | 状态 | 说明 |
|------|------|------|
| 技术栈 | 可维护 | Java 8 / Spring Boot 2.5.14 / Vue 2.6 / MyBatis / MySQL / 微信原生小程序 / Android Java |
| 部署 | 已跑通 | Tencent Cloud CVM + CynosDB + Docker Compose + Nginx |
| 后台管理 | 较完整 | RuoYi-Vue 框架,含医院/订单/财务/服务类型/护理资讯管理 |
| 微信小程序 | 部分完成 | 14 页面 + 3 Tab,核心流程可走,但存在假数据和空壳页面 |
| Android App | 需重构 | 从家装项目改造而来,遗留大量无关代码 |
| 安全性 | **不达标** | 支付密钥硬编码、JWT 弱密钥、手机号明文存储等多项致命问题 |
---
## 二、安全问题(P0 — 商用前必须解决)
### 2.1 致命级
| # | 问题 | 位置 | 风险 |
|---|------|------|------|
| 1 | 微信支付 RSA 私钥 + APIv3 Key 硬编码在源码中 | `WXPayConstantsAAA.java:23-50`、`PaySign.java:25-52`、`SysWeixinPayControllerNewBBBBB.java:34-42` | 源码泄露 = 资金被盗 |
| 2 | JWT 签名密钥 `abcdefghijklmnopqrstuvwxyz` | `application.yml:96` | 可暴力破解,伪造任意用户 Token |
| 3 | 短信接口无限流保护 | `MsmController.java` — 全平台零限流 | 可被刷短信,造成资损 |
### 2.2 高级
| # | 问题 | 位置 | 风险 |
|---|------|------|------|
| 4 | 手机号明文存储 + API 返回明文 + `System.out.print` 打印 | `sys_user.phonenumber`、`SysLoginController.java:76,123`、`MsmController.java:34` | 用户隐私泄露 |
| 5 | SMS 验证码明文打印到控制台 | `MsmController.java:46` | 验证码泄露 |
| 6 | 操作日志表 `sys_oper_log` 记录完整请求体(含密码、手机号) | `LogAspect.java:142-154` | 日志库成为敏感数据泄露源 |
| 7 | WeChat AppSecret 硬编码在 URL 参数中 | `SysLoginController.java:178,226` | AppSecret 泄露 |
| 8 | Android WeChat AppSecret 硬编码 | `WXEntryActivity.java:46` | AppSecret 泄露 |
### 2.3 中级
| # | 问题 | 位置 | 风险 |
|---|------|------|------|
| 9 | Druid 监控面板生产环境公开可访问(无 IP 白名单) | `SecurityConfig.java:116`、`application-docker.yml:53` | 数据库连接池信息暴露 |
| 10 | Swagger API 文档生产环境公开 | `application.yml:118`、`SecurityConfig.java:116` | API 结构暴露 |
| 11 | CSRF 全局关闭 + `X-Frame-Options` 关闭 | `SecurityConfig.java:104,120` | CSRF / 点击劫持风险 |
| 12 | ~95% 接口 `@PreAuthorize` 被注释 | 全平台 Controller | 任意认证用户可调用任意接口,无细粒度权限控制 |
---
## 三、小程序功能缺陷(P1)
| # | 页面 | 问题 | 影响 |
|---|------|------|------|
| 1 | `reforderdetail` 退款订单详情 | **全假数据**,真实 API 调用被整段注释 | 退款流程完全不可用 |
| 2 | `tuiguang/extensionInfor` 推广页 | **完全空壳**,所有生命周期函数为空 | 推广模块不存在 |
| 3 | `guanyu/guanyu` 关于/反馈页 | 表单提交调用不存在的函数 `this.payment()` | 反馈功能不可用 |
| 4 | `mechanism/mechanism` 机构页 | 机构详情跳转到不存在的 `pages/institutions/institutions` | 机构详情 404 |
| 5 | `peizhen/peizhen` + `peihu/peihu` | 医院选择器数据硬编码(不从后台接口读取) | 数据不实时 |
| 6 | `index/index` | 服务价格硬编码(200/20/100/50) | 调价需改代码发版 |
| 7 | `myshoucang/myshoucang_index` | 收藏列表全假数据 | 收藏功能不可用 |
| 8 | `my/my` | 砍价功能写死"暂无开放" | 营销功能缺失 |
| 9 | `orderdetail/orderdetail` | 客服电话硬编码 `18133233292` | 换号需改代码 |
| 10 | 全页面 | 错误处理均为 `.catch(() => {})` 静默吞错 | 用户无错误反馈 |
---
## 四、Android App 问题(P2)
| # | 问题 |
|---|------|
| 1 | 含大量原家装项目遗留代码(`DesignActivity`、`StyleActivity`、`Douyin` 接口等) |
| 2 | 两套并行网络层:Retrofit(`MyApi.java`)+ OkHttp(`NetApi.java`),架构混乱 |
| 3 | `API.java` 引用已废弃的 Bmob 云后端,含硬编码测试密钥 |
| 4 | 引用 `/device/AppCustomer/*`、`/device/AppDouyin/*` 等可能不存在的接口 |
| 5 | 包名和类名仍保留原产品命名(`kehu`、`find`、`goddess` 等) |
---
## 五、基础设施缺失(P3)
| # | 问题 |
|---|------|
| 1 | 无自动化数据库备份策略 |
| 2 | 无日志聚合 / 监控告警系统 |
| 3 | 自定义建表 SQL(`rlz_order`、`sys_hospital` 等)未入仓 |
| 4 | 无 Staging 测试环境 |
| 5 | CI/CD 仅覆盖后端 Docker 构建,小程序和 Android 无自动化 |
| 6 | 数据库 `rlz_order` 表使用 20 个通用字段 `yuliu1~20`,Schema 不透明 |
---
## 六、数据库问题
| # | 表 | 问题 |
|---|------|------|
| 1 | `rlz_order` | 20 个 `yuliu1`~`yuliu20` 通用 varchar 列,无文档说明 |
| 2 | `rlz_order` / `sys_hospital` / `rlz_service_type` 等 | 建表 DDL 未入仓(仅 `ry_20220625.sql` + `nursing_article.sql` 在 repo 中) |
| 3 | `order_view` / `hospital_user_view` / `user_hospital_view` | 视图 DDL 未入仓 |
| 4 | `sys_user.phonenumber` | 手机号明文存储,无加密 |
| 5 | `sys_oper_log` | 存储完整请求/响应 JSON,含密码和手机号 |
---
## 七、开发工作量估算
### 方案 A:最小可用方案(安全整改 + 关键缺陷修复)— 3~4 个月
| 阶段 | 工作内容 | 人月 |
|------|----------|------|
| 安全整改 | 密钥迁移环境变量、JWT 加固、全接口限流、日志脱敏、Druid/Swagger 生产关闭 | 1.5 |
| 小程序修复 | 退款页/推广页/关于页/机构页修复、硬编码改为接口调用、错误状态完善 | 1.5 |
| 基础设施 | 备份策略、基础监控告警 | 0.5 |
| 全流程测试 | 回归测试 | 0.5 |
| **合计** | | **4 人月** |
> 2 人团队 × 2 个月,费用约 **8~15 万 RMB**
---
### 方案 B:完整商用方案 — 6~8 个月
| 阶段 | 工作内容 | 人月 |
|------|----------|------|
| 安全整改 | 同上 + 敏感数据加密存储 + 渗透测试 + 等保咨询 | 2 |
| 小程序完整修复 | 全部页面真实接口、通知推送、图文混排优化 | 2 |
| 功能补全 | 营销模块(砍价/拼团)、收藏、反馈系统、CMS 完善 | 1.5 |
| Android 重构 | 移除遗留代码、统一网络层、端到端测试 | 2 |
| 基础设施 | CI/CD 全流程、监控告警、日志平台、多环境管理、灾备方案 | 1 |
| 合规 + 文档 | 隐私政策、用户协议、数据删除/导出、运维文档 | 1 |
| 测试 | 全平台回归 + 性能压测 | 1 |
| **合计** | | **10.5 人月** |
> 2~3 人团队 × 4~6 个月,费用约 **25~50 万 RMB**
---
## 八、运维费用估算(月度)
| 项目 | 规格 | 费用(元/月) |
|------|------|---------------|
| 腾讯云 CVM | 4C8G | 300 ~ 600 |
| CynosDB MySQL | 按量 | 500 ~ 2,000 |
| 带宽 + CDN | 按量 | 200 ~ 800 |
| 域名 | ruilaizipj.com | ~10 |
| 微信小程序认证 | 年付 300 | 25 |
| 微信商户手续费 | 0.6% 交易额 | 按交易量 |
| 短信服务 | 验证码 + 通知 | 100 ~ 500 |
| SSL 证书 | Let's Encrypt | 0 |
| 云监控基础版 | - | 200 ~ 500 |
| 对象存储 COS | 图片/文件 | 50 ~ 300 |
| **月度合计** | | **约 1,500 ~ 5,000** |
---
## 九、数据合规要求(中国法律)
陪诊平台属于医疗健康相关服务,涉及用户敏感个人信息,需关注:
| 法规 | 要求 | 当前状态 |
|------|------|----------|
| 《个人信息保护法》 | 收集用户信息需明确告知 + 获取同意 | 小程序无隐私弹窗 |
| 《个人信息保护法》 | 敏感个人信息(医疗健康、行踪轨迹)需单独同意 | 未实现 |
| 《个人信息保护法》 | 用户有权查阅/更正/删除/导出其个人信息 | 未实现 |
| 《网络安全法》 | 网络运营者应采取技术措施保障信息安全 | 多项安全问题未解决 |
| 《数据安全法》 | 重要数据处理者需定期风险评估 | 无 |
| 等保 2.0 | 医疗健康类系统至少等保二级 | 未做 |
| 微信小程序运营规范 | 涉及医疗健康需提供相关资质 | 需确认 |
---
## 十、建议实施路径
```
第 1 个月 ─── 安全整改(密钥管理 + 限流 + 日志脱敏 + 生产配置加固)
第 2 个月 ─── 小程序缺陷修复(假数据 → 真接口 + 空壳页面补全)
第 3 个月 ─── 基础设施(备份 + 监控 + CI/CD)+ 全流程测试
第 4 个月 ─── 上线试运营,收集用户反馈
第 5-6 个月 ─ 功能增强(营销模块、通知推送、数据看板)
第 7-8 个月 ─ Android 重构 + 合规材料准备
```
---
## 十一、总结
| | 方案 A(最小可用) | 方案 B(完整商用) |
|------|------|------|
| 开发周期 | 3 ~ 4 个月 | 6 ~ 8 个月 |
| 开发费用 | 8 ~ 15 万 | 25 ~ 50 万 |
| 月度运维 | 1,500 ~ 5,000 元 | 1,500 ~ 5,000 元 |
| 交付水平 | 核心业务流程可运转 | 具备完整 SaaS 商业化能力 |
**最紧迫事项**:安全漏洞修复(支付密钥、JWT、限流)必须在任何商用推广前完成。源码一旦泄露,攻击者可直接操作微信支付。

View File

@@ -0,0 +1,201 @@
# 瑞来健康 — 域名与 Nginx 配置方案
> 日期:2026-05-24
> 服务器:101.43.95.130
> 现状:与"生长激素预测模型"项目共用服务器和域名 ruilaizipj.com
---
## 一、现状分析
### 1.1 域名占用
```
ruilaizipj.com / www.ruilaizipj.com (80/443)
└── host nginx (/etc/nginx/conf.d/default.conf)
├── / → /www/wwwroot/code/public (ThinkPHP - 生长激素项目)
└── /adminghd/ → ThinkPHP 后台管理
```
### 1.2 RLZ 服务(Docker 内部)
| 服务 | 容器 | 内部端口 | 外部访问 |
|------|------|---------|---------|
| 后端 API | rlz-backend | 8039 | `http://101.43.95.130:8039` |
| 管理后台 | rlz-ui-server | 80 | `http://101.43.95.130:8050` |
| Redis | rlz-redis | 6379 | 127.0.0.1:6379 |
### 1.3 微信小程序约束
微信小程序已在微信后台登记 `https://ruilaizipj.com` 作为合法 API 域名,不能更改。
### 1.4 RLZ API 路径前缀
- 后台管理:`/dev-api/`(由前端 `VUE_APP_BASE_API` 控制)
- 微信小程序/Android:`/prod-api/`(直接请求后端路径)
---
## 二、方案设计
### 2.1 核心思路
**API 走主域名路径代理,管理后台走子域名。**
- 小程序 API → 主域名路径 `/prod-api/*`(不改微信登记,与 ThinkPHP 无冲突)
- 管理后台 → 子域名 `admin.ruilaizipj.com`(SPA 路由清爽,独立 server 块)
### 2.2 目标架构
```
101.43.95.130 (host nginx 80/443)
│
┌─────────────────────────┼─────────────────────────┐
│ │ │
ruilaizipj.com ruilaizipj.com admin.ruilaizipj.com
(生长激素) (RLZ API) (RLZ 管理后台)
│ │ │
/ → code/public /prod-api/* / → rlz-ui-server
/adminghd/ /dev-api/* (127.0.0.1:8050)
│ (127.0.0.1:8039)
▼ │
ThinkPHP 5.0 ▼
(PHP-FPM) rlz-backend
(Spring Boot)
```
### 2.3 SSL 证书
当前 Let's Encrypt 证书覆盖 `ruilaizipj.com` + `www.ruilaizipj.com`。
需要新增 `admin.ruilaizipj.com` 的 SAN 覆盖。两种方式:
- **方式 A**:宝塔面板 → 网站 → SSL → 重新签发(添加子域名)
- **方式 B**:手动 certbot 扩展
推荐方式 A,宝塔自动续期。
---
## 三、实施步骤
### Step 1:DNS 添加子域名
在域名 DNS 管理后台添加记录:
```
admin.ruilaizipj.com → A → 101.43.95.130
```
### Step 2:扩展 SSL 证书
通过宝塔面板重新签发证书,添加 `admin.ruilaizipj.com` 到域名列表。
### Step 3:修改 host nginx 配置
编辑 `/etc/nginx/conf.d/default.conf`,在现有 server 块中新增:
```nginx
# === 现有 ThinkPHP 配置保持不变 ===
# 新增:RLZ 后端 API 代理
location /prod-api/ {
proxy_pass http://127.0.0.1:8039/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_read_timeout 60s;
}
location /dev-api/ {
proxy_pass http://127.0.0.1:8039/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_read_timeout 60s;
}
```
新建 `/etc/nginx/conf.d/rlz-admin.conf`:
```nginx
server {
listen 80;
server_name admin.ruilaizipj.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name admin.ruilaizipj.com;
ssl_certificate /www/server/panel/vhost/cert/101.43.95.130/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/101.43.95.130/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5;
ssl_prefer_server_ciphers on;
charset utf-8;
location / {
proxy_pass http://127.0.0.1:8050;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_read_timeout 60s;
}
location /prod-api/ {
proxy_pass http://127.0.0.1:8039/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### Step 4:验证
```bash
# 重载 nginx
nginx -t && nginx -s reload
# 测试 API
curl -s https://ruilaizipj.com/prod-api/captchaImage
# 测试管理后台
curl -s -o /dev/null -w "%{http_code}" https://admin.ruilaizipj.com/
```
### Step 5:更新前端配置
- `rlz-ui/.env.production`:`VUE_APP_BASE_API` 改为 `/prod-api`
- 微信小程序 `config.js`:`baseUrl` 改为 `https://ruilaizipj.com/prod-api`
- Android `BuildConfig`:Release 的 `API_BASE_URL` 改为 `https://ruilaizipj.com/prod-api`
---
## 四、访问地址总览(实施后)
| 端 | 地址 |
|------|------|
| 管理后台 | `https://admin.ruilaizipj.com` |
| 后端 API | `https://ruilaizipj.com/prod-api/` |
| 微信小程序 API | `https://ruilaizipj.com/prod-api/` |
| 生长激素项目(不变) | `https://www.ruilaizipj.com` |
| 生长激素后台(不变) | `https://www.ruilaizipj.com/adminghd/` |
---
## 五、注意事项
1. **不要改 `server_name` 的 ThinkPHP 部分**,否则影响生长激素项目正常运行
2. **nginx 修改前备份**:`cp /etc/nginx/conf.d/default.conf /tmp/default.conf.bak`
3. **宝塔可能覆盖 nginx 配置**,修改后注意观察
4. **API 路径冲突检查**:确认 ThinkPHP 项目没有 `/prod-api/` 或 `/dev-api/` 路由
5. **RLZ 前端的 `VUE_APP_BASE_API` 需要同步更新**为 `/prod-api` 并重新构建部署

View File

@@ -0,0 +1,235 @@
# 服务项目管理动态化方案
> 日期:2026-05-25
> 目标:将小程序端硬编码的服务项目改为后台动态配置,支持上架/下架/调价/排序
---
## 一、现状分析
### 1.1 现有基础设施(已存在,可直接使用)
| 组件 | 状态 | 位置 |
|------|------|------|
| 数据库表 `rlz_service_type` | 已有 | MySQL |
| 实体类 `RlzServiceType` | 已有 | `ruoyi-system/.../domain/RlzServiceType.java` |
| Mapper / Service / Controller | 已有 | CRUD + 分页 + Excel 导出 |
| 价格变更历史 `rlz_price_history` | 已有 | 调价自动记录操作人/时间/原因 |
| 管理后台 Vue 页面 | 已有 | `rlz-ui/src/views/system/serviceType/index.vue` |
### 1.2 现有字段
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 主键 |
| name | String | 服务名称 |
| category | String | 类别: 1=陪护, 2=陪诊 |
| typeCode | String | 类型编码 |
| price | BigDecimal | 价格 |
| description | String | 服务内容描述 |
| status | String | 0=启用, 1=停用 |
| sortOrder | Integer | 排序(越小越靠前) |
| createBy/createTime | - | 创建信息 |
| updateBy/updateTime | - | 更新信息 |
| remark | String | 备注 |
### 1.3 当前问题
小程序端服务项目全部硬编码:
- `pages/index/index.js:13-41` — 4 个服务项,价格写死(200/20/100/50)
- `pages/peizhen/peizhen.js:26-56` — 陪诊服务列表写死
- `pages/peihu/peihu.js` — 陪护服务列表写死
- 图标通过 `../image/home/{{item.icon}}.png` 本地引用,无法动态配置
---
## 二、改造方案
### 2.1 数据库变更
`rlz_service_type` 表新增 `icon` 字段:
```sql
ALTER TABLE rlz_service_type ADD COLUMN icon VARCHAR(255) DEFAULT '' COMMENT '服务图标URL';
```
### 2.2 后端变更
#### a) 实体新增 icon 字段
`RlzServiceType.java` 增加 `private String icon;` 及 getter/setter。
#### b) Mapper XML 更新
`RlzServiceTypeMapper.xml` 的 resultMap / SQL / insert / update 中增加 icon 字段。
#### c) 新增小程序公开接口
```java
// 无需登录,返回所有启用状态的服务
@GetMapping("/enabled")
public AjaxResult enabled(RlzServiceType rlzServiceType)
{
rlzServiceType.setStatus("0"); // 只查启用的
List<RlzServiceType> list = rlzServiceTypeService
.selectRlzServiceTypeList(rlzServiceType);
return AjaxResult.success(list);
}
```
#### d) SecurityConfig 白名单
```java
.antMatchers("/system/serviceType/enabled").permitAll()
```
### 2.3 管理后台变更
`rlz-ui/src/views/system/serviceType/index.vue`:
- 表格增加"图标"列(显示缩略图)
- 新增/编辑表单增加图标上传组件
- 增加"上架/下架"快捷操作按钮
### 2.4 小程序变更
#### index.js(首页 4 个服务卡片)
```
修改前:data.itemView = [{ icon: "全程陪诊", title: "全程陪诊", price: "200", ... }]
修改后:onShow 调用 /system/serviceType/enabled?category=2 获取陪诊服务列表
```
#### peizhen.js(陪诊页)
```
修改前:services = [{ name: "全程陪诊", price: "200" }, ...]
修改后:调用 /system/serviceType/enabled?category=2 动态加载
```
#### peihu.js(陪护页)
```
修改前:services 硬编码
修改后:调用 /system/serviceType/enabled?category=1 动态加载
```
#### WXML 图标引用
```xml
<!-- 修改前(本地图片) -->
<image src="../image/home/{{item.icon}}.png" mode="aspectFit"></image>
<!-- 修改后(远程URL) -->
<image src="{{item.icon}}" mode="aspectFit"></image>
```
---
## 三、数据流
```
┌─────────────────────────────────────────────────────────┐
│ 管理后台 (Vue Admin) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 服务类型管理 │ │
│ │ [新增] [编辑] [删除] [调价] [上架/下架] │ │
│ │ ┌──────┬──────┬──────┬──────┬──────┐ │ │
│ │ │ 图标 │ 名称 │ 类别 │ 价格 │ 状态 │ │ │
│ │ ├──────┼──────┼──────┼──────┼──────┤ │ │
│ │ │ 🏥 │全程陪诊│陪诊 │ 200 │ 启用 │ │ │
│ │ │ 📋 │诊前约号│陪诊 │ 20 │ 启用 │ │ │
│ │ │ 💊 │代办买药│陪诊 │ 80 │ 停用 │ │ │
│ │ └──────┴──────┴──────┴──────┴──────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │ POST/PUT/DELETE │
│ ▼ (需登录 + 权限) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 后端 Controller: /system/serviceType/* │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 微信小程序 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ /system/serviceType/enabled?category=2 │ │
│ │ [公开接口,无需登录] │ │
│ └─────────────────────────────────────────────────┘ │
│ │ GET │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 返回: [{ name:"全程陪诊", price:200, icon:"...", │ │
│ │ description:"排队预约挂号...", status:"0" │ │
│ │ sortOrder:1 }, ...] │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 首页服务卡片区域 动态渲染 │ │
│ │ [全程陪诊] [诊前约号] [代办买药] [取送结果] │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
---
## 四、实现步骤
| 步骤 | 内容 | 涉及文件 |
|------|------|----------|
| 1 | 数据库加 `icon` 字段 | DDL |
| 2 | 实体 + Mapper 更新 | `RlzServiceType.java`, `RlzServiceTypeMapper.xml` |
| 3 | Controller 增加 `/enabled` 公开接口 | `RlzServiceTypeController.java` |
| 4 | SecurityConfig 加白名单 | `SecurityConfig.java` |
| 5 | 管理后台增加图标字段 | `serviceType/index.vue` |
| 6 | 小程序首页改为 API 加载 | `index.js`, `index.wxml` |
| 7 | 小程序陪诊页改为 API 加载 | `peizhen.js`, `peizhen.wxml` |
| 8 | 小程序陪护页改为 API 加载 | `peihu.js`, `peihu.wxml` |
| 9 | 部署后端 + 测试 | Docker 构建部署 |
---
## 五、接口设计
### GET /system/serviceType/enabled
无需登录,根据 category 返回已启用的服务列表。
**请求参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| category | String | 否 | 1=陪护, 2=陪诊,不传则返回全部 |
**响应示例:**
```json
{
"code": 200,
"data": [
{
"id": 1,
"name": "全程陪诊",
"category": "2",
"typeCode": "1",
"price": 200.00,
"description": "排队预约挂号缴费取药记录医嘱",
"icon": "https://ruilaizipj.com/profile/upload/icon_peizhen.png",
"status": "0",
"sortOrder": 1
},
{
"id": 2,
"name": "诊前约号",
"category": "2",
"typeCode": "2",
"price": 20.00,
"description": "协助办理完成挂号",
"icon": "https://ruilaizipj.com/profile/upload/icon_yuehao.png",
"status": "0",
"sortOrder": 2
}
]
}
```

View File

@@ -0,0 +1,256 @@
# 陪诊员服务费分账方案(A+B 结合)
## 一、需求概述
订单完成后,将服务费按比例分配给平台和陪诊员,实现自动计算 + 人工审核确认的结算流程。
## 二、业务流程
```
订单完成(status=3,待支付)
→ 患者微信支付
→ 支付成功(status=4,已完成)
→ 系统自动计算分账金额(根据配置的抽成比例)
→ 写入结算金额到订单字段
→ 管理员后台审核确认
→ 陪诊员余额增加(yue)、累计收益更新(leijijine)
→ 生成交易记录(jiaoyi_detail)
→ 订单标记为已结算(status=8)
```
## 三、数据库改动
### 3.1 新增平台配置表 `rlz_platform_config`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | bigint | PK |
| `config_key` | varchar(50) | 配置键 |
| `config_value` | varchar(255) | 配置值 |
| `description` | varchar(200) | 描述 |
| `create_time` | datetime | 创建时间 |
| `update_time` | datetime | 更新时间 |
初始化数据:
```sql
INSERT INTO rlz_platform_config (config_key, config_value, description) VALUES
('commission_rate', '20', '平台抽成比例(%)'),
('auto_settle', 'false', '是否自动结算(true/false)');
```
### 3.2 现有表字段利用
`rlz_order` 表已有字段(无需改动):
- `jiesuan_money` — 结算金额(订单总金额)
- `kouchu_money` — 平台抽成金额
- `fanxian_money` — 陪诊员所得金额
- `fanxian_tax` — 抽成比例(%)
- `is_zhifu` — 打款状态(0=未打款, 1=已打款)
`sys_user` 表已有字段(无需改动):
- `yue` — 余额(陪诊员可用余额)
- `leijijine` — 累计收益
`jiaoyi_detail` 表已有字段(无需改动):
- `type` — 1=收入, 2=支出
- `jine` — 金额
- `name` — 交易名称
- `yue` — 交易后余额
- `user_id` — 用户ID
## 四、后端实现
### 4.1 新增类文件
| 类 | 路径 | 职责 |
|----|------|------|
| `RlzPlatformConfig.java` | `domain/` | 平台配置实体 |
| `RlzPlatformConfigMapper.java` | `mapper/` | 配置 Mapper |
| `RlzPlatformConfigMapper.xml` | `mapper/system/` | 配置 SQL |
| `IRlzPlatformConfigService.java` | `service/` | 配置服务接口 |
| `RlzPlatformConfigServiceImpl.java` | `service/impl/` | 配置服务实现 |
| `RlzSettlementService.java` | `service/` | 结算服务(核心逻辑) |
| `RlzSettlementController.java` | `controller/` | 结算接口 |
| `RlzTransactionController.java` | `controller/` | 交易记录查询接口 |
### 4.2 核心服务 RlzSettlementService
```java
@Service
public class RlzSettlementService {
// 获取抽成比例(默认20%)
public BigDecimal getCommissionRate() {
String rate = platformConfigService.getConfigValue("commission_rate", "20");
return new BigDecimal(rate);
}
// 订单支付成功后自动计算分账
public void calculateOrderFee(RlzOrder order) {
BigDecimal totalAmount = new BigDecimal(order.getYuguMoney());
BigDecimal rate = getCommissionRate(); // 20%
BigDecimal kouchu = totalAmount.multiply(rate).divide(new BigDecimal("100")); // 平台抽成
BigDecimal fanxian = totalAmount.subtract(kouchu); // 陪诊员所得
order.setJiesuanMoney(order.getYuguMoney()); // 结算金额=订单金额
order.setKouchuMoney(kouchu.toString()); // 平台抽成
order.setFanxianMoney(fanxian.toString()); // 陪诊员所得
order.setFanxianTax(rate.toString()); // 抽成比例
order.setIsZhifu("0"); // 未打款
rlzOrderService.updateOrder(order);
}
// 管理员确认结算(打款给陪诊员)
@Transactional
public void settleOrder(Long orderId) {
RlzOrder order = rlzOrderService.selectRlzOrderById(orderId);
if (!"4".equals(order.getStatus())) {
throw new BusinessException("订单未支付,无法结算");
}
if ("1".equals(order.getIsZhifu())) {
throw new BusinessException("已打款,请勿重复操作");
}
Long caregiverId = order.getBId(); // 陪诊员ID
BigDecimal fanxianMoney = new BigDecimal(order.getFanxianMoney());
// 1. 更新订单打款状态
order.setIsZhifu("1");
order.setStatus("8"); // 已结算
rlzOrderService.updateOrder(order);
// 2. 更新陪诊员余额和累计收益
SysUser caregiver = sysUserService.selectUserById(caregiverId);
BigDecimal oldBalance = new BigDecimal(caregiver.getYue() != null ? caregiver.getYue() : "0");
BigDecimal newBalance = oldBalance.add(fanxianMoney);
caregiver.setYue(newBalance.toString());
BigDecimal oldTotal = new BigDecimal(caregiver.getLeijijine() != null ? caregiver.getLeijijine() : "0");
caregiver.setLeijijine(oldTotal.add(fanxianMoney).toString());
sysUserService.updateUser(caregiver);
// 3. 生成交易记录
insertTransaction(caregiverId, fanxianMoney, newBalance, order.getYuliu1());
}
private void insertTransaction(Long userId, BigDecimal jine, BigDecimal newBalance, String orderNo) {
JiaoyiDetail detail = new JiaoyiDetail();
detail.setUserId(userId);
detail.setType("1"); // 收入
detail.setJine(jine.toString());
detail.setYue(newBalance.toString());
detail.setName("订单结算 - " + orderNo);
jiaoyiDetailService.insertJiaoyiDetail(detail);
}
}
```
### 4.3 结算接口 RlzSettlementController
```java
@RestController
@RequestMapping("/system/settlement")
public class RlzSettlementController extends BaseController {
@Autowired
private RlzSettlementService settlementService;
// 支付回调中自动触发计算(在 WxPayNotify 中调用)
// 此处提供手动重新计算的接口
// 管理员确认结算
@PostMapping("/settle/{orderId}")
public AjaxResult settle(@PathVariable Long orderId) {
settlementService.settleOrder(orderId);
return AjaxResult.success("结算完成");
}
// 批量结算
@PostMapping("/batchSettle")
public AjaxResult batchSettle(@RequestBody Long[] orderIds) {
for (Long id : orderIds) {
settlementService.settleOrder(id);
}
return AjaxResult.success("批量结算完成");
}
// 查询待结算订单列表(status=4 已支付但未打款)
@GetMapping("/pending")
public TableDataInfo pending() {
// 查询 status=4 AND is_zhifu='0' 的订单
...
}
}
```
### 4.4 支付回调中自动触发计算
在 `WxPayNotify` 处理中增加一行调用:
```java
// 支付成功,更新 status=4
order.setStatus("4");
// ... 存储微信交易号 ...
rlzOrderService.updateOrder(order);
// === 新增:自动计算分账金额 ===
settlementService.calculateOrderFee(order);
```
## 五、后台管理面板改动
### 5.1 新增菜单
| 父菜单 | 菜单名称 | 路径 | 组件 |
|--------|----------|------|------|
| 财务管理(3000) | 结算管理 | `settlement` | `finance/settlement/index` |
| 财务管理(3000) | 交易明细 | `transaction` | `finance/transaction/index` |
| 财务管理(3000) | 平台配置 | `platformConfig` | `finance/platformConfig/index` |
### 5.2 结算管理页面 `finance/settlement/index.vue`
功能:
- 列表展示已支付待结算订单
- 搜索:订单号、陪诊员姓名、日期范围
- 列:订单号、陪诊员、订单金额、平台抽成、应发金额、支付时间、状态
- 操作:单个结算 / 批量结算
- 点击结算弹出确认框,显示分账明细预览
### 5.3 平台配置页面 `finance/platformConfig/index.vue`
功能:
- 平台抽成比例设置(默认20%)
- 自动结算开关(暂不启用)
## 六、SecurityConfig 更新
```java
// 新增匿名访问(若小程序也需要查询余额/交易记录)
// 结算接口需要认证(仅后台管理员操作)
.antMatchers("/system/settlement/**", "/system/transaction/**").authenticated()
```
## 七、实施步骤
| 步骤 | 内容 | 预估 |
|------|------|------|
| 1 | 创建 `rlz_platform_config` 表 + 初始化数据 | 10min |
| 2 | 创建配置管理后端代码(CRUD) | 20min |
| 3 | 实现 `RlzSettlementService` 核心分账逻辑 | 30min |
| 4 | 在支付回调中集成自动计算 | 10min |
| 5 | 创建结算管理后台页面 | 30min |
| 6 | 创建平台配置后台页面 | 20min |
| 7 | 部署、测试完整流程 | 20min |
## 八、分账示例
假设平台抽成比例 20%,订单金额 500 元:
| 项目 | 金额 | 计算 |
|------|------|------|
| 订单金额 | 500.00 | 患者支付 |
| 平台抽成(20%) | 100.00 | 500 × 20% |
| 陪诊员所得 | 400.00 | 500 × 80% |
| 陪诊员余额变化 | +400.00 | yue += 400 |
管理员可在后台"平台配置"中调整抽成比例,新比例对后续订单生效。