feat(v1.1.0): 支付改由 epay 插件承载、插件纳入版本库,补齐媒体上传/LBS/互动/优惠券/后台看板,并修复余额充值资损

## 一、支付:改由已安装的 epay 插件承载(不再自研签名)
- 下单:\addons\epay\library\Service::submitOrder($amount 元, $orderNo, 'wechat', $body, $notifyUrl, null, 'miniapp', $openid)
- 验签:Service::checkNotify('wechat')(内部 $pay->verify());应答:$pay->success()(v2 = XML)
- 业务编排仍在 application/api/controller/Pay.php,不改插件源码:
  POST /pay/prepay(取调起参数,下发前剔除 appId);POST /pay/notify(公开路由:插件验签 → 金额核对 → 幂等分派)
- 配置真源 = addons/epay/config.php(后台「插件管理 → 微信支付宝整合插件 → 配置」);
  fc_config 的 wx.mchId / wx.apiKey / wx.payNotifyUrl 废弃;后台「系统设置」支付表单改只读并跳插件配置页
- 插件 API 保持 version=v2(PHP 7.4 可跑);certs/* 为 0 字节占位,不含任何真实密钥
- 修复 BUG-015:Pay::notify() 与基类站内信同名且签名不兼容导致 /pay/* 全 500,动作改名 notifyCallback()

## 二、插件纳入 Git 版本管理
- .gitignore 移除 /addons/* 与 /public/assets/addons/*(保留 .gitkeep 占位)
- 入库 addons/epay、addons/wechat、public/assets/addons/*、addons/.htaccess(deny from all)
- config.php 入库为空白模板;服务器请在填好密钥后执行
  git update-index --skip-worktree addons/epay/config.php,避免真实密钥回传仓库

## 三、安全修复:余额充值资损(高危,已实测复现)
- 现象:POST /order/recharge {payType:'balance'} 只加余额不扣款(余额 134.50 → 1022.50),等于凭空增发余额,
  可再去买会员 / 付费发布 / 解锁联系方式
- 修复:/order/recharge 仅接受 payType=wxpay,balance → 「余额充值仅支持微信支付」,其它 → 「支付方式不支持」;
  订单恒为 status=pending、pay_type=wxpay、paytime=null;入账只发生在 /pay/notify 回调(Order::creditRecharge)
  或后台「会员管理 → 余额充值」(fc/userext/recharge)
- 小程序充值页同步为「仅微信支付」+ 防重复提交 + onShow 刷新资产;mock / 用例 / 文档同步

## 四、其他 v1.1.0 能力
- 媒体上传:POST /post/upload(图片 ≤10MB / 视频 ≤50MB,真落盘),发布页 uni.uploadFile 真上传
- LBS:fc_post.lat/lng、首页 sort=nearby(距离升序、无坐标排最后)、卡片 distance、详情页地图
- 互动:举报 POST /report(原因字典)、收藏 / 关注(fc_favorite / fc_follow、/favorite/*、/follow/toggle)
- 交易:余额充值 POST /order/recharge、优惠券 /coupon/list|receive(fc_coupon / fc_user_coupon、我的资产券)
- 发布计费:会员发布折扣(discountRate/discountText)+ 免费发布条数(freeQuota*)
- 后台新增:数据总览 fc/dashboard、系统设置 fc/setting、优惠券管理 fc/coupon(含菜单与权限节点)

## 五、本批审核修复
- 详情页:游客不再请求登录态接口(避免 401 被清 token 踢去登录);微信付费查看改为支付后轮询解锁,
  不再读取 wxpay 分支未返回的 contact;加防重复提交
- 发布页:freeLeftToday → freeQuotaLeft / freeQuotaCycle / freeUnlimited(对齐后端出参);
  上传图预览拼后端域名(新增 format.js#assetUrl,后端返回相对路径)
- 收藏空态「去首页」:switchTab → reLaunch(项目用自绘 fc-tabbar,无 tabBar,switchTab 必然失败)
- 会员券 days=0 显示「永久有效」;manifest.json 补 requiredPrivateInfos(getLocation / chooseLocation)
- 后端:publishConfig 补 discountRate/discountText 与 memberPostDiscountText();优惠券总开关 coupon.enabled
  真生效(关闭后 list/receive 拒绝);后台 fc/Coupon 的 add/edit 补 strip_tags 过滤(防存储型 XSS)、
  补 searchFields='name',列表 name 列再做一次 HTML 转义
- FcInstall:补清理后台菜单缓存 __menu__(否则新菜单要等缓存过期才显示),并同步 README 说明
- 后台语言包 fc/setting.php 按「支付只读 + 跳插件配置」的新页面重写
- 文档同步:uniapp/docs/{01,03,04}、uniapp/README.md、docs/05-后端实现规范.md、后台 README 上线步骤

## 六、校验结果(全部通过)
- 后端端到端:tools/verify-api.mjs 226/226(含新增「拒绝余额充值(资损防护)」用例)
- 小程序:uniapp/tests/run.mjs 167/167;validate.mjs 0 错 0 警;check-scss 19/19
- 后台:check-admin-i18n 0 错 0 警;check-theme 0 错 0 警;9 个页面 HTTP 200 全中文
- php -l 改动/新增 PHP 文件 0 语法错误;探针复测:余额不再增长、epay 插件下单/验签链路通
这个提交包含在:
2026-10-19 10:03:13 +08:00
父节点 592c5af931
当前提交 63fdfe87d7
共有 1086 个文件被更改,包括 68849 次插入 和 276 次删除

查看文件

@@ -11,8 +11,8 @@
| --- | --- | --- |
| **① 功能模块清单(PM)** | [`docs/01-产品功能模块清单.md`](docs/01-产品功能模块清单.md) | 用户端 + 后台管理端模块清单、优先级、业务规则、接口映射 |
| **② UI 设计规范(UI)** | [`docs/02-UI设计规范.md`](docs/02-UI设计规范.md) | 春绿设计令牌、排版、组件、动效规范 |
| **③ 接口文档(前后端)** | [`docs/03-接口文档.md`](docs/03-接口文档.md) | 52 个接口字段级契约 + Mock 兜底机制 + 联调清单 |
| **④ 全链路测试报告(QA)** | [`docs/04-全链路测试用例与报告.md`](docs/04-全链路测试用例与报告.md) | 139 条用例、15 步 E2E、14 个缺陷记录与修复 |
| **③ 接口文档(前后端)** | [`docs/03-接口文档.md`](docs/03-接口文档.md) | 73 个接口(用户端 41 + 后台 32)字段级契约 + Mock 兜底机制 + 联调清单 |
| **④ 全链路测试报告(QA)** | [`docs/04-全链路测试用例与报告.md`](docs/04-全链路测试用例与报告.md) | 167 条用例、15 步 E2E、15 个缺陷记录与修复 |
## 二、目录结构
@@ -20,16 +20,18 @@
fenleixinxi/
├── App.vue # 全局主题样式(春绿 CSS 变量 + 原子类)
├── uni.scss # 设计令牌($fc-* 自动注入所有 scss)
├── pages.json # 13 个页面路由
├── pages.json # 15 个页面路由
├── pages/ # 用户端页面
│ ├── index/index.vue # 首页:定位/搜索/轮播/分类/会员条/筛选/信息流
│ ├── search/index.vue # 智能搜索:历史 + 热门
│ ├── post/list.vue # 分类列表:多维筛选 + 分页
│ ├── post/create.vue # 发布:表单/发布方式(免费·单条·套餐)/草稿箱
│ ├── post/create.vue # 发布:表单/真上传/定位/发布方式(免费·单条·套餐)/草稿箱
│ ├── post/mine.vue # 我的发布:审核状态筛选 + 驳回原因 + 删除(v1.0.7 新增)
│ ├── detail/detail.vue # 详情:图文/地图/付费查看/评价/海报
│ ├── member/index.vue # 会员:权益/开通/资产
│ ├── detail/detail.vue # 详情:图文/真实坐标地图/付费查看/评价/海报/收藏关注/举报
│ ├── member/index.vue # 会员:权益/开通/资产/优惠券领取
│ ├── member/orders.vue # 订单管理
│ ├── member/recharge.vue # 余额充值(v1.1.0 新增)
│ ├── favorite/index.vue # 我的收藏(v1.1.0 新增)
│ ├── distribution/index.vue # 分销:佣金/邀请/团队/提现
│ ├── message/index.vue # 消息中心
│ ├── mine/index.vue # 我的
@@ -39,9 +41,10 @@ fenleixinxi/
│ ├── config/index.js # 网关地址、开关、常量
│ ├── utils/request.js # 统一请求层(鉴权/解包/401/降级)
│ ├── utils/{storage,format,pay}.js
│ ├── api/ # auth/home/post/member/distribution/message/admin
│ ├── api/ # auth/home/post/member/distribution/message/favorite/report/order/pay/coupon/setting/admin
│ ├── constants/report.js # 举报原因字典(report.js,v1.1.0)
│ ├── assets/ # 图标注册表(icons.js)+ 轮播取图与主题护栏(banners.js)
│ ├── mock/ # 52 条 mock 路由 + 数据集(离线可跑)
│ ├── mock/ # 77 条 mock 路由 + 数据集(离线可跑)
│ ├── constants/ui.js # 状态/图标映射
│ └── store/index.js # 轻量全局状态
├── static/ # 74 枚图标 + 3 张轮播插画(banners/)+ LOGO
@@ -149,7 +152,7 @@ sh tests/run.sh
# 等价于依次执行:
# 1) node tests/validate.mjs 项目结构校验(路由/.vue 标签/SCSS 括号/import 依赖)
# 2) node tools/check-scss.cjs 用 dart-sass 编译全部 SCSS
# 3) node tests/run.mjs 全链路接口测试(139 例)
# 3) node tests/run.mjs 全链路接口测试(167 例)
```
## 七、设计主色调(春绿 · Spring Green)
@@ -182,10 +185,11 @@ node tools/preview-theme.mjs --render # 首页仿真 + 全量令牌色板;
```
[1/3] 项目结构校验 : 错误 0,警告 0
[2/3] SCSS 编译校验 : 通过 17 个,失败 0(dart-sass)
[3/3] 全链路接口测试 : 用例总数 139 通过 139 失败 0 (通过率 100%)
—— 含 配图完整性与后台自定义闭环 16 例 + 轮播配图与主题护栏 14 例
—— 含 v1.0.7 新增:我的发布 / 发布配置与计费 / 后台置顶闭环 15 例
[2/3] SCSS 编译校验 : 通过 19 个,失败 0(dart-sass)
[3/3] 全链路接口测试 : 用例总数 167 通过 167 失败 0 (通过率 100%)
—— 含 配图完整性与后台自定义闭环 16 例 + 轮播配图与主题护栏 17 例
—— 含 v1.0.7 新增:我的发布 / 发布配置与计费 / 后台置顶闭环 14 例
—— 含 v1.1.0 新增:支付 / 上传 / LBS / 收藏关注 / 举报 / 充值 / 优惠券 28 例
```
| 视觉资源 | 状态 |
@@ -199,13 +203,14 @@ node tools/preview-theme.mjs --render # 首页仿真 + 全量令牌色板;
### 8.1 后端接口(线上真实环境)
后端 v1 业务接口已实现并部署到 `https://fenleixinxi.gzyakj.com.cn/api/v1`,
与本前端 `common/api/*.js` 的 65 个端点**逐一对应**,契约见 `docs/03-接口文档.md`。
与本前端 `common/api/*.js` 的端点**逐一对应**(`application/route.php` 共 73 条:用户端 41 + 后台 32),契约见 `docs/03-接口文档.md`。
```
后端接口端到端验证(tools/verify-api.mjs)
用例总数: 183 通过: 183 失败: 0
—— 65 端点契约 + 12 条业务规则文案比对 + 8 步双端业务闭环
用例总数: 226 通过: 226 失败: 0
—— 端点契约 + 业务规则文案比对 + 8 步双端业务闭环
(用户发布 → 后台待审 → 后台审核 → 前台可见 → 消息通知 → 操作留痕)
—— v1.1.0 新增 39 例:媒体上传 / LBS / 收藏关注 / 举报 / 充值 / prepay / 回调 / 优惠券 / 发布回读
```
**后端种子数据与本前端 `common/mock/data.js` 逐字段对齐**,因此把
@@ -239,6 +244,7 @@ node tools/preview-theme.mjs --render # 首页仿真 + 全量令牌色板;
| **1.0.7** | 2026-10-15 | **发布侧三件事(缺陷 + 两个新功能)**:① **「我的发布」页上线**——原先「我的」页入口指向 `/pages/post/list`(分类信息列表,只显示别人审核通过的信息),用户发完**看不到自己的发布记录**,现新增 `GET /post/mine`(按 全部/待审核/已通过/已驳回/已下架 筛选 + 各类计数 + 驳回原因)与 `pages/post/mine.vue`,发布成功后直接跳转该页;② **后台手动置顶**——`fc_post.is_top` 正式接入用户端:后台「信息管理」新增「置顶/取消置顶」(工具栏批量 + 行内按钮 + 状态徽章,附站内消息通知),`Home::feed` 与 `/post/mine` 均按 `is_top desc` 排序,卡片显示「置顶」角标;③ **免费/付费发布开关**——后台新增「发布与费用配置」页(同城业务 → 基础数据):发布模式(免费发布 / 付费发布)+ 会员单条发布价格 + 按月(价格 + 含条数)+ 按年(价格 + 含条数),保存后同步会员套餐(月卡 M1 / 年卡 M12);`fc_user_ext` 新增发布额度 `post_quota_left`,开通月/年卡自动发放额度、额度内免费发布(付费模式下仍保留「每日免费条数」避免硬付费墙);④ **计费改为服务端定价**(BUG-012):原先发布金额由前端 `amount` 传入并被后端直接采信,客户端可改价(0.01 元发帖),现金额一律按后台配置在服务端计算;新增 15 条用例(总用例 134,全绿) |
| **1.0.8** | 2026-10-16 | **会员手机号真绑定 + 后台会员列表字段调整**:① **修复「手机号授权绑定」假实现**(BUG-013)——原先登录页点击绑定只在前端拼 `138 + 时间戳` 的假号、从不落库,导致微信注册会员 `fa_user.mobile` 恒为空、后台会员列表手机号大面积为空;现改为 `<button open-type="getPhoneNumber">` 调用新增的 `POST /auth/wx-phone`(后端用小程序 access_token 调微信 `phonenumber.getPhoneNumber` 真实取号并写入 `fa_user.mobile`,access_token 带缓存),微信侧不可用时自动回落手动输入 `bind-phone`;② **后台会员列表**按要求去掉「角色组 / 电子邮箱 / 等级」三列,手机号列前置并**全量展示**:未绑定号时回落显示该会员最近一条发布信息里的联系电话并标注「来自发布信息」(`mobile_source=post`),两者都无显示「未绑定」;③ 后台 JS 变更后 bump `site.version` 强制刷新浏览器静态资源缓存;新增 2 条用例(总用例 136,全绿) |
| **1.0.9** | 2026-10-16 | **采集会员微信昵称与头像**(BUG-014):微信自 2022 年已回收主动拉取资料接口,登录页改用官方 `open-type="chooseAvatar"` + `<input type="nickname">` 由用户授权采集,登录时随 `wx-login` 提交;新增 `POST /auth/avatar`(用户端 multipart 上传头像,落盘并写 `fa_user.avatar`)与 `POST /auth/profile`(随时补填资料);后台「会员列表」昵称/头像列由此显示真实微信资料(无头像仍显示字母头像);另含 v1.0.8 的会员手机号真绑定与列表字段调整。新增 3 条用例(总用例 139,全绿) |
| **1.1.0** | 2026-10-17 | **支付闭环 + 媒体上传 + LBS + 互动 + 优惠券 + 后台看板/设置**:① **微信支付改用 epay 插件承载(插件下单 + 插件验签,本地业务编排在 `api/Pay`)**——新增 `POST /pay/prepay`、`POST /pay/notify`:下单走 `\addons\epay\library\Service::submitOrder(..., 'miniapp', $openid)`,验签与应答由插件 `checkNotify()` / `$pay->success()` 生成;商户号/密钥真源为**插件配置**(`addons/epay/config.php`,后台「插件管理 → 微信支付宝整合插件 → 配置」),回调地址按当前请求域名动态生成、回调新增金额核对;`/post/{id}/pay-view`、`/member/open`、`/order/recharge` 微信支付统一改为**只落 `pending` 订单**、由回调置 `paid` 并分派后续动作(会员发放 / 余额入账 / 付费解锁),回调幂等;修复 **BUG-015**(`Pay::notify()` 与基类站内信 `notify()` 同名签名不兼容导致 `/pay/*` 全 500,动作改名 `notifyCallback()`);② **发布媒体真上传**——新增 `POST /post/upload`(图片 ≤10MB / 视频 ≤50MB),发布页 `uni.uploadFile` 真上传并提交 URL 数组;③ **LBS**——`fc_post` 增 `lat`/`lng`,首页 `uni.getLocation` + `sort=nearby`(按距离升序、无坐标排最后)、卡片显示 `distance`,详情页真实坐标地图;④ **举报**——新增 `POST /report` + 详情页入口(原因字典 `report.reasons`);⑤ **收藏 / 关注**——新增 `fc_favorite`/`fc_follow` 与 `/favorite/*`、`/follow/toggle`,新增「我的收藏」页;⑥ **余额充值**——新增 `POST /order/recharge` 与 `pages/member/recharge`(**只允许微信支付**:`payType=balance` 等于用余额给余额加钱、余额只增不减,会凭空增发余额,属资损漏洞,已禁止并加用例守护);⑦ **优惠券**——新增 `fc_coupon`/`fc_user_coupon`、后台 CRUD(`fc/coupon`)、用户领取(`/coupon/*`)与资产展示(**不含下单抵扣**);⑧ **发布折扣**——单条付费按 `member.benefit.postDiscount` 折扣计价(BR-16);⑨ **后台 PC**新增「数据总览」`fc/dashboard`(核心看板 + 近 14 天趋势,置首)、「系统设置」`fc/setting`(仅基础配置 `base.*`,支付配置改由插件页维护)、「优惠券管理」`fc/coupon`,商家表单增店铺头图/公告/推荐位;`site.version` → `1.7.0.20261017`。新增 28 条用例(**总用例 167,全绿**),后端端到端 226/226,后台 6 页面 HTTP 200 |
**生产构建命令**(无需 HBuilderX GUI,复用同级项目的 uni-app 工具链):
@@ -287,7 +293,7 @@ rm -rf /tmp/fcmp && cp -R "$OUTPUT_DIR" /tmp/fcmp
### 8.4 余额支付测试指引(微信支付未配置时的替代方案)
微信支付商户参数(`mchId`/`apiKey`)尚未配置,因此**测试阶段统一走余额支付**。
微信支付尚未配置:商户号/密钥的真源是 **epay 插件配置**(`wechat.miniapp_id`/`wechat.mch_id`/`wechat.key`),当前为空,因此**测试阶段统一走余额支付**。
**① 支付方式由一处开关控制**
@@ -334,3 +340,45 @@ defaultPayType: 'balance',
付费查看 余额 → 10.00 → 8.00,付费记录 ¥2.00 balance(重复查看不再扣款)
余额不足 → 「余额不足」,余额不变,且**不产生假订单**
```
### 8.5 部署说明(v1.1.0 上线步骤)
1. **推送/部署后端代码**后,在**服务器执行一次**(幂等,可重复执行):
> **首次拉取本批改动的特别注意**:`addons/epay`、`addons/wechat`、`public/assets/addons/*` 此前被 `.gitignore` 忽略,
> 服务器上早就有这些文件却不在 Git 里 → 直接 `git pull` 会报 `untracked working tree files would be overwritten by merge`。
> 若**已在服务器配置过插件密钥**,先备份 `addons/epay/config.php`(当前为空白模板):
```bash
cd /www/wwwroot/fenleixinxi.gzyakj.com.cn
cp addons/epay/config.php /root/epay-config.bak # 只有已配置过才需要
rm -rf addons/epay addons/wechat public/assets/addons/epay public/assets/addons/wechat
git pull
cp /root/epay-config.bak addons/epay/config.php # 上面备份过的才需要复原
git update-index --skip-worktree addons/epay/config.php # 之后服务器改插件配置不再污染 git 状态
php think fc:install # 不带 --reset,常规上线勿带
```
该命令会**幂等补齐**本轮新增结构:建 4 张新表(`fa_fc_favorite` / `fa_fc_follow` / `fa_fc_coupon` / `fa_fc_user_coupon`)、
补列(`fa_fc_post.lat` / `lng`,`fa_fc_merchant.banner` / `notice` / `recommend`)、
补配置(`coupon.enabled` / `report.reasons`;**支付配置不在其中**,改由 epay 插件配置维护)、
补后台菜单与权限节点(数据总览 / 系统设置 / 优惠券管理)。**已存在的列 / 配置一律跳过,不覆盖运营已改的值**;
菜单与权限节点按 `name` **对齐标题/上级/排序**(若在后台改过这三个新页面的菜单名,会被重置回默认名)。
从 v1.1.0 起它还会**清理插件钩子缓存 `hooks`**:`application/extra/addons.php` 的钩子会被 File 缓存,
若该缓存是在「插件目录还不存在」时生成的,插件启用后也不会被加载(症状:微信支付报 `Class 'Yansongda\Pay\Pay' not found`)。
同时清理后台左侧菜单缓存 `__menu__`(`Auth::getSidebar()` 依赖它,否则新菜单要等缓存过期才显示)。
手工兜底:删除 `runtime/cache` 目录即可重建。
2. 后台 JS/视图/CSS 变更已通过 bump `site.version`(`1.7.0.20261017`)强制刷新浏览器静态资源缓存。
3. **微信支付真机联调前置**:在后台「插件管理 → 微信支付宝整合插件 → 配置」填写 `wechat.miniapp_id`(**小程序 AppID,必须填这一项**)、`wechat.mch_id`(商户号)、`wechat.key`(v2 密钥);
未配置时 `/pay/prepay` 会返回「微信支付未配置」,`/pay/notify` 返回 SUCCESS 但不处理(余额支付不受影响)。
4. 小程序侧为 **1.1.0**(`manifest.json` `versionName=1.1.0` / `versionCode=110`),新增页面 `pages/favorite/index`、`pages/member/recharge` 已注册进 `pages.json`。
5. **插件已入库**:`.gitignore` 已移除 `/addons/*` 与 `/public/assets/addons/*`,`addons/epay`、`addons/wechat` 与 `public/assets/addons/{epay,wechat}` 现由 git 跟踪(`addons/epay` 即微信支付宝整合插件源码,无需在服务器另行放置插件包)。
6. **重要提示(插件密钥防冲突)**:插件的商户号/密钥保存在 `addons/epay/config.php`,该文件已入库(当前为**空白模板**);在服务器上通过后台配置后该文件会被改写,为避免 `git pull` 冲突,请在服务器上执行 `git update-index --skip-worktree addons/epay/config.php`;**切勿把填了真实密钥的该文件提交到仓库**。
7. **插件 API 版本必须保持 `v2`**:站点与服务器均为 PHP 7.4,插件配置里的 `version` 只能用 `v2`(V3 版本要求 PHP 8.0+ 且需上传 API 证书);
`addons/epay/certs/*.pem` 是插件自带的**空白占位文件**,V2 支付(JSAPI 统一下单 + 回调验签)用不到它们,无需处理。