重构初始化

This commit is contained in:
技术老胡
2026-04-15 11:45:46 +08:00
parent 72d72d735b
commit 7612026773
381 changed files with 28287 additions and 14717 deletions

View File

@@ -1,216 +0,0 @@
协议规则
请求数据格式application/x-www-form-urlencoded
返回数据格式JSON
签名算法MD5
字符编码UTF-8
页面跳转支付
此接口可用于用户前台直接发起支付使用form表单跳转或拼接成url跳转。
URL地址http://192.168.31.200:4000/submit.php
请求方式POST 或 GET推荐POST不容易被劫持或屏蔽
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
商户ID pid 是 Int 1001
支付方式 type 否 String alipay 支付方式列表
商户订单号 out_trade_no 是 String 20160806151343349
异步通知地址 notify_url 是 String http://www.pay.com/notify_url.php 服务器异步通知地址
跳转通知地址 return_url 是 String http://www.pay.com/return_url.php 页面跳转通知地址
商品名称 name 是 String VIP会员 如超过127个字节会自动截取
商品金额 money 是 String 1.00 单位最大2位小数
业务扩展参数 param 否 String 没有请留空 支付后原样返回
签名字符串 sign 是 String 202cb962ac59075b964b07152d234b70 签名算法点此查看
签名类型 sign_type 是 String MD5 默认为MD5
支付方式type不传会跳转到收银台支付
API接口支付
此接口可用于服务器后端发起支付请求会返回支付二维码链接或支付跳转url。
URL地址http://192.168.31.200:4000/mapi.php
请求方式POST
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
商户ID pid 是 Int 1001
支付方式 type 是 String alipay 支付方式列表
商户订单号 out_trade_no 是 String 20160806151343349
异步通知地址 notify_url 是 String http://www.pay.com/notify_url.php 服务器异步通知地址
跳转通知地址 return_url 否 String http://www.pay.com/return_url.php 页面跳转通知地址
商品名称 name 是 String VIP会员 如超过127个字节会自动截取
商品金额 money 是 String 1.00 单位最大2位小数
用户IP地址 clientip 是 String 192.168.1.100 用户发起支付的IP地址
设备类型 device 否 String pc 根据用户浏览器的UA判断
传入用户所使用的浏览器
或设备类型默认为pc
设备类型列表
业务扩展参数 param 否 String 没有请留空 支付后原样返回
签名字符串 sign 是 String 202cb962ac59075b964b07152d234b70 签名算法点此查看
签名类型 sign_type 是 String MD5 默认为MD5
返回结果json
字段名 变量名 类型 示例值 描述
返回状态码 code Int 1 1为成功其它值为失败
返回信息 msg String 失败时返回原因
订单号 trade_no String 20160806151343349 支付订单号
支付跳转url payurl String http://192.168.31.200:4000/pay/wxpay/202010903/ 如果返回该字段则直接跳转到该url支付
二维码链接 qrcode String weixin://wxpay/bizpayurl?pr=04IPMKM 如果返回该字段则根据该url生成二维码
小程序跳转url urlscheme String weixin://dl/business/?ticket=xxx 如果返回该字段则使用js跳转该url可发起微信小程序支付
payurl、qrcode、urlscheme 三个参数只会返回其中一个
支付结果通知
通知类型服务器异步通知notify_url、页面跳转通知return_url
请求方式GET
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
商户ID pid 是 Int 1001
易支付订单号 trade_no 是 String 20160806151343349021 聚合支付平台订单号
商户订单号 out_trade_no 是 String 20160806151343349 商户系统内部的订单号
支付方式 type 是 String alipay 支付方式列表
商品名称 name 是 String VIP会员
商品金额 money 是 String 1.00
支付状态 trade_status 是 String TRADE_SUCCESS 只有TRADE_SUCCESS是成功
业务扩展参数 param 否 String
签名字符串 sign 是 String 202cb962ac59075b964b07152d234b70 签名算法点此查看
签名类型 sign_type 是 String MD5 默认为MD5
收到异步通知后需返回success以表示服务器接收到了订单通知
MD5签名算法
1、将发送或接收到的所有参数按照参数名ASCII码从小到大排序a-zsign、sign_type、和空值不参与签名
2、将排序后的参数拼接成URL键值对的格式例如 a=b&c=d&e=f参数值不要进行url编码。
3、再将拼接好的字符串与商户密钥KEY进行MD5加密得出sign签名参数sign = md5 ( a=b&c=d&e=f + KEY ) (注意:+ 为各语言的拼接符不是字符md5结果为小写。
4、具体签名与发起支付的示例代码可下载SDK查看。
支付方式列表
调用值 描述
alipay 支付宝
wxpay 微信支付
qqpay QQ钱包
设备类型列表
调用值 描述
pc 电脑浏览器
mobile 手机浏览器
qq 手机QQ内浏览器
wechat 微信内浏览器
alipay 支付宝客户端
jump 仅返回支付跳转url
[API]查询商户信息
URL地址http://192.168.31.200:4000/api.php?act=query&pid={商户ID}&key={商户密钥}
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
操作类型 act 是 String query 此API固定值
商户ID pid 是 Int 1001
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
返回结果:
字段名 变量名 类型 示例值 描述
返回状态码 code Int 1 1为成功其它值为失败
商户ID pid Int 1001 商户ID
商户密钥 key String(32) 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i 商户密钥
商户状态 active Int 1 1为正常0为封禁
商户余额 money String 0.00 商户所拥有的余额
结算方式 type Int 1 1:支付宝,2:微信,3:QQ,4:银行卡
结算账号 account String admin@pay.com 结算的支付宝账号
结算姓名 username String 张三 结算的支付宝姓名
订单总数 orders Int 30 订单总数统计
今日订单 order_today Int 15 今日订单数量
昨日订单 order_lastday Int 15 昨日订单数量
[API]查询结算记录
URL地址http://192.168.31.200:4000/api.php?act=settle&pid={商户ID}&key={商户密钥}
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
操作类型 act 是 String settle 此API固定值
商户ID pid 是 Int 1001
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
返回结果:
字段名 变量名 类型 示例值 描述
返回状态码 code Int 1 1为成功其它值为失败
返回信息 msg String 查询结算记录成功!
结算记录 data Array 结算记录列表
[API]查询单个订单
URL地址http://192.168.31.200:4000/api.php?act=order&pid={商户ID}&key={商户密钥}&out_trade_no={商户订单号}
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
操作类型 act 是 String order 此API固定值
商户ID pid 是 Int 1001
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
系统订单号 trade_no 选择 String 20160806151343312
商户订单号 out_trade_no 选择 String 20160806151343349
提示:系统订单号 和 商户订单号 二选一传入即可,如果都传入以系统订单号为准!
返回结果:
字段名 变量名 类型 示例值 描述
返回状态码 code Int 1 1为成功其它值为失败
返回信息 msg String 查询订单号成功!
易支付订单号 trade_no String 2016080622555342651 聚合支付平台订单号
商户订单号 out_trade_no String 20160806151343349 商户系统内部的订单号
第三方订单号 api_trade_no String 20160806151343349 支付宝微信等接口方订单号
支付方式 type String alipay 支付方式列表
商户ID pid Int 1001 发起支付的商户ID
创建订单时间 addtime String 2016-08-06 22:55:52
完成交易时间 endtime String 2016-08-06 22:55:52
商品名称 name String VIP会员
商品金额 money String 1.00
支付状态 status Int 0 1为支付成功0为未支付
业务扩展参数 param String 默认留空
支付者账号 buyer String 默认留空
[API]批量查询订单
URL地址http://192.168.31.200:4000/api.php?act=orders&pid={商户ID}&key={商户密钥}
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
操作类型 act 是 String orders 此API固定值
商户ID pid 是 Int 1001
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
查询订单数量 limit 否 Int 20 返回的订单数量最大50
页码 page 否 Int 1 当前查询的页码
返回结果:
字段名 变量名 类型 示例值 描述
返回状态码 code Int 1 1为成功其它值为失败
返回信息 msg String 查询结算记录成功!
订单列表 data Array 订单列表
[API]提交订单退款
需要先在商户后台开启订单退款API接口开关才能调用该接口发起订单退款
URL地址http://192.168.31.200:4000/api.php?act=refund
请求方式POST
请求参数说明:
字段名 变量名 必填 类型 示例值 描述
商户ID pid 是 Int 1001
商户密钥 key 是 String 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i
易支付订单号 trade_no 特殊可选 String 20160806151343349021 易支付订单号
商户订单号 out_trade_no 特殊可选 String 20160806151343349 订单支付时传入的商户订单号,商家自定义且保证商家系统中唯一
退款金额 money 是 String 1.50 少数通道需要与原订单金额一致
trade_no、out_trade_no 不能同时为空如果都传了以trade_no为准
返回结果:
字段名 变量名 类型 示例值 描述
返回状态码 code Int 0 0为成功其它值为失败
返回信息 msg String 退款成功

View File

@@ -1,112 +0,0 @@
event事件处理
webman/event 提供一种精巧的事件机制可实现在不侵入代码的情况下执行一些业务逻辑实现业务模块之间的解耦。典型的场景如一个新用户注册成功时只要发布一个自定义事件如user.register各个模块遍能收到该事件执行相应的业务逻辑。
安装
composer require webman/event
订阅事件
订阅事件统一通过文件config/event.php来配置
<?php
return [
'user.register' => [
[app\event\User::class, 'register'],
// ...其它事件处理函数...
],
'user.logout' => [
[app\event\User::class, 'logout'],
// ...其它事件处理函数...
]
];
说明:
user.register user.logout 等是事件名称,字符串类型,建议小写单词并以点(.)分割
一个事件可以对应多个事件处理函数,调用顺序为配置的顺序
事件处理函数
事件处理函数可以是任意的类方法、函数、闭包函数等。
例如创建事件处理类 app/event/User.php (目录不存在请自行创建)
<?php
namespace app\event;
class User
{
function register($user)
{
var_export($user);
}
function logout($user)
{
var_export($user);
}
}
发布事件
使用 Event::dispatch($event_name, $data); 或 Event::emit($event_name, $data); 发布事件,例如
<?php
namespace app\controller;
use support\Request;
use Webman\Event\Event;
class User
{
public function register(Request $request)
{
$user = [
'name' => 'webman',
'age' => 2
];
Event::dispatch('user.register', $user);
}
}
发布事件有两个函数Event::dispatch($event_name, $data); 和 Event::emit($event_name, $data); 二者参数一样。
区别是emit内部会自动捕获异常也就是说如果一个事件有多个处理函数某个处理函数发生异常不会影响其它处理函数的执行。
而dispatch则内部不会自动捕获异常当前事件的任何一个处理函数发生异常则停止执行下一个处理函数并直接向上抛出异常。
提示
参数$data可以是任意的数据例如数组、类实例、字符串等
通配符事件监听
通配符注册监听允许您在同一个监听器上处理多个事件例如config/event.php里配置
<?php
return [
'user.*' => [
[app\event\User::class, 'deal']
],
];
我们可以通过事件处理函数第二个参数$event_data获得具体的事件名
<?php
namespace app\event;
class User
{
function deal($user, $event_name)
{
echo $event_name; // 具体的事件名,如 user.register user.logout 等
var_export($user);
}
}
停止事件广播
当我们在事件处理函数里返回false时该事件将停止广播
闭包函数处理事件
事件处理函数可以是类方法,也可以是闭包函数例如
<?php
return [
'user.login' => [
function($user){
var_dump($user);
}
]
];
查看事件及监听器
使用命令 php webman event:list 查看项目配置的所有事件及监听器
支持范围
除了主项目基础插件和应用插件同样支持event.php配置。
基础插件配置文件 config/plugin/插件厂商/插件名/event.php
应用插件配置文件 plugin/插件名/config/event.php
注意事项
event事件处理并不是异步的event不适合处理慢业务慢业务应该用消息队列处理

View File

@@ -1,114 +0,0 @@
异常处理
配置
config/exception.php
return [
// 这里配置异常处理类
'' => support\exception\Handler::class,
];
多应用模式时,你可以为每个应用单独配置异常处理类,参见多应用
默认异常处理类
webman中异常默认由 support\exception\Handler 类来处理。可修改配置文件config/exception.php来更改默认异常处理类。异常处理类必须实现Webman\Exception\ExceptionHandlerInterface 接口。
interface ExceptionHandlerInterface
{
/**
* 记录日志
* @param Throwable $e
* @return mixed
*/
public function report(Throwable $e);
/**
* 渲染返回
* @param Request $request
* @param Throwable $e
* @return Response
*/
public function render(Request $request, Throwable $e) : Response;
}
渲染响应
异常处理类中的render方法是用来渲染响应的。
如果配置文件config/app.php中debug值为true(以下简称app.debug=true),将返回详细的异常信息,否则将返回简略的异常信息。
如果请求期待是json返回则返回的异常信息将以json格式返回类似
{
"code": "500",
"msg": "异常信息"
}
如果app.debug=truejson数据里会额外增加一个trace字段返回详细的调用栈。
你可以编写自己的异常处理类来更改默认异常处理逻辑。
业务异常 BusinessException
有时候我们想在某个嵌套函数里终止请求并返回一个错误信息给客户端这时可以通过抛出BusinessException来做到这点。
例如:
<?php
namespace app\controller;
use support\Request;
use support\exception\BusinessException;
class FooController
{
public function index(Request $request)
{
$this->checkInput($request->post());
return response('hello index');
}
protected function checkInput($input)
{
if (!isset($input['token'])) {
throw new BusinessException('参数错误', 3000);
}
}
}
以上示例会返回一个
{"code": 3000, "msg": "参数错误"}
注意
业务异常BusinessException不需要业务try捕获框架会自动捕获并根据请求类型返回合适的输出。
自定义业务异常
如果以上响应不符合你的需求例如想把msg要改为message可以自定义一个MyBusinessException
新建 app/exception/MyBusinessException.php 内容如下
<?php
namespace app\exception;
use support\exception\BusinessException;
use Webman\Http\Request;
use Webman\Http\Response;
class MyBusinessException extends BusinessException
{
public function render(Request $request): ?Response
{
// json请求返回json数据
if ($request->expectsJson()) {
return json(['code' => $this->getCode() ?: 500, 'message' => $this->getMessage()]);
}
// 非json请求则返回一个页面
return new Response(200, [], $this->getMessage());
}
}
这样当业务调用
use app\exception\MyBusinessException;
throw new MyBusinessException('参数错误', 3000);
json请求将收到一个类似如下的json返回
{"code": 3000, "message": "参数错误"}
提示
因为BusinessException异常属于业务异常(例如用户输入参数错误),它是可预知的,所以框架并不会认为它是致命错误,并不会记录日志。
总结
在任何想中断当前请求并返回信息给客户端的时候可以考虑使用BusinessException异常。

View File

@@ -1,414 +0,0 @@
# MPay V2 Project Overview
更新日期2026-03-13
## 1. 项目定位
这是一个基于 Webman 的多商户支付中台项目,当前主要目标是:
- 提供后台管理能力,维护商户、应用、支付方式、支付插件、支付通道、订单与系统配置
- 为商户应用提供统一支付能力
- 当前已优先兼容 `epay` 协议,后续可继续扩展更多外部支付协议
- 通过“支付插件 + 通道配置”的方式对接第三方渠道
结合当前代码与数据库,项目已经具备“多商户 -> 多应用 -> 多通道 -> 多插件”的基础骨架。
## 2. 技术栈与运行环境
### 后端技术栈
- PHP `>= 8.1`
- Webman `^2.1`
- webman/database
- webman/redis
- webman/cache
- webman/console
- webman/captcha
- webman/event
- webman/redis-queue
- firebase/php-jwt
- yansongda/pay `~3.7.0`
### 当前环境配置要点
- HTTP 服务监听:`0.0.0.0:8787`
- 数据库MySQL
- 缓存与队列Redis
- 管理后台认证JWT
- 当前 `.env` 已配置远程 MySQL / Redis 地址,开发前需要确认本机网络可达
## 3. 当前环境可调用的 MCP 能力
本次会话中,已确认可以直接用于本项目的 MCP / 环境能力如下:
### MySQL MCP
- 可直接执行 SQL
- 可读取当前开发库表结构与数据
- 已确认能访问 `mpay_admin` 相关表,例如:
- `ma_merchant`
- `ma_merchant_app`
- `ma_pay_channel`
- `ma_pay_order`
- `ma_notify_task`
- `ma_callback_inbox`
- `ma_pay_callback_log`
适合后续继续做:
- 表结构核对
- 初始化数据检查
- 回调与订单状态排查
- 开发联调时快速确认通道配置
### Playwright MCP
- 可进行浏览器打开、点击、表单填写、快照、截图、网络请求分析
- 适合后续验证:
- 管理后台登录流程
- 通道配置页面交互
- 提交支付后的跳转页/表单页
- 回调相关前端可视流程
### MCP 资源浏览
- 可列出 MCP 资源
- 可读取资源内容
- 当前未返回资源模板
### 非 MCP 但对开发有用的本地能力
- Shell 命令执行
- 工作区文件读写
- 代码补丁编辑
## 4. 业务模型总览
### 4.1 商户模型
- 表:`ma_merchant`
- 作用:定义商户主体
- 关键字段:
- `merchant_no`
- `merchant_name`
- `funds_mode`
- `status`
### 4.2 商户应用模型
- 表:`ma_merchant_app`
- 作用:商户可创建多个应用,每个应用具备独立 `app_id` / `app_secret`
- 关键字段:
- `merchant_id`
- `api_type`
- `app_id`
- `app_secret`
- `app_name`
- `status`
当前代码中,`app_id` 既是应用标识,也是外部协议鉴权入口;`epay` 兼容链路直接用它作为 `pid`
### 4.3 支付方式模型
- 表:`ma_pay_method`
- 作用:维护支付方式字典
- 当前库内数据:
- `alipay`
- `wechat`
- `unionpay`
### 4.4 支付插件模型
- 表:`ma_pay_plugin`
- 作用把“支付通道配置”与“PHP 插件实现类”解耦
- 插件需要同时实现:
- `PaymentInterface`
- `PayPluginInterface`
当前代码里已有两个插件类:
- `app/common/payment/LakalaPayment.php`
- `app/common/payment/AlipayPayment.php`
但当前数据库只注册了 `lakala`,还没有把 `alipay` 作为活动插件注册进现网开发库。
### 4.5 支付通道模型
- 表:`ma_pay_channel`
- 作用:把“商户应用 + 支付方式 + 插件 + 参数配置”绑定起来
- 关键字段:
- `merchant_id`
- `merchant_app_id`
- `plugin_code`
- `method_id`
- `config_json`
- `split_ratio`
- `chan_cost`
- `chan_mode`
- `daily_limit`
- `daily_cnt`
- `min_amount`
- `max_amount`
- `status`
- `sort`
这正对应你描述的核心业务特点:一个应用下可配置多个支付通道,每个通道可挂接不同插件与参数。
### 4.6 支付订单模型
- 表:`ma_pay_order`
- 作用:统一存放系统支付订单
- 关键特性:
- 系统订单号:`order_id`
- 商户订单号:`mch_order_no`
- 幂等唯一键:`(merchant_id, merchant_app_id, mch_order_no)`
- `extra` JSON 用于存放 `notify_url``return_url``pay_params`、退款信息等
### 4.7 回调与通知模型
- `ma_callback_inbox`:回调幂等收件箱
- `ma_pay_callback_log`:回调日志
- `ma_notify_task`:商户异步通知任务
这三张表说明项目已经为“渠道回调幂等 + 日志留痕 + 商户通知补偿”预留了比较完整的基础设施。
## 5. 代码分层与关键入口
### 外部接口入口
- `app/http/api/controller/EpayController.php`
- `app/http/api/controller/PayController.php`
### 支付主流程服务
- `app/services/api/EpayProtocolService.php`
- `app/services/api/EpayService.php`
- `app/services/PayService.php`
- `app/services/PayOrderService.php`
- `app/services/ChannelRouterService.php`
- `app/services/PluginService.php`
- `app/services/PayNotifyService.php`
- `app/services/NotifyService.php`
- `app/services/PaymentStateService.php`
### 支付插件契约
- `app/common/contracts/PaymentInterface.php`
- `app/common/contracts/PayPluginInterface.php`
- `app/common/base/BasePayment.php`
### 管理后台接口
- 商户:`MerchantController`
- 商户应用:`MerchantAppController`
- 支付方式:`PayMethodController`
- 插件注册:`PayPluginController`
- 通道:`ChannelController`
- 订单:`OrderController`
- 系统配置:`SystemController`
- 登录认证:`AuthController`
## 6. 当前已落地的对外接口
### 路由现状
当前 `app/routes/api.php` 实际挂载的对外接口为:
- `GET|POST /submit.php`
- `POST /mapi.php`
- `GET /api.php`
- `ANY /notify/{pluginCode}`
### 兼容协议现状
当前真正已打通的是 `epay` 风格接口:
- `submit.php`:页面跳转支付
- `mapi.php`API 下单
- `api.php?act=order`:查单
- `api.php?act=refund`:退款
### OpenAPI 现状
`PayController` 中存在以下方法:
- `create`
- `query`
- `close`
- `refund`
但当前都还是 `501 not implemented`,并且对应路由尚未挂载,因此“通用 OpenAPI”目前仍是预留骨架不是已上线能力。
## 7. 核心支付链路
### 7.1 Epay 下单链路
1. 商户调用 `submit.php``mapi.php`
2. `EpayProtocolService` 负责参数提取与校验
3. `EpayService` 使用 `app_secret` 做 MD5 验签
4. 构造统一内部订单数据
5. `PayOrderService` 创建订单,并通过联合唯一键保证幂等
6. `ChannelRouterService` 根据 `merchant_id + merchant_app_id + method_id` 选取通道
7. `PluginService` 从注册表解析插件类并实例化
8. 插件执行 `pay()`
9. `PayService` 回写:
- `channel_id`
- `chan_order_no`
- `chan_trade_no`
- `fee`
- `real_amount`
- `extra.pay_params`
10. 转换成 `epay` 所需返回结构给调用方
### 7.2 回调处理链路
1. 第三方渠道回调 `/notify/{pluginCode}`
2. `PayNotifyService` 调插件 `notify()` 验签与解析
3. 通过 `ma_callback_inbox` 做幂等去重
4. 状态机更新订单状态
5. 写入回调日志
6. 创建商户通知任务
### 7.3 商户通知链路
1. `NotifyService` 根据订单 `extra.notify_url` 创建通知任务
2. 通知内容写入 `ma_notify_task`
3. `sendNotify()` 使用 HTTP POST JSON 回调商户
4. 若商户返回 HTTP 200 且 body 为 `success`,视为通知成功
## 8. 插件与通道现状
### `LakalaPayment`
状态:示例插件 / mock 插件
现状:
- `pay()` 已实现,但只是返回模拟二维码字符串
- `query()` 未实现
- `close()` 未实现
- `refund()` 未实现
- `notify()` 未实现
这意味着当前库里虽然已经能“创建订单并拿到拉起参数”,但还不能完成真实的拉卡拉闭环。
### `AlipayPayment`
状态:代码层面相对完整
已实现:
- `pay()`
- `query()`
- `close()`
- `refund()`
- `notify()`
特点:
- 基于 `yansongda/pay`
- 支持产品类型:
- `alipay_web`
- `alipay_h5`
- `alipay_scan`
- `alipay_app`
- 可根据环境自动选产品
注意:
- 当前开发库没有注册 `alipay` 插件记录
- 当前通道也没有指向 `AlipayPayment`
所以它虽然写在代码里,但当前数据库并没有真正启用它。
## 9. 管理后台现状
后台已经覆盖以下核心维护能力:
- 验证码登录 + JWT 鉴权
- 商户管理
- 商户应用管理
- 支付方式管理
- 支付插件注册管理
- 支付通道管理
- 订单列表 / 详情 / 退款
- 系统基础配置管理
这部分说明“支付中心后台”已经不是空架子,而是可以承接后续运营配置的。
## 10. 当前开发库快照(基于 2026-03-13 实际查询)
### 数据量
- `ma_admin`: 1
- `ma_merchant`: 1
- `ma_merchant_app`: 1
- `ma_pay_method`: 3
- `ma_pay_plugin`: 1
- `ma_pay_channel`: 2
- `ma_pay_order`: 1
- `ma_notify_task`: 0
- `ma_callback_inbox`: 0
- `ma_pay_callback_log`: 0
### 当前商户与应用
- 商户:`M001 / 测试商户`
- 应用:`1001 / 测试应用-易支付`
- 应用类型:`epay`
### 当前活动插件
- `lakala -> app\\common\\payment\\LakalaPayment`
### 当前通道
- `lakala_alipay`
- `lakala_wechat`
### 当前示例订单
- 订单号:`P20260312160833644578`
- 商户单号:`TEST123`
- 状态:`PENDING`
- 通道:`channel_id = 1`
- `extra.pay_params` 为 mock 二维码
## 11. 当前代码与需求的对应关系
你给出的项目特点,与当前实现的对应情况如下:
### 已匹配的部分
- 多商户:已支持
- 一个商户多个应用:已支持
- 一个应用多个支付通道:已支持
- 通道可绑定支付方式:已支持
- 通道可绑定支付插件:已支持
- 通道可存储插件参数:已支持
- 通道可配置手续费:已支持,当前会参与 `fee` / `real_amount` 计算
- 商户通过 `APPID` 发起支付:已支持,当前主要在 `epay` 兼容链路中落地
- 创建订单并调用第三方插件:已支持
### 仅完成“数据建模”,尚未完全落地执行的部分
- 每日限额:字段已存在,但当前下单/路由流程未校验
- 每日笔数限制:字段已存在,但当前未校验
- 最小/最大金额限制:字段已存在,但当前未校验
- 更复杂的路由策略:当前仅按 `sort` 取第一条可用通道
- 多协议统一 OpenAPI控制器骨架存在但未真正接入
## 12. 后续阅读建议
如果下一次继续开发,建议优先从以下文件继续进入:
- 支付入口:`app/http/api/controller/EpayController.php`
- 协议适配:`app/services/api/EpayProtocolService.php`
- 业务主流程:`app/services/PayService.php`
- 订单创建:`app/services/PayOrderService.php`
- 回调处理:`app/services/PayNotifyService.php`
- 插件管理:`app/services/PluginService.php`
- 拉卡拉插件:`app/common/payment/LakalaPayment.php`
- 支付宝插件:`app/common/payment/AlipayPayment.php`
- 通道配置:`app/http/admin/controller/ChannelController.php`

View File

@@ -1,320 +0,0 @@
# MPay V2 Development Progress
更新日期2026-03-13
本文档用于记录当前项目完成度、明显缺口和建议推进顺序,方便后续继续开发时快速接手。
## 1. 当前总体判断
项目已经完成了“支付中台基础骨架 + 后台配置能力 + Epay 协议首条链路”的主体搭建。
更准确地说:
- 数据模型已经比较完整
- 后台配置能力已经具备可用性
- 支付流程主链路已经跑通到“下单并返回拉起参数”
- 真正需要继续补的是“真实渠道闭环、规则执行、异步补偿、通用协议扩展”
## 2. 已完成
### 2.1 基础框架与环境
- Webman 项目骨架已搭建
- MySQL / Redis / JWT / Cache / Event / Redis Queue 依赖已接入
- 管理后台与 API 路由已拆分
### 2.2 管理后台能力
- 验证码登录
- JWT 鉴权中间件
- 管理员信息查询
- 菜单与系统配置读取
- 商户 CRUD
- 商户应用 CRUD
- 支付方式 CRUD
- 支付插件注册 CRUD
- 支付通道 CRUD
- 订单列表 / 详情 / 后台发起退款
### 2.3 核心支付数据结构
已建表并落地:
- 商户
- 商户应用
- 支付方式
- 插件注册
- 支付通道
- 支付订单
- 回调日志
- 商户通知任务
- 回调幂等收件箱
### 2.4 下单主链路
已打通:
- Epay 参数校验
- Epay MD5 验签
- 商户应用识别
- 幂等订单创建
- 通道路由
- 插件实例化
- 插件下单
- 订单回写支付参数
- 返回兼容 Epay 的响应结构
### 2.5 支付状态基础设施
- 订单状态机服务已存在
- 成功 / 失败 / 全额退款关单状态迁移已定义
- 回调日志记录能力已存在
- 回调幂等收件箱已存在
- 商户通知任务创建逻辑已存在
### 2.6 插件体系
已建立统一插件契约:
- `PaymentInterface`
- `PayPluginInterface`
- `BasePayment`
说明后续继续接入新渠道时,整体扩展方式已经明确。
## 3. 部分完成
### 3.1 Epay 兼容是“主路径”,但还不是“全量兼容”
当前已实现:
- `submit.php`
- `mapi.php`
- `api.php?act=order`
- `api.php?act=refund`
`doc/epay.md` 中提到的一些能力,如 `query``settle``orders` 等,代码中暂未实现。
### 3.2 支付宝插件代码较完整,但未在当前数据库启用
现状:
- `AlipayPayment.php` 已实现
- 当前开发库 `ma_pay_plugin` 中只有 `lakala`
这意味着支付宝更多处于“代码已写好、配置未接入”的状态。
### 3.3 回调后通知商户的基础逻辑存在,但补偿闭环还不完整
已完成:
- 创建通知任务
- 发送通知
- 失败重试时间计算
待确认 / 待补齐:
- 当前没有看到明确的任务投递入口
- 也没有看到定时调度 `NotifyMerchantJob` 的配置闭环
- `NotifyMerchantJob` 虽然存在,但尚未形成明确的可运行消费链路
更保守地说,商户通知补偿链路还没有真正闭环。
## 4. 待完成
### 4.1 通用 OpenAPI
当前状态:
- `PayController` 只有骨架
- `create/query/close/refund` 都返回 `501`
- `OpenApiAuthMiddleware` 已存在,但未挂到路由
建议判断:这是下一阶段最适合补完的能力之一。
### 4.2 拉卡拉真实对接
当前状态:
- `LakalaPayment::pay()` 只返回 mock 二维码
- `query/close/refund/notify` 全部未实现
影响:
- 现在只能用于打通订单创建流程
- 还不能进行真实线上支付联调
### 4.3 通道路由规则执行
数据库已设计的字段很多,但运行期并未全部生效:
- `daily_limit` 未校验
- `daily_cnt` 未校验
- `min_amount` 未校验
- `max_amount` 未校验
- `split_ratio` 当前只存储,未看到清算分账逻辑
- 路由策略目前只是“按排序取第一条可用通道”
这块是项目从“能下单”走向“可运营”的关键缺口。
### 4.4 Epay 协议映射细节
当前内部支付方式代码使用:
- `alipay`
- `wechat`
但传统 Epay 常见值通常还有:
- `wxpay`
- `qqpay`
当前代码里没有看到统一别名映射层,说明“协议兼容”仍偏接口形态兼容,而不是完整字段语义兼容。
### 4.5 插件注册与初始化数据同步
代码、SQL、数据库现状存在轻微偏差
- `database/dev_seed.sql` 里准备了 `alipay``lakala`
- 当前开发库只看到 `lakala`
建议后续把“代码存在但数据库未启用”的状态统一起来,减少联调歧义。
### 4.6 通道安全与敏感配置
当前通道配置直接存在 `config_json` 中,后续建议补充:
- 敏感字段加密存储
- 后台展示脱敏
- 配置变更审计日志
### 4.7 测试体系
当前仓库里没有看到成体系的:
- 单元测试
- 协议测试
- 插件对接测试
- 回调幂等测试
- 退款回归测试
这会让后续迭代的回归成本越来越高。
## 5. 风险与注意点
### 5.1 当前“多通道”能力更偏配置层,而不是调度层
虽然表结构和后台已经支持多通道,但运行时路由还比较简单,不能完全体现:
- 限额控制
- 金额区间控制
- 通道健康度切换
- 优先级与容灾
### 5.2 退款能力目前偏基础版
当前退款服务已存在,但从实现上看:
- 更适合单次退款 / 全额退款
- 全额退款后直接把订单关闭
- 没有独立退款单模型
- 没有完整的部分退款累计能力
### 5.3 回调成功后的订单与通知一致性要继续加强
当前已经有:
- 幂等收件箱
- 状态机
- 通知任务表
这是很好的基础。
但真正生产级还建议再补:
- 事务边界说明
- 异常重放工具
- 回调人工补单工具
- 通知签名
## 6. 建议优先级
### P0优先补完直接影响可用性
1. 实现真实渠道插件,至少先补完一个可联调通道
2. 补完 OpenAPI 主链路
3. 在路由阶段执行金额限制 / 限额 / 笔数规则
4. 打通商户通知任务的实际调度与重试闭环
### P1补齐可运营能力
1. 增加支付方式别名映射,提升 Epay 兼容度
2.`AlipayPayment` 正式接入插件注册与通道配置
3. 增加后台对通道能力、产品、环境的可视化说明
4. 增加日志检索与问题排查手段
### P2走向平台化
1. 增加更多协议兼容层
2. 增加清算 / 分账 / 对账
3. 增加风控规则
4. 增加监控、告警、报表
## 7. 建议后续开发方向
### 方向一:先做“一个真实可用通道”
建议优先把某一个通道做成完整闭环:
- 下单
- 回调
- 查单
- 关单
- 退款
这样项目就能从“框架完成”升级为“真实可上线联调”。
### 方向二:补通用 OpenAPI
原因:
- 你已经明确后续可能兼容更多接口
- 当前通用控制器和鉴权中间件已经有雏形
- 补完之后,项目会从“单协议适配器”升级为“统一支付网关”
### 方向三:把通道路由做成真正的策略引擎
建议把下面这些字段从“仅存储”升级为“真实执行”:
- 金额范围
- 单日限额
- 单日限笔
- 通道优先级
- 通道健康状态
- 权重或降级策略
### 方向四:补测试与排障工具
优先建议增加:
- 下单幂等测试
- 回调幂等测试
- 退款状态测试
- 协议字段兼容测试
- 一键重发通知工具
## 8. 推荐继续开发顺序
如果下一次直接继续往下做,我建议按这个顺序推进:
1. 选定一个真实渠道作为首个闭环目标
2. 补完该插件的 `notify/query/refund/close`
3. 接入并验证商户通知补偿链路
4.`ChannelRouterService` 前后补齐通道规则校验
5. 正式实现 `PayController`
6. 抽象协议适配层,准备支持更多接口
7. 增加测试与后台排障能力
## 9. 当前一句话结论
这是一个“骨架已经成型、第一条协议已打通、非常适合继续往生产级推进”的支付中台项目;下一阶段的重点不是重写,而是把已有设计真正补成闭环。

View File

@@ -1,312 +0,0 @@
# MPAY V2 项目技术栈与结构文档
## 1. 项目概述
MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理系统,核心聚焦支付业务:商户管理、通道配置、统一支付、易支付兼容、商户通知等。管理后台提供管理员认证、菜单、系统配置、通道与插件管理;对外提供 OpenAPI 与易支付标准接口。
## 2. 技术架构
### 2.1 后端技术栈
| 类别 | 技术/框架 | 版本 | 用途 | 来源 |
|------|-----------|------|------|------|
| 基础框架 | Webman | ^2.1 | 高性能HTTP服务框架 | composer.json |
| PHP版本 | PHP | >=8.1 | 开发语言 | composer.json |
| 数据库 | webman/database | ^2.1 | 数据库操作 | composer.json |
| 缓存 | Redis | ^2.1 | 缓存存储 | composer.json |
| 缓存 | webman/cache | ^2.1 | 缓存管理 | composer.json |
| 认证 | JWT | ^7.0 | 管理员认证 | composer.json |
| 验证码 | webman/captcha | ^1.0 | 登录验证码 | composer.json |
| 事件系统 | webman/event | ^1.0 | 事件管理 | composer.json |
| 配置管理 | vlucas/phpdotenv | ^5.6 | 环境变量 | composer.json |
| 定时任务 | workerman/crontab | ^1.0 | 定时任务 | composer.json |
| 队列 | webman/redis-queue | ^2.1 | 消息队列 | composer.json |
| 验证 | topthink/think-validate | ^3.0 | 数据验证 | composer.json |
| 容器 | php-di/php-di | 7.0 | 依赖注入 | composer.json |
| 日志 | monolog/monolog | ^2.0 | 日志管理 | composer.json |
| 控制台 | webman/console | ^2.1 | 命令行工具 | composer.json |
### 2.2 前端技术栈
| 类别 | 技术/框架 | 版本 | 用途 | 来源 |
|------|-----------|------|------|------|
| 基础框架 | Vue | ^3.5.15 | 前端框架 | package.json |
| 语言 | TypeScript | ^5.2.2 | 开发语言 | package.json |
| 构建工具 | Vite | ^6.3.5 | 构建工具 | package.json |
| UI框架 | Arco Design | ^2.57.0 | 界面组件库 | package.json |
| 状态管理 | Pinia | ^2.3.0 | 状态管理 | package.json |
| 路由 | Vue Router | ^4.3.0 | 前端路由 | package.json |
| HTTP客户端 | Axios | ^1.6.8 | API调用 | package.json |
| 表单生成 | @form-create/arco-design | ^3.2.37 | 动态表单 | package.json |
| 图表 | @visactor/vchart | ^1.11.0 | 数据可视化 | package.json |
| 国际化 | vue-i18n | 10.0.0-alpha.3 | 多语言支持 | package.json |
| 工具库 | @vueuse/core | ^12.4.0 | 实用工具 | package.json |
| 二维码 | qrcode | ^1.5.4 | 二维码生成 | package.json |
## 3. 项目结构
### 3.1 后端目录结构
```
d:\phpstudy_pro\WWW\mpay\mpay_v2_webman\
├── app/ # 应用代码
│ ├── common/ # 通用代码
│ │ ├── base/ # 基础类
│ │ │ ├── BaseController.php
│ │ │ ├── BaseModel.php
│ │ │ ├── BaseRepository.php
│ │ │ └── BaseService.php
│ │ ├── contracts/ # 契约/接口
│ │ │ ├── PayPluginInterface.php
│ │ │ └── AbstractPayPlugin.php
│ │ ├── constants/ # 常量
│ │ ├── enums/ # 枚举
│ │ ├── middleware/ # 中间件Cors, StaticFile
│ │ ├── payment/ # 支付插件实现
│ │ │ └── LakalaPayment.php
│ │ └── utils/ # 工具类JwtUtil 等)
│ ├── events/ # 事件
│ ├── exceptions/ # 异常BadRequest, NotFound, Validation 等)
│ ├── http/
│ │ ├── admin/ # 管理后台
│ │ │ ├── controller/
│ │ │ │ ├── AuthController.php
│ │ │ │ ├── AdminController.php
│ │ │ │ ├── MenuController.php
│ │ │ │ ├── SystemController.php
│ │ │ │ ├── ChannelController.php
│ │ │ │ └── PluginController.php
│ │ │ └── middleware/
│ │ │ └── AuthMiddleware.php
│ │ └── api/ # 对外 API
│ │ ├── controller/
│ │ │ ├── PayController.php # OpenAPI 支付接口(骨架)
│ │ │ └── EpayController.php # 易支付接口submit.php/mapi.php/api.php
│ │ └── middleware/
│ │ ├── EpayAuthMiddleware.php
│ │ └── OpenApiAuthMiddleware.php
│ ├── jobs/ # 异步任务
│ │ └── NotifyMerchantJob.php
│ ├── models/ # 数据模型
│ │ ├── Admin.php
│ │ ├── Merchant.php
│ │ ├── MerchantApp.php
│ │ ├── PaymentMethod.php
│ │ ├── PaymentPlugin.php
│ │ ├── PaymentChannel.php
│ │ ├── PaymentOrder.php
│ │ ├── PaymentCallbackLog.php
│ │ ├── PaymentNotifyTask.php
│ │ └── SystemConfig.php
│ ├── repositories/ # 数据仓储
│ │ ├── AdminRepository.php
│ │ ├── MerchantRepository.php
│ │ ├── MerchantAppRepository.php
│ │ ├── PaymentMethodRepository.php
│ │ ├── PaymentPluginRepository.php
│ │ ├── PaymentChannelRepository.php
│ │ ├── PaymentOrderRepository.php
│ │ ├── PaymentNotifyTaskRepository.php
│ │ ├── PaymentCallbackLogRepository.php
│ │ └── SystemConfigRepository.php
│ ├── routes/ # 路由
│ │ ├── admin.php
│ │ ├── api.php
│ │ └── mer.php
│ ├── services/ # 业务逻辑
│ │ ├── AuthService.php
│ │ ├── AdminService.php
│ │ ├── CaptchaService.php
│ │ ├── MenuService.php
│ │ ├── SystemConfigService.php
│ │ ├── SystemSettingService.php
│ │ ├── PluginService.php # 插件注册与实例化
│ │ ├── ChannelRouterService.php # 通道路由(按商户+应用+支付方式选通道)
│ │ ├── PayOrderService.php # 订单创建、幂等、退款
│ │ ├── PayService.php # 统一下单、调用插件
│ │ ├── NotifyService.php # 商户通知、重试
│ │ └── api/
│ │ └── EpayService.php # 易支付业务封装
│ ├── validation/ # 验证器
│ │ ├── EpayValidator.php
│ │ └── SystemConfigValidator.php
│ └── process/ # 进程Http, Monitor
├── config/ # 配置文件
├── database/ # 数据库脚本
│ └── mvp_payment_tables.sql # 支付系统核心表ma_*
├── doc/ # 文档
│ ├── skill.md
│ ├── epay.md
│ ├── payment_flow.md
│ ├── validation.md
│ └── payment_system_implementation.md
├── public/
├── resource/
│ └── mpay_v2_admin/ # 前端项目
├── .env
└── composer.json
```
### 3.2 数据库表结构(`database/mvp_payment_tables.sql`
| 表名 | 说明 |
|------|------|
| ma_merchant | 商户表 |
| ma_merchant_app | 商户应用表api_type 区分 openapi/epay/custom |
| ma_pay_method | 支付方式字典alipay/wechat/unionpay |
| ma_pay_plugin | 支付插件注册表plugin_code 为主键) |
| ma_pay_channel | 支付通道表merchant_id, merchant_app_id, method_id 关联) |
| ma_pay_order | 支付订单表status: 0-PENDING, 1-SUCCESS, 2-FAIL, 3-CLOSED |
| ma_pay_callback_log | 支付回调日志表 |
| ma_notify_task | 商户通知任务表order_id, retry_cnt, next_retry_at |
| ma_system_config | 系统配置表 |
| ma_admin | 管理员表 |
### 3.3 前端目录结构
```
resource/mpay_v2_admin/
├── src/
│ ├── api/
│ ├── components/
│ ├── layout/
│ ├── router/
│ ├── store/
│ ├── views/
│ │ ├── login/
│ │ ├── home/
│ │ ├── finance/
│ │ ├── channel/
│ │ ├── analysis/
│ │ └── system/
│ ├── App.vue
│ └── main.ts
├── package.json
└── vite.config.ts
```
## 4. 核心功能模块
### 4.1 支付业务流程约定
1. **订单创建**`PayOrderService::createOrder`支持幂等merchant_id + merchant_app_id + mch_order_no 唯一)
2. **通道路由**`ChannelRouterService::chooseChannel(merchantId, merchantAppId, methodId)` 按第一个可用通道
3. **统一下单**`PayService::unifiedPay` → 创建订单 → 选通道 → 实例化插件 → 调用 `unifiedOrder`
4. **商户通知**`NotifyService::createNotifyTask``notify_url` 从订单 `extra['notify_url']` 获取
5. **通知重试**`NotifyMerchantJob` 定时拉取待重试任务,指数退避
### 4.2 支付插件接口
- `app/common/contracts/PayPluginInterface.php`
- `app/common/contracts/AbstractPayPlugin.php`
- 示例实现:`app/common/payment/LakalaPayment.php`
插件需实现:`getName``getSupportedMethods``getConfigSchema``getSupportedProducts``init``unifiedOrder``refund``verifyNotify` 等。
### 4.3 后端核心模块
| 模块 | 主要功能 | 文件位置 |
|------|----------|----------|
| 认证 | 管理员登录、验证码 | AuthController, AuthService |
| 管理员 | 获取管理员信息 | AdminController, AdminService, Admin 模型 |
| 菜单 | 获取路由菜单 | MenuController, MenuService |
| 系统 | 字典、配置管理 | SystemController, SystemConfigService |
| 通道管理 | 通道列表、详情、保存 | ChannelController, PaymentChannelRepository |
| 插件管理 | 插件列表、配置 Schema、产品列表 | PluginController, PluginService |
| 易支付 | submit.php/mapi.php/api.php | EpayController, EpayService |
### 4.4 前端核心模块
| 模块 | 主要功能 | 位置 |
|------|----------|------|
| 布局 | 系统整体布局 | src/layout/ |
| 认证 | 登录、权限控制 | src/views/login/ |
| 首页 | 数据概览 | src/views/home/ |
| 财务管理 | 结算、对账、发票 | src/views/finance/ |
| 渠道管理 | 通道配置、支付方式 | src/views/channel/ |
| 数据分析 | 交易分析、商户分析 | src/views/analysis/ |
| 系统设置 | 系统配置、字典管理 | src/views/system/ |
## 5. API 接口设计
### 5.1 管理后台(/adminapi
| 路径 | 方法 | 控制器 | 功能 | 权限 |
|------|------|--------|------|------|
| /adminapi/captcha | GET | AuthController | 获取验证码 | 无 |
| /adminapi/login | POST | AuthController | 管理员登录 | 无 |
| /adminapi/user/getUserInfo | GET | AdminController | 获取管理员信息 | JWT |
| /adminapi/menu/getRouters | GET | MenuController | 获取路由菜单 | JWT |
| /adminapi/system/getDict[/{code}] | GET | SystemController | 获取字典 | JWT |
| /adminapi/system/base-config/tabs | GET | SystemController | 获取配置标签 | JWT |
| /adminapi/system/base-config/form/{tabKey} | GET | SystemController | 获取表单配置 | JWT |
| /adminapi/system/base-config/submit/{tabKey} | POST | SystemController | 提交配置 | JWT |
| /adminapi/channel/list | GET | ChannelController | 通道列表 | JWT |
| /adminapi/channel/detail | GET | ChannelController | 通道详情 | JWT |
| /adminapi/channel/save | POST | ChannelController | 保存通道 | JWT |
| /adminapi/channel/plugins | GET | PluginController | 插件列表 | JWT |
| /adminapi/channel/plugin/config-schema | GET | PluginController | 插件配置 Schema | JWT |
| /adminapi/channel/plugin/products | GET | PluginController | 插件产品列表 | JWT |
### 5.2 易支付接口(对外 API
| 路径 | 方法 | 控制器 | 功能 | 说明 |
|------|------|--------|------|------|
| /submit.php | ANY | EpayController | 页面跳转支付 | 参数pid, key, out_trade_no, money, name, type, notify_url 等 |
| /mapi.php | POST | EpayController | API 接口支付 | 返回 trade_no、payurl/qrcode/urlscheme |
| /api.php | GET | EpayController | 订单查询/退款 | act=order 查询act=refund 退款 |
易支付约定:`pid` 映射为 `app_id`(商户应用标识),`key``app_secret`
## 6. 命名与约定
### 6.1 模型与仓储命名
- 业务语义命名:`PaymentMethod``PaymentOrder``PaymentChannel` 等,不使用 `ma` 前缀
- 表名仍为 `ma_*`,通过模型 `$table` 映射
### 6.2 订单相关字段
- 系统订单号:`order_id`
- 商户订单号:`mch_order_no`
- 商户ID`merchant_id`
- 商户应用ID`merchant_app_id`
- 通道ID`channel_id`
- 支付方式ID`method_id`(关联 ma_pay_method.id
### 6.3 商户应用 api_type
用于区分不同 API 的验签与通知方式:`openapi``epay``custom` 等。
## 7. 开发流程
### 7.1 后端开发
1. **环境**PHP 8.1+ComposerMySQLRedis
2. **依赖**`composer install`
3. **数据库**:执行 `database/mvp_payment_tables.sql`
4. **配置**:复制 `.env.example``.env`
5. **启动**
- Linux`php start.php start`
- Windows`php windows.php start`
### 7.2 前端开发
1. **环境**Node.js 18.12+PNPM 8.7+
2. **依赖**`pnpm install`
3. **开发**`pnpm dev`
4. **构建**`pnpm build:prod`
## 8. 相关文档
| 文件 | 说明 |
|------|------|
| doc/epay.md | 易支付接口说明 |
| doc/payment_flow.md | 支付流程说明 |
| doc/payment_system_implementation.md | 支付系统实现说明 |
| doc/validation.md | 验证规则说明 |
| database/mvp_payment_tables.sql | 支付系统表结构 |
## 9. 总结
MPAY V2 以支付业务为核心,采用 Webman + Vue 3 技术栈后端分层清晰Controller → Service → Repository → Model支持支付插件扩展与易支付兼容。管理后台基于 JWT 认证提供通道、插件、系统配置等管理能力对外提供易支付标准接口submit/mapi/api便于第三方商户接入。

View File

@@ -1,395 +0,0 @@
验证器 webman/validation
基于 illuminate/validation提供手动验证、注解验证、参数级验证以及可复用的规则集。
安装
composer require webman/validation
基本概念
规则集复用:通过继承 support\validation\Validator 定义可复用的 rules messages attributes scenes可在手动与注解中复用。
方法级注解Attribute验证使用 PHP 8 属性注解 #[Validate] 绑定控制器方法。
参数级注解Attribute验证使用 PHP 8 属性注解 #[Param] 绑定控制器方法参数。
异常处理:验证失败抛出 support\validation\ValidationException异常类可通过配置自定义
数据库验证:如果涉及数据库验证,需要安装 composer require webman/database
手动验证
基本用法
use support\validation\Validator;
$data = ['email' => 'user@example.com'];
Validator::make($data, [
'email' => 'required|email',
])->validate();
提示
validate() 校验失败会抛出 support\validation\ValidationException。如果你不希望抛异常请使用下方的 fails() 写法获取错误信息。
自定义 messages 与 attributes
use support\validation\Validator;
$data = ['contact' => 'user@example.com'];
Validator::make(
$data,
['contact' => 'required|email'],
['contact.email' => '邮箱格式不正确'],
['contact' => '邮箱']
)->validate();
不抛异常并获取错误信息
如果你不希望抛异常,可以使用 fails() 判断,并通过 errors()(返回 MessageBag获取错误信息
use support\validation\Validator;
$data = ['email' => 'bad-email'];
$validator = Validator::make($data, [
'email' => 'required|email',
]);
if ($validator->fails()) {
$firstError = $validator->errors()->first(); // string
$allErrors = $validator->errors()->all(); // array
$errorsByField = $validator->errors()->toArray(); // array
// 处理错误...
}
规则集复用(自定义 Validator
namespace app\validation;
use support\validation\Validator;
class UserValidator extends Validator
{
protected array $rules = [
'id' => 'required|integer|min:1',
'name' => 'required|string|min:2|max:20',
'email' => 'required|email',
];
protected array $messages = [
'name.required' => '姓名必填',
'email.required' => '邮箱必填',
'email.email' => '邮箱格式不正确',
];
protected array $attributes = [
'name' => '姓名',
'email' => '邮箱',
];
}
手动验证复用
use app\validation\UserValidator;
UserValidator::make($data)->validate();
使用 scenes可选
scenes 是可选能力,只有在你调用 withScene(...) 时,才会按场景只验证部分字段。
namespace app\validation;
use support\validation\Validator;
class UserValidator extends Validator
{
protected array $rules = [
'id' => 'required|integer|min:1',
'name' => 'required|string|min:2|max:20',
'email' => 'required|email',
];
protected array $scenes = [
'create' => ['name', 'email'],
'update' => ['id', 'name', 'email'],
];
}
use app\validation\UserValidator;
// 不指定场景 -> 验证全部规则
UserValidator::make($data)->validate();
// 指定场景 -> 只验证该场景包含的字段
UserValidator::make($data)->withScene('create')->validate();
注解验证(方法级)
直接规则
use support\Request;
use support\validation\annotation\Validate;
class AuthController
{
#[Validate(
rules: [
'email' => 'required|email',
'password' => 'required|string|min:6',
],
messages: [
'email.required' => '邮箱必填',
'password.required' => '密码必填',
],
attributes: [
'email' => '邮箱',
'password' => '密码',
]
)]
public function login(Request $request)
{
return json(['code' => 0, 'msg' => 'ok']);
}
}
复用规则集
use app\validation\UserValidator;
use support\Request;
use support\validation\annotation\Validate;
class UserController
{
#[Validate(validator: UserValidator::class, scene: 'create')]
public function create(Request $request)
{
return json(['code' => 0, 'msg' => 'ok']);
}
}
多重验证叠加
use support\validation\annotation\Validate;
class UserController
{
#[Validate(rules: ['email' => 'required|email'])]
#[Validate(rules: ['token' => 'required|string'])]
public function send()
{
return json(['code' => 0, 'msg' => 'ok']);
}
}
验证数据来源
use support\validation\annotation\Validate;
class UserController
{
#[Validate(
rules: ['email' => 'required|email'],
in: ['query', 'body', 'path']
)]
public function send()
{
return json(['code' => 0, 'msg' => 'ok']);
}
}
通过in参数来指定数据来源其中
query http请求的query参数取自 $request->get()
body http请求的包体取自 $request->post()
path http请求的路径参数取自 $request->route->param()
in可为字符串或数组为数组时按顺序合并后者覆盖前者。未传递in时默认等效于 ['query', 'body', 'path']。
参数级验证Param
基本用法
use support\validation\annotation\Param;
class MailController
{
public function send(
#[Param(rules: 'required|email')] string $from,
#[Param(rules: 'required|email')] string $to,
#[Param(rules: 'required|string|min:1|max:500')] string $content
) {
return json(['code' => 0, 'msg' => 'ok']);
}
}
验证数据来源
类似的参数级也支持in参数指定来源
use support\validation\annotation\Param;
class MailController
{
public function send(
#[Param(rules: 'required|email', in: ['body'])] string $from
) {
return json(['code' => 0, 'msg' => 'ok']);
}
}
rules 支持字符串或数组
use support\validation\annotation\Param;
class MailController
{
public function send(
#[Param(rules: ['required', 'email'])] string $from
) {
return json(['code' => 0, 'msg' => 'ok']);
}
}
自定义 messages / attribute
use support\validation\annotation\Param;
class UserController
{
public function updateEmail(
#[Param(
rules: 'required|email',
messages: ['email.email' => '邮箱格式不正确'],
attribute: '邮箱'
)]
string $email
) {
return json(['code' => 0, 'msg' => 'ok']);
}
}
规则常量复用
final class ParamRules
{
public const EMAIL = ['required', 'email'];
}
class UserController
{
public function send(
#[Param(rules: ParamRules::EMAIL)] string $email
) {
return json(['code' => 0, 'msg' => 'ok']);
}
}
方法级 + 参数级混合
use support\Request;
use support\validation\annotation\Param;
use support\validation\annotation\Validate;
class UserController
{
#[Validate(rules: ['token' => 'required|string'])]
public function send(
Request $request,
#[Param(rules: 'required|email')] string $from,
#[Param(rules: 'required|integer')] int $id
) {
return json(['code' => 0, 'msg' => 'ok']);
}
}
自动规则推导(基于参数签名)
当方法上使用 #[Validate],或该方法的任意参数使用了 #[Param] 时,本组件会根据方法参数签名自动推导并补全基础验证规则,再与已有规则合并后执行验证。
示例:#[Validate] 等价展开
1) 只开启 #[Validate],不手写规则:
use support\validation\annotation\Validate;
class DemoController
{
#[Validate]
public function create(string $content, int $uid)
{
}
}
等价于:
use support\validation\annotation\Validate;
class DemoController
{
#[Validate(rules: [
'content' => 'required|string',
'uid' => 'required|integer',
])]
public function create(string $content, int $uid)
{
}
}
2) 只写了部分规则,其余由参数签名补全:
use support\validation\annotation\Validate;
class DemoController
{
#[Validate(rules: [
'content' => 'min:2',
])]
public function create(string $content, int $uid)
{
}
}
等价于:
use support\validation\annotation\Validate;
class DemoController
{
#[Validate(rules: [
'content' => 'required|string|min:2',
'uid' => 'required|integer',
])]
public function create(string $content, int $uid)
{
}
}
3) 默认值/可空类型:
use support\validation\annotation\Validate;
class DemoController
{
#[Validate]
public function create(string $content = '默认值', ?int $uid = null)
{
}
}
等价于:
use support\validation\annotation\Validate;
class DemoController
{
#[Validate(rules: [
'content' => 'string',
'uid' => 'integer|nullable',
])]
public function create(string $content = '默认值', ?int $uid = null)
{
}
}
异常处理
默认异常
验证失败默认抛出 support\validation\ValidationException继承 Webman\Exception\BusinessException不会记录错误日志。
默认响应行为由 BusinessException::render() 处理:
普通请求:返回字符串消息,例如 token 为必填项。
JSON 请求:返回 JSON 响应,例如 {"code": 422, "msg": "token 为必填项。", "data":....}
通过自定义异常修改处理方式
全局配置config/plugin/webman/validation/app.php 的 exception
多语言支持
组件内置中英文语言包,并支持项目覆盖。加载顺序:
项目语言包 resource/translations/{locale}/validation.php
组件内置 vendor/webman/validation/resources/lang/{locale}/validation.php
Illuminate 内置英文(兜底)
提示
webman默认语言由 config/translation.php 配置,也可以通过函数 locale('en'); 更改。
本地覆盖示例
resource/translations/zh_CN/validation.php
return [
'email' => ':attribute 不是有效的邮件格式。',
];
中间件自动加载
组件安装后会通过 config/plugin/webman/validation/middleware.php 自动加载验证中间件,无需手动注册。
命令行生成注解
使用命令 make:validator 生成验证器类(默认生成到 app/validation 目录)。
提示
需要安装 composer require webman/console
基础用法
生成空模板
php webman make:validator UserValidator
覆盖已存在文件
php webman make:validator UserValidator --force
php webman make:validator UserValidator -f
从表结构生成规则
指定表名生成基础规则(会根据字段类型/可空/长度等推导 $rules默认排除字段与 ORM 相关laravel 为 created_at/updated_at/deleted_atthinkorm 为 create_time/update_time/delete_time
php webman make:validator UserValidator --table=wa_users
php webman make:validator UserValidator -t wa_users
指定数据库连接(多连接场景)
php webman make:validator UserValidator --table=wa_users --database=mysql
php webman make:validator UserValidator -t wa_users -d mysql
场景scenes
生成 CRUD 场景create/update/delete/detail
php webman make:validator UserValidator --table=wa_users --scenes=crud
php webman make:validator UserValidator -t wa_users -s crud
update 场景会包含主键字段用于定位记录以及其余字段delete/detail 默认仅包含主键字段。