0. 阅读对象与约定
平台开发 改 backend/(TP8) + admin-web/(Vite/Vue3),对应「总平台管理后台」。
商家开发 改 backend/route/merchant.php + admin-web/src/router/index.js 的 /merchant 段。
C 端开发 改 uni-app/,需 HBuilderX 重新打包,不在本仓库热更新。
项目没有自定义 Translator/Adapter 类(仅 vendor
league/flysystem)。分↔元与字段映射在 service 层按约定处理,新增表/接口请沿用在 service 层统一换算,不要散落各处 *100/100。
每次本地改完后端 controller/路由后需重启 8000 服务才生效;前端改完需重新 vite build 并 cp 到 backend/public/admin/。
1. 本地工程与启动
| 模块 | 目录 | 启动 / 调试 | 端口 |
|---|---|---|---|
| 后端 API | backend/ | php think run -H 127.0.0.1 -p 8000 | 8000 |
| 管理后台前端 | admin-web/ | npm run dev(Vite) | 9999 |
| C 端(uni-app) | uni-app/ | HBuilderX 编译 | — |
- 后端路由前缀:
/api/admin、/api/merchant、/api/member、/api/pay(见backend/route/*.php)。 - 本地库:
xshop_admin(root/空)。测试线库xshop,正式线库xydshop。 - 前端请求封装:
admin-web/src/utils/request.js,统一信封{code, msg, data};code===0为成功。
2. 新增一个管理后台 API
入口文件:backend/route/admin.php。统一前缀 /admin,响应 {code,msg,data}。
->middleware('别名','参数1','参数2'),不支持 '别名:参数' 冒号写法(冒号会被当成类名一部分)。// backend/route/admin.php (在 Route::group('admin', ...) 内合适分组下追加) Route::get('demo/list', 'admin/Demo/list') ->middleware('PermissionMiddleware', 'demo:view') // 权限点 ->middleware('LogMiddleware', 'demo', 'demo_list', 1); // 模块,动作,operation_level
- 权限中间件
PermissionMiddleware:参数perm_code(如demo:view),未配置该权限点的角色返回 403。 - 日志中间件
LogMiddleware:参数模块, 动作, operation_level: 1 查看2 操作3 高危·动资金。 - 二次密码(高危操作):当权限点
operation_level = 3,PermissionMiddleware校验请求头X-Verify-Password(明文,bcrypt 比对admin_user.password)。前端目前尚未实现该输入框,故所有 level=3 按钮在未补前端前会一律 403——新增 level=3 接口务必同步补前端弹窗(见 §7)。
3. 新增一个权限点(permCode)
权限点存于 admin_permission 表:perm_code、perm_name、perm_type(menu/button)、parent_id、operation_level、sort_order、status。
- 菜单类:
perm_code形如menu:xxx,前端路由meta.permCode与之对应(见 §6)。 - 操作类:
perm_code形如demo:edit,在路由挂到PermissionMiddleware。 - 增删建议走「系统管理 → 权限管理」后台或幂等 SQL;SUPER_ADMIN 拥有全部权限(
PermissionService::hasPerm)。 - 权限树接口:
GET /admin/permission/perm/tree(返回扁平树,供角色授权编辑)。
4. 新增一个支付渠道(核心扩展点)
所有第三方支付实现统一接口 app\service\gateway\PaymentGatewayInterface。上层 PaymentGatewayManager 按 channel_code 取实例,对上层零感知。
接口五个方法
interface PaymentGatewayInterface { public function getChannelCode(): string; // 与 pay_channel.channel_code 一致 public function createPayment(array $trade): array; // 发起支付 public function verifyNotify(array $payload): array; // 校验异步回调 public function query(string $outTradeNo): array; // 查单(可选) public function refund(array $params): array; // 退款(可选) }
createPayment 返回(pay_type 决定前端调起方式):
[ 'pay_type' => 'redirect'|'qr'|'form'|'none', // 跳转/扫码/表单/无 'pay_url'=>'', 'qr_code'=>'', 'form_html'=>'', 'channel_trade_no'=>'', 'expire_at'=>'', 'raw'=>[] ]
verifyNotify 返回(控制器据此写支付成功并原样输出 respond 给支付商确认):
[ 'valid'=>true, 'out_trade_no'=>'', 'channel_trade_no'=>'', 'amount'=>0.0, 'paid'=>true, 'paid_at'=>'', 'respond'=>'', // 支付商期望回执,如微信 "SUCCESS"/支付宝 "success" 'raw'=>[] ]
新增渠道四步
- 在
backend/app/service/gateway/新建XxxGateway.php,implements PaymentGatewayInterface。 - 在
pay_channel表插入一行:channel_code(如xxx)、channel_name、gateway_class(app\service\gateway\XxxGateway)、mode(1=实时分账 / 2=平台代收)、status=1、priority、min_amount/max_amount(max=0 不限)、config_json(密钥 JSON)。 - (可选)在
PaymentGatewayManager::FALLBACK补充channel_code => 类名,保证缺gateway_class时也能实例化。 - 密钥字段在
PayChannel控制器有脱敏白名单(api_key/api_v3_key/private_key/sec_key/sign_key/md5_key/plat_key/mp_secret/mini_secret/ali_private_key/ali_public_key等),列表接口回显自动打码为******。
joinpay、kuaifu(快付/Gdkf)、wechat、alipay、ecny(数币)、unionpay、mock。C 端收银台 xshop-multipay 四渠道路由:
xpay(微信/支付宝) / joinpay(汇聚) / gdkf(快付,渠道码 kuaifu) / ecny(数币)。注意 UnionPay/余额/Wallet/Mock 为后端可配置渠道,但不在 C 端四渠道收银台内;要让 C 端用户看到新渠道,还须改 C 端
xshop-multipay 组件(需 HBuilderX 重新打包)。
渠道路由优先级(PaymentGatewayManager::resolve):
① 订单指定渠道(preferred) → ② 商户绑定优先级(merchant_pay_channel.priority) → ③ 金额区间 → ④ 平台默认优先级。实时分账渠道子商户未入驻会自动跳过降级。
5. 商家维度数据隔离
商家体系(非平台租户 tid 方案)通过 merchant_id 隔离:
- 表:
merchant(组织根)、merchant_staff(员工)、merchant_role(角色)。 - 鉴权:
MerchantAuthMiddleware校验商家员工 token,注入request->merchantInfo。 - 路由:
backend/route/merchant.php,统一前缀/merchant,与平台后台平行独立登录态。 - 数据隔离:所有商家查询必须带
merchant_id条件(门店/供应商/团队均归属商家)。
6. 新增后台菜单 + 页面
文件:admin-web/src/router/index.js。
本项目一级菜单是分组容器(无对应页面),用 redirect 指到默认子页;子页面 meta.permCode 与后端 admin_permission.perm_code 一一对应,前端按权限点控制左侧菜单显隐。
// 1) 一级分组里加一个重定向容器(若该分组已存在则跳过) { path: 'operation', redirect: '/operation/announcement' }, // 2) 加子页面 { path: 'operation/demo', name: 'OperationDemo', component: () => import('@/views/demo/index.vue'), meta: { title: '示例模块', permCode: 'menu:demo' }, // ← 与后端 perm_code 对齐 }
- 视图文件放
admin-web/src/views/<模块>/index.vue。 - 旧路径用
redirect做兼容,避免历史书签 404。 - V6 专属模块:
/v6/:section(.*)*(权限点v6:dashboard)。
7. 高危操作的二次密码弹窗(待补)
当前前端未实现 operation_level=3 的二次密码输入,导致所有 level=3 接口在前端会 403。
- 修复点:调用 level=3 接口前,弹窗收集管理员密码,塞入请求头
X-Verify-Password。 - 后端校验见 §2(
PermissionMiddleware)。新增 level=3 接口必须同步补此弹窗,否则按钮不可用。
8. 数据库与迁移
- 迁移顺序(幂等,可生产安全重跑):
install → modules → business → migrate → seed,用INSERT IGNORE/CREATE TABLE IF NOT EXISTS。 - 字段命名:沿用 snake_case;金额类字段存储「分」(int),展示在 service 层转元。
- 索引长度:生产 MySQL 5.7,utf8mb4 InnoDB 单列索引上限约 3072 字节(≈ varchar(767)),长字符串唯一键注意前缀或改用哈希列。
- 敏感配置不入 git/migration:密钥、DB 密码放环境变量/配置表
config_param。
9. 部署
测试线(xyd.findhere.cn)
outputs/prod-tool/prod.sh 已写死 BACKEND=/www/wwwroot/•••/•••/backend(仅测试线)。常用:
bash outputs/prod-tool/prod.sh upload 本地文件 相对路径 # 上传到测试线 backend/ bash outputs/prod-tool/prod.sh sql xxx.sql # 执行 SQL bash outputs/prod-tool/prod.sh run # 重启后端 bash outputs/prod-tool/prod.sh backup / dump # 备份/导出
- 改后端 controller/路由后须重启
php-fpm-82。 - 含反引号/引号的 SQL 走
sqlf文件管道,sql走 shell 易丢转义报 1064。
正式线(xyd.xyanda.cn)
prod.sh 默认指向测试线,发正式线须换路径:web 根 /www/wwwroot/•••/•••/backend/public/,库 xydshop。用原始 scp/ssh:
KEY=<项目>/.workbuddy/ssh/xshop_prod_key
scp -i $KEY backend/app/... root@8.166.•••.133:/www/wwwroot/•••/•••/backend/app/...
ssh -i $KEY root@8.166.•••.133 "cd /www/wwwroot/•••/•••/backend && php think clear"
- .user.ini 不可变:宝塔 open_basedir 限制,
.user.ini为 immutable,禁止sed/>直接改;确需改须chattr -i解锁→改→chattr +i复原。 - 每轮收尾必须 diff 正式线 vs 本地,曾两次抓到正式线落后,发现落后直接传本地完整版。
- 公开文档目录:
backend/public/docs/(线上https://xyd.xyanda.cn/docs/可访问)。
10. 常见坑(务必先看)
| 坑1 | TP8 中间件变参语法:->middleware('Auth','a','b'),别写 ->middleware('Auth:a:b')。 |
| 坑2 | 二次密码未实现:所有 operation_level=3 接口前端先 403,需补 X-Verify-Password 弹窗。 |
| 坑3 | 金额单位:库内「分」,service 层转「元」,勿在 controller/前端散算。 |
| 坑4 | 等级 V0 合法:会员等级 level() 下限 0,取值用 ?? 兜底,禁止 level || 默认(V0 会被当 falsy 吞掉)。 |
| 坑5 | C 端改动要打包:uni-app/ 与 admin-web 的 uni_modules/ 改完需 HBuilderX 重新发行。 |
| 坑6 | 渠道密钥脱敏:PayChannel 列表对白名单字段打码,保存时 ****** 表示未改、保留原值。 |
| 坑7 | 幂等 SQL:迁移脚本必须可重复执行,生产重跑不报错。 |
11. 扩展清单速查
| 扩展场景 | 改哪里 | 注意 |
|---|---|---|
| 新后台 API | route/admin.php + app/controller/admin/Xxx.php | 挂 Permission/Log 中间件 |
| 新权限点 | admin_permission 表/后台 | menu:* 关联前端 permCode |
| 新支付渠道 | app/service/gateway/XxxGateway.php + pay_channel 表 | 实现 5 方法;C 端需另改 xshop-multipay |
| 新后台菜单页 | admin-web/src/router/index.js + views/xxx | 一级分组用 redirect 容器 |
| 新商家接口 | route/merchant.php + MerchantXxx 控制器 | 带 merchant_id 隔离 |
| 新迁移 | xxx.sql(幂等) | install→modules→business→migrate→seed |
| 上线正式线 | 原始 scp/ssh 到 xyanda.cn | 勿用写死测试线的 prod.sh;重启 php-fpm-82 |