更新数据库结构

This commit is contained in:
技术老胡
2026-03-10 13:47:28 +08:00
parent 54ad21ac8f
commit 9de902231f
54 changed files with 5070 additions and 501 deletions

145
doc/auth_strategy_design.md Normal file
View File

@@ -0,0 +1,145 @@
# 认证策略设计说明
## 设计理念
采用**策略模式**替代中间件方式处理认证,具有以下优势:
1. **灵活扩展**可以轻松添加新的接口标准如易支付、OpenAPI、自定义标准等
2. **按需使用**:控制器可以根据需要选择认证策略,而不是在路由层面强制
3. **易于测试**:策略类可以独立测试,不依赖中间件
4. **代码复用**:不同接口可以共享相同的认证逻辑
## 架构设计
### 1. 核心接口
**`AuthStrategyInterface`** - 认证策略接口
```php
interface AuthStrategyInterface
{
public function authenticate(Request $request): MerchantApp;
}
```
### 2. 策略实现
#### EpayAuthStrategy易支付认证
- 使用 `pid` + `key` + `MD5签名`
- 参数格式:`application/x-www-form-urlencoded`
- 签名算法MD5(排序后的参数字符串 + KEY)
#### OpenApiAuthStrategyOpenAPI认证
- 使用 `app_id` + `timestamp` + `nonce` + `HMAC-SHA256签名`
- 支持请求头或参数传递
- 签名算法HMAC-SHA256(签名字符串, app_secret)
### 3. 认证服务
**`AuthService`** - 认证服务,负责:
- 自动检测接口标准类型
- 根据类型选择对应的认证策略
- 支持手动注册新的认证策略
```php
// 自动检测
$app = $authService->authenticate($request);
// 指定策略类型
$app = $authService->authenticate($request, 'epay');
// 注册新策略
$authService->registerStrategy('custom', CustomAuthStrategy::class);
```
## 使用示例
### 控制器中使用
```php
class PayController extends BaseController
{
public function __construct(
protected PayOrderService $payOrderService,
protected AuthService $authService
) {
}
public function submit(Request $request)
{
// 自动检测或指定策略类型
$app = $this->authService->authenticate($request, 'epay');
// 使用 $app 进行后续业务处理
// ...
}
}
```
### 添加新的认证策略
1. **实现策略接口**
```php
class CustomAuthStrategy implements AuthStrategyInterface
{
public function authenticate(Request $request): MerchantApp
{
// 实现自定义认证逻辑
// ...
}
}
```
2. **注册策略**
```php
// 在服务提供者或启动文件中
$authService = new AuthService();
$authService->registerStrategy('custom', CustomAuthStrategy::class);
```
3. **在控制器中使用**
```php
$app = $this->authService->authenticate($request, 'custom');
```
## 自动检测机制
`AuthService` 会根据请求特征自动检测接口标准:
- **易支付**:检测到 `pid` 参数
- **OpenAPI**:检测到 `X-App-Id` 请求头或 `app_id` 参数
如果无法自动检测,可以手动指定策略类型。
## 优势对比
### 中间件方式(旧方案)
- ❌ 路由配置复杂,每个接口标准需要不同的中间件
- ❌ 难以在同一路由支持多种认证方式
- ❌ 扩展新标准需要修改路由配置
### 策略模式(新方案)
- ✅ 控制器按需选择认证策略
- ✅ 同一路由可以支持多种认证方式(通过参数区分)
- ✅ 扩展新标准只需实现策略接口并注册
- ✅ 代码更清晰,职责分离
## 路由配置
由于不再使用中间件,路由配置更简洁:
```php
// 易支付接口
Route::any('/submit.php', [PayController::class, 'submit']);
Route::post('/mapi.php', [PayController::class, 'mapi']);
Route::get('/api.php', [PayController::class, 'queryOrder']);
// 所有接口都在控制器内部进行认证,无需中间件
```
## 总结
通过策略模式重构认证逻辑,系统具备了:
- **高扩展性**:轻松添加新的接口标准
- **高灵活性**:控制器可以自由选择认证方式
- **高可维护性**:代码结构清晰,易于理解和维护

216
doc/epay.md Normal file
View File

@@ -0,0 +1,216 @@
协议规则
请求数据格式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 退款成功

214
doc/order_table_design.md Normal file
View File

@@ -0,0 +1,214 @@
# 支付订单表设计说明
## 一、订单表设计原因
### 1.1 订单号设计(双重订单号)
**系统订单号 (`pay_order_id`)**
- **作用**:系统内部唯一标识,用于查询、对账、退款等操作
- **生成规则**`P` + `YYYYMMDDHHmmss` + `6位随机数`P20240101120000123456
- **唯一性**:通过 `uk_pay_order_id` 唯一索引保证
- **优势**
- 全局唯一,不受商户影响
- 便于系统内部查询和关联
- 对账时作为主键
**商户订单号 (`mch_order_no`)**
- **作用**:商户传入的订单号,用于幂等性校验
- **唯一性**:通过 `uk_mch_order_no(merchant_id, mch_order_no)` 联合唯一索引保证
- **优势**
- 同一商户下订单号唯一,防止重复提交
- 商户侧可以自定义订单号规则
- 支持商户订单号查询订单
**为什么需要两个订单号?**
- 系统订单号:保证全局唯一,便于系统内部管理
- 商户订单号:保证商户侧唯一,防止重复支付(幂等性)
### 1.2 关联关系设计
**商户与应用关联 (`merchant_id` + `app_id`)**
- **作用**:标识订单所属商户和应用
- **用途**
- 权限控制(商户只能查询自己的订单)
- 对账统计(按商户/应用维度)
- 通知路由(根据应用配置的通知地址)
**支付通道关联 (`channel_id`)**
- **作用**:记录实际使用的支付通道
- **用途**
- 退款时找到对应的插件和配置
- 对账时关联通道信息
- 统计通道使用情况
**支付方式与产品 (`method_code` + `product_code`)**
- **method_code**支付方式alipay/wechat/unionpay
- 用于统计、筛选、报表
- **product_code**支付产品alipay_h5/alipay_life/wechat_jsapi等
- 由插件根据用户环境自动选择
- 用于记录实际使用的支付产品
### 1.3 金额字段设计
**订单金额 (`amount`)**
- 商户实际收款金额(扣除手续费前)
- 用于退款金额校验、对账
**手续费 (`fee`)**
- 可选字段,记录通道手续费
- 用于对账、结算、利润统计
- 如果不需要详细记录手续费,可以留空或通过 `extra` 存储
**币种 (`currency`)**
- 默认 CNY支持国际化扩展
- 预留字段,便于后续支持多币种
### 1.4 状态流转设计
```
PENDING待支付
├─> SUCCESS支付成功← 收到渠道回调并验签通过
├─> FAIL支付失败← 用户取消、超时、渠道返回失败
└─> CLOSED已关闭← 全额退款后
```
**状态说明**
- **PENDING**:订单创建后,等待用户支付
- **SUCCESS**:支付成功,已收到渠道回调并验签通过
- **FAIL**:支付失败(用户取消、订单超时、渠道返回失败等)
- **CLOSED**:已关闭(全额退款后)
### 1.5 渠道信息设计
**渠道订单号 (`channel_order_no`)**
- 渠道返回的订单号
- 用于查询订单状态、退款等操作
**渠道交易号 (`channel_trade_no`)**
- 部分渠道有交易号概念(如支付宝的 trade_no
- 用于对账、查询等
### 1.6 通知机制设计
**通知状态 (`notify_status`)**
- 0未通知
- 1已通知成功
**通知次数 (`notify_count`)**
- 记录通知次数,用于重试控制
- 配合 `ma_notify_task` 表实现异步通知
### 1.7 扩展性设计
**扩展字段 (`extra`)**
- JSON 格式,存储:
- 支付参数(`pay_params`):前端支付所需的参数
- 退款信息(`refund_info`):退款结果
- 自定义字段:业务扩展字段
**订单过期时间 (`expire_time`)**
- 用于自动关闭超时订单
- 默认 30 分钟,可配置
## 二、索引设计说明
### 2.1 唯一索引
- **`uk_pay_order_id`**:保证系统订单号唯一
- **`uk_mch_order_no(merchant_id, mch_order_no)`**:保证同一商户下商户订单号唯一(幂等性)
### 2.2 普通索引
- **`idx_merchant_app(merchant_id, app_id)`**:商户/应用维度查询
- **`idx_channel_id`**:通道维度查询
- **`idx_method_code`**:支付方式维度统计
- **`idx_status`**:状态筛选
- **`idx_pay_time`**:按支付时间查询(对账、统计)
- **`idx_created_at`**:按创建时间查询(分页、统计)
## 三、可能遗漏的字段(后续扩展)
### 3.1 退款相关字段
如果后续需要支持**部分退款**或**多次退款**,可以考虑添加:
```sql
`refund_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '已退款金额(累计)',
`refund_status` varchar(20) NOT NULL DEFAULT '' COMMENT '退款状态PENDING-退款中, SUCCESS-退款成功, FAIL-退款失败',
`refund_time` datetime DEFAULT NULL COMMENT '最后退款时间',
```
**当前设计**
- 退款信息存储在 `extra['refund_info']`
- 全额退款后订单状态改为 `CLOSED`
- 如果只需要全额退款,当前设计已足够
### 3.2 结算相关字段
如果后续需要**分账/结算**功能,可以考虑添加:
```sql
`settlement_status` varchar(20) NOT NULL DEFAULT '' COMMENT '结算状态PENDING-待结算, SUCCESS-已结算, FAIL-结算失败',
`settlement_time` datetime DEFAULT NULL COMMENT '结算时间',
`settlement_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '结算金额',
```
**当前设计**
- 结算信息可以通过 `extra` 存储
- 如果不需要复杂的结算流程,当前设计已足够
### 3.3 风控相关字段
如果需要**风控功能**,可以考虑添加:
```sql
`risk_level` varchar(20) NOT NULL DEFAULT '' COMMENT '风险等级LOW-低, MEDIUM-中, HIGH-高',
`risk_score` int(11) NOT NULL DEFAULT 0 COMMENT '风险评分',
`risk_reason` varchar(255) NOT NULL DEFAULT '' COMMENT '风险原因',
```
**当前设计**
- 风控信息可以通过 `extra` 存储
- 如果不需要复杂的风控系统,当前设计已足够
### 3.4 其他扩展字段
- **`user_id`**用户ID如果需要关联用户
- **`device_info`**:设备信息(用于风控)
- **`remark`**:备注(管理员备注)
- **`close_reason`**:关闭原因(用户取消/超时/管理员关闭等)
## 四、设计原则总结
1. **幂等性**:通过 `uk_mch_order_no` 保证同一商户下订单号唯一
2. **可追溯性**:记录完整的订单信息、渠道信息、时间信息
3. **可扩展性**:通过 `extra` JSON 字段存储扩展信息
4. **性能优化**:合理的索引设计,支持常见查询场景
5. **业务完整性**:覆盖订单全生命周期(创建→支付→退款→关闭)
## 五、与代码的对应关系
| SQL 字段 | 代码字段 | 说明 |
|---------|---------|------|
| `pay_order_id` | `pay_order_id` | 系统订单号 |
| `merchant_id` | `merchant_id` | 商户ID |
| `app_id` | `app_id` | 应用ID |
| `mch_order_no` | `mch_order_no` | 商户订单号 |
| `method_code` | `method_code` | 支付方式 |
| `product_code` | `product_code` | 支付产品 |
| `channel_id` | `channel_id` | 通道ID |
| `amount` | `amount` | 订单金额 |
| `currency` | `currency` | 币种 |
| `status` | `status` | 订单状态 |
| `channel_order_no` | `channel_order_no` | 渠道订单号 |
| `channel_trade_no` | `channel_trade_no` | 渠道交易号 |
| `extra` | `extra` | 扩展字段JSON |
## 六、注意事项
1. **字段命名统一**SQL 和代码中的字段名必须一致
2. **索引维护**:定期检查索引使用情况,优化慢查询
3. **数据归档**:历史订单数据量大时,考虑归档策略
4. **JSON 字段**`extra` 字段使用 JSON 类型,便于扩展但查询性能略低
5. **时间字段**`pay_time``expire_time` 等时间字段使用 `datetime` 类型,便于查询和统计

485
doc/payment_flow.md Normal file
View File

@@ -0,0 +1,485 @@
# 支付订单发起流程说明
## 一、业务系统调用统一下单接口
### 1. 接口地址
```
POST /api/pay/unifiedOrder
```
### 2. 请求头(签名认证)
```
X-App-Id: app001 # 应用ID
X-Timestamp: 1704067200 # 时间戳Unix秒
X-Nonce: abc123xyz # 随机字符串
X-Signature: calculated_signature # 签名HMAC-SHA256
Content-Type: application/json
```
### 3. 签名算法
**待签名字符串**
```
app_id={app_id}&timestamp={timestamp}&nonce={nonce}&method=POST&path=/api/pay/unifiedOrder&body_sha256={body_sha256}
```
**计算签名**
```php
$bodySha256 = hash('sha256', json_encode($requestBody));
$signString = "app_id={app_id}&timestamp={timestamp}&nonce={nonce}&method=POST&path=/api/pay/unifiedOrder&body_sha256={bodySha256}";
$signature = hash_hmac('sha256', $signString, $appSecret);
```
### 4. 请求体示例
```json
{
"mch_order_no": "ORDER202401011200001",
"pay_method": "alipay",
"amount": 100.00,
"currency": "CNY",
"subject": "测试商品",
"body": "测试商品描述"
}
```
**字段说明**
- `mch_order_no`:商户订单号(必填,唯一,用于幂等)
- `pay_method`支付方式必填alipay、wechat、unionpay
- `amount`:订单金额(必填,单位:元)
- `currency`币种可选默认CNY
- `subject`:订单标题(必填)
- `body`:订单描述(可选)
### 5. 调用示例cURL
```bash
curl -X POST http://localhost:8787/api/pay/unifiedOrder \
-H "Content-Type: application/json" \
-H "X-App-Id: app001" \
-H "X-Timestamp: 1704067200" \
-H "X-Nonce: abc123xyz" \
-H "X-Signature: calculated_signature" \
-d '{
"mch_order_no": "ORDER202401011200001",
"pay_method": "alipay",
"amount": 100.00,
"subject": "测试商品",
"body": "测试商品描述"
}'
```
### 6. PHP调用示例
```php
<?php
$appId = 'app001';
$appSecret = 'your_app_secret';
$baseUrl = 'http://localhost:8787';
// 准备请求数据
$requestBody = [
'mch_order_no' => 'ORDER202401011200001',
'pay_method' => 'alipay',
'amount' => 100.00,
'subject' => '测试商品',
'body' => '测试商品描述'
];
// 计算签名
$timestamp = time();
$nonce = uniqid();
$bodyJson = json_encode($requestBody);
$bodySha256 = hash('sha256', $bodyJson);
$signString = "app_id={$appId}&timestamp={$timestamp}&nonce={$nonce}&method=POST&path=/api/pay/unifiedOrder&body_sha256={$bodySha256}";
$signature = hash_hmac('sha256', $signString, $appSecret);
// 发送请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '/api/pay/unifiedOrder');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
"X-App-Id: {$appId}",
"X-Timestamp: {$timestamp}",
"X-Nonce: {$nonce}",
"X-Signature: {$signature}",
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode === 200 && $result['code'] === 200) {
echo "支付订单号:" . $result['data']['pay_order_id'] . "\n";
echo "支付参数:" . json_encode($result['data']['pay_params'], JSON_UNESCAPED_UNICODE) . "\n";
} else {
echo "错误:" . $result['msg'] . "\n";
}
```
## 二、服务端处理流程
### 流程图
```mermaid
sequenceDiagram
participant BizSystem as 业务系统
participant OpenAPI as OpenAPI接口
participant AuthMW as 签名中间件
participant PayService as 订单服务
participant ChannelRouter as 通道路由
participant PluginFactory as 插件工厂
participant Plugin as 支付插件
participant Channel as 第三方渠道
BizSystem->>OpenAPI: POST /api/pay/unifiedOrder
OpenAPI->>AuthMW: 验证签名
AuthMW->>PayService: 调用统一下单
PayService->>PayService: 1. 验证商户应用
PayService->>PayService: 2. 幂等校验
PayService->>PayService: 3. 创建支付订单
PayService->>ChannelRouter: 4. 选择通道
ChannelRouter-->>PayService: 返回通道信息
PayService->>PluginFactory: 5. 实例化插件
PluginFactory-->>PayService: 返回插件实例
PayService->>Plugin: 6. 初始化插件(init)
PayService->>Plugin: 7. 环境检测
PayService->>Plugin: 8. 调用统一下单(unifiedOrder)
Plugin->>Plugin: 8.1 根据环境选择产品
Plugin->>Channel: 8.2 调用第三方接口
Channel-->>Plugin: 返回支付参数
Plugin-->>PayService: 返回支付结果
PayService->>PayService: 9. 更新订单信息
PayService-->>OpenAPI: 返回结果
OpenAPI-->>BizSystem: 返回支付参数
```
### 详细步骤说明
#### 步骤1签名验证中间件
- `OpenApiAuthMiddleware` 验证请求头中的签名
- 验证时间戳5分钟内有效
- 验证签名是否正确
- 将应用信息注入到请求对象
#### 步骤2验证商户应用
- 根据 `app_id` 查询 `ma_merchant_app`
- 检查应用状态是否启用
#### 步骤3幂等校验
- 根据 `merchant_id + mch_order_no` 查询是否已存在订单
- 如果存在,直接返回已有订单信息(支持幂等)
#### 步骤4创建支付订单
- 生成支付订单号(格式:`P20240101120000123456`
- 创建 `ma_pay_order` 记录
- 状态:`PENDING`(待支付)
- 过期时间30分钟后
#### 步骤5通道路由选择
- 根据 `merchant_id + app_id + method_code` 查找可用通道
-`ma_pay_channel` 表中查询
- 选择第一个可用的通道(后续可扩展权重、容灾策略)
#### 步骤6实例化插件
-`PayService` 中根据 `ma_pay_plugin` 注册表解析插件:优先使用表中的 `class_name`,否则按约定使用 `app\common\payment\{Code}Payment` 实例化插件
- 例如:`plugin_code = 'lakala'` → 实例化 `LakalaPlugin`
#### 步骤7初始化插件
- 调用 `$plugin->init($methodCode, $channelConfig)`
- 插件内部切换到指定支付方式的配置和逻辑
- 例如:拉卡拉插件初始化到 `alipay` 模式
#### 步骤8环境检测
- 从请求头 `User-Agent` 判断用户环境
- 环境类型:
- `PC`PC桌面浏览器
- `H5`H5手机浏览器
- `WECHAT`:微信内浏览器
- `ALIPAY_CLIENT`:支付宝客户端
#### 步骤9调用插件统一下单
- 调用 `$plugin->unifiedOrder($orderData, $channelConfig, $env)`
- 插件内部处理:
1. **产品选择**:从通道的 `enabled_products` 中,根据环境自动选择一个产品
- 例如H5环境 → 选择 `alipay_h5`
- 例如:支付宝客户端 → 选择 `alipay_life`
2. **调用第三方接口**:根据产品和支付方式,调用对应的第三方支付接口
- 例如拉卡拉插件的支付宝H5接口
3. **返回支付参数**:返回给业务系统的支付参数
#### 步骤10更新订单
- 更新订单的 `product_code`(实际使用的产品)
- 更新订单的 `channel_id`
- 更新订单的 `channel_order_no`(渠道订单号)
- 保存 `pay_params``extra` 字段
## 三、响应数据格式
### 成功响应
```json
{
"code": 200,
"msg": "success",
"data": {
"pay_order_id": "P20240101120000123456",
"status": "PENDING",
"pay_params": {
"type": "redirect",
"url": "https://mapi.alipay.com/gateway.do?..."
}
}
}
```
### 支付参数类型
根据不同的支付产品和环境,`pay_params` 的格式不同:
#### 1. 跳转支付H5/PC扫码
```json
{
"type": "redirect",
"url": "https://mapi.alipay.com/gateway.do?xxx"
}
```
业务系统需要:**跳转到该URL**
#### 2. 表单提交H5
```json
{
"type": "form",
"method": "POST",
"action": "https://mapi.alipay.com/gateway.do",
"fields": {
"app_id": "xxx",
"method": "alipay.trade.wap.pay",
"biz_content": "{...}"
}
}
```
业务系统需要:**自动提交表单**
#### 3. JSAPI支付微信内/支付宝生活号)
```json
{
"type": "jsapi",
"appId": "wx1234567890",
"timeStamp": "1704067200",
"nonceStr": "abc123",
"package": "prepay_id=wx1234567890",
"signType": "MD5",
"paySign": "calculated_signature"
}
```
业务系统需要:**调用微信/支付宝JSAPI**
#### 4. 二维码支付PC扫码
```json
{
"type": "qrcode",
"qrcode_url": "https://qr.alipay.com/xxx",
"qrcode_data": "data:image/png;base64,..."
}
```
业务系统需要:**展示二维码**
## 四、用户支付流程
### 1. 业务系统处理支付参数
根据 `pay_params.type` 进行不同处理:
```javascript
// 前端处理示例
const payParams = response.data.pay_params;
switch (payParams.type) {
case 'redirect':
// 跳转支付
window.location.href = payParams.url;
break;
case 'form':
// 表单提交
const form = document.createElement('form');
form.method = payParams.method;
form.action = payParams.action;
Object.keys(payParams.fields).forEach(key => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = payParams.fields[key];
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
break;
case 'jsapi':
// 微信JSAPI支付
WeixinJSBridge.invoke('getBrandWCPayRequest', {
appId: payParams.appId,
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign
}, function(res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
// 支付成功
}
});
break;
case 'qrcode':
// 展示二维码
document.getElementById('qrcode').src = payParams.qrcode_data;
break;
}
```
### 2. 用户完成支付
- 用户在第三方支付平台完成支付
- 第三方平台异步回调到支付中心
### 3. 支付中心处理回调
- 接收回调:`POST /api/notify/alipay``/api/notify/wechat`
- 验签:使用插件验证回调签名
- 更新订单状态:`PENDING``SUCCESS``FAIL`
- 创建通知任务:异步通知业务系统
### 4. 业务系统接收通知
- 支付中心异步通知业务系统的 `notify_url`
- 业务系统验证签名并处理订单
## 五、查询订单接口
### 接口地址
```
GET /api/pay/query?pay_order_id=P20240101120000123456
```
### 请求头(需要签名)
```
X-App-Id: app001
X-Timestamp: 1704067200
X-Nonce: abc123xyz
X-Signature: calculated_signature
```
### 响应示例
```json
{
"code": 200,
"msg": "success",
"data": {
"pay_order_id": "P20240101120000123456",
"mch_order_no": "ORDER202401011200001",
"status": "SUCCESS",
"amount": 100.00,
"pay_time": "2024-01-01 12:00:30"
}
}
```
## 六、完整调用示例Node.js
```javascript
const crypto = require('crypto');
const axios = require('axios');
class PaymentClient {
constructor(appId, appSecret, baseUrl) {
this.appId = appId;
this.appSecret = appSecret;
this.baseUrl = baseUrl;
}
// 计算签名
calculateSignature(method, path, body, timestamp, nonce) {
const bodySha256 = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
const signString = `app_id=${this.appId}&timestamp=${timestamp}&nonce=${nonce}&method=${method}&path=${path}&body_sha256=${bodySha256}`;
return crypto.createHmac('sha256', this.appSecret).update(signString).digest('hex');
}
// 统一下单
async unifiedOrder(orderData) {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = Math.random().toString(36).substring(7);
const method = 'POST';
const path = '/api/pay/unifiedOrder';
const signature = this.calculateSignature(method, path, orderData, timestamp, nonce);
const response = await axios.post(`${this.baseUrl}${path}`, orderData, {
headers: {
'Content-Type': 'application/json',
'X-App-Id': this.appId,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature
}
});
return response.data;
}
// 查询订单
async queryOrder(payOrderId) {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = Math.random().toString(36).substring(7);
const method = 'GET';
const path = `/api/pay/query?pay_order_id=${payOrderId}`;
const signature = this.calculateSignature(method, path, {}, timestamp, nonce);
const response = await axios.get(`${this.baseUrl}${path}`, {
headers: {
'X-App-Id': this.appId,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature
}
});
return response.data;
}
}
// 使用示例
const client = new PaymentClient('app001', 'your_app_secret', 'http://localhost:8787');
// 统一下单
client.unifiedOrder({
mch_order_no: 'ORDER202401011200001',
pay_method: 'alipay',
amount: 100.00,
subject: '测试商品',
body: '测试商品描述'
}).then(result => {
console.log('支付参数:', result.data.pay_params);
// 根据 pay_params.type 处理支付
}).catch(err => {
console.error('下单失败:', err.message);
});
```
## 七、注意事项
1. **幂等性**:相同的 `mch_order_no` 多次调用,返回同一订单信息
2. **签名有效期**时间戳5分钟内有效
3. **订单过期**订单默认30分钟过期
4. **环境检测**系统自动根据UA判断环境选择合适的产品
5. **异步通知**:支付成功后,系统会异步通知业务系统的 `notify_url`
6. **订单查询**:业务系统可通过查询接口主动查询订单状态

View File

@@ -0,0 +1,182 @@
# 支付系统核心实现说明
## 概述
已实现支付系统核心功能,包括:
- 插件化支付通道系统(支持一个插件多个支付方式)
- OpenAPI统一支付网关
- 通道管理与配置
- 订单管理与状态机
- 异步通知机制
## 数据库初始化
执行以下SQL脚本创建表结构
```bash
mysql -u用户名 -p 数据库名 < database/mvp_payment_tables.sql
```
## 核心架构
### 1. 插件系统
- **插件接口**`app/common/contracts/PayPluginInterface.php`
- **抽象基类**`app/common/contracts/AbstractPayPlugin.php`(提供环境检测、产品选择等通用功能)
- **插件类示例**`app/common/payment/LakalaPayment.php`(命名规范:`XxxPayment`
- **插件解析**:由 `PayService``PayOrderService``PluginService` 直接根据 `ma_pay_plugin` 注册表中配置的 `plugin_code` / `class_name` 解析并实例化插件(默认约定类名为 `app\common\payment\{Code}Payment`
**插件特点**
- 一个插件可以支持多个支付方式(如拉卡拉插件支持 alipay/wechat/unionpay
- **支付产品由插件内部定义**,不需要数据库字典表
- 插件根据用户环境PC/H5/微信内/支付宝客户端)自动选择已开通的产品
- 通道配置中,用户只需勾选确认开启了哪些产品(产品编码由插件定义)
- 有些支付平台不区分产品,插件会根据通道配置自行处理
- 通道配置表单由插件动态生成
### 2. 数据模型
- `Merchant`:商户
- `MerchantApp`商户应用AppId/AppSecret
- `PayMethod`支付方式alipay/wechat等
- `PayChannel`:支付通道(绑定到"插件+支付方式",配置已开通的产品列表)
- `PayOrder`:支付订单
- `NotifyTask`:商户通知任务
**注意**:支付产品不由数据库管理,而是由插件通过 `getSupportedProducts()` 方法定义。通道配置中的 `enabled_products` 字段存储的是用户勾选的产品编码数组。
### 3. 服务层
- `PayOrderService`:订单业务编排(统一下单、查询)
- `ChannelRouterService`:通道路由选择
- `NotifyService`:商户通知服务
### 4. API接口
#### OpenAPI对外支付网关
- `POST /api/pay/unifiedOrder`:统一下单(需要签名认证)
- `GET /api/pay/query`:查询订单(需要签名认证)
- `POST /api/notify/alipay`:支付宝回调
- `POST /api/notify/wechat`:微信回调
#### 管理后台API
- `GET /adminapi/channel/plugins`:获取所有可用插件
- `GET /adminapi/channel/plugin/config-schema`获取插件配置表单Schema
- `GET /adminapi/channel/plugin/products`:获取插件支持的支付产品
- `GET /adminapi/channel/list`:通道列表
- `GET /adminapi/channel/detail`:通道详情
- `POST /adminapi/channel/save`:保存通道
## 使用流程
### 1. 创建商户和应用
```sql
INSERT INTO ma_merchant (merchant_no, merchant_name, funds_mode, status)
VALUES ('M001', '测试商户', 'direct', 1);
INSERT INTO ma_merchant_app (merchant_id, app_id, app_secret, app_name, notify_url, status)
VALUES (1, 'app001', 'secret_key_here', '测试应用', 'https://example.com/notify', 1);
```
### 2. 配置支付通道
**配置流程**
1. 创建通道:选择支付方式、支付插件,配置通道基本信息(显示名称、分成比例、通道成本、通道模式、限额等)
2. 配置插件参数:通道创建后,再配置该通道的插件参数信息(通过插件的配置表单动态生成)
通过管理后台或直接操作数据库:
```sql
INSERT INTO ma_pay_channel (
merchant_id, app_id, channel_code, channel_name,
plugin_code, method_code, enabled_products, config_json,
split_ratio, channel_cost, channel_mode,
daily_limit, daily_count, min_amount, max_amount,
status
) VALUES (
1, 1, 'CH001', '拉卡拉-支付宝通道',
'lakala', 'alipay',
'["alipay_h5", "alipay_life"]',
'{"merchant_id": "lakala_merchant", "secret_key": "xxx", "api_url": "https://api.lakala.com"}',
100.00, 0.00, 'wallet',
0.00, 0, NULL, NULL,
1
);
```
**通道字段说明**
- `split_ratio`: 分成比例(%默认100.00
- `channel_cost`: 通道成本(%默认0.00
- `channel_mode`: 通道模式,`wallet`-支付金额扣除手续费后加入商户余额,`direct`-直连到商户
- `daily_limit`: 单日限额0表示不限制
- `daily_count`: 单日限笔0表示不限制
- `min_amount`: 单笔最小金额NULL表示不限制
- `max_amount`: 单笔最大金额NULL表示不限制
### 3. 调用统一下单接口
```bash
curl -X POST http://localhost:8787/api/pay/unifiedOrder \
-H "X-App-Id: app001" \
-H "X-Timestamp: 1234567890" \
-H "X-Nonce: abc123" \
-H "X-Signature: calculated_signature" \
-d '{
"mch_order_no": "ORDER001",
"pay_method": "alipay",
"amount": 100.00,
"subject": "测试订单",
"body": "测试订单描述"
}'
```
### 4. 签名算法
```
signString = "app_id={app_id}&timestamp={timestamp}&nonce={nonce}&method={method}&path={path}&body_sha256={body_sha256}"
signature = HMAC-SHA256(signString, app_secret)
```
## 扩展新插件
1. 创建插件类,继承 `AbstractPayPlugin`,并按照 `XxxPayment` 命名放在 `app/common/payment` 目录:
```php
namespace app\common\payment;
use app\common\contracts\AbstractPayPlugin;
class AlipayPayment extends AbstractPayPlugin
{
public static function getCode(): string { return 'alipay'; }
public static function getName(): string { return '支付宝直连'; }
public static function getSupportedMethods(): array { return ['alipay']; }
// ... 实现其他方法
}
```
2.`ma_pay_plugin` 表中注册插件信息(也可通过后台管理界面维护):
```sql
INSERT INTO ma_pay_plugin (plugin_code, plugin_name, class_name, status)
VALUES ('alipay', '支付宝直连', 'app\\common\\payment\\AlipayPayment', 1);
```
## 注意事项
1. **支付产品定义**:支付产品由插件内部通过 `getSupportedProducts()` 方法定义,不需要数据库字典表。通道配置时,用户只需勾选已开通的产品编码。
2. **环境检测**:插件基类提供 `detectEnvironment()` 方法可根据UA判断环境
3. **产品选择**:插件根据环境从通道已开通产品中自动选择。如果通道配置为空或不区分产品,插件会根据配置自行处理。
4. **通知重试**:使用 `NotifyMerchantJob` 异步重试通知,支持指数退避
5. **幂等性**:统一下单接口支持幂等,相同 `mch_order_no` 返回已有订单
## 后续扩展
- 账务系统(账户、分录、余额)
- 结算系统(可结算金额、结算批次、打款)
- 对账系统(渠道账单导入、差异处理)
- 风控系统(规则引擎、风险预警)

View File

@@ -2,7 +2,7 @@
## 1. 项目概述
MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理系统,提供完整的支付业务管理功能,包括用户认证、菜单管理、系统配置、财务管理、渠道管理和数据分析等核心模块
MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理系统,核心聚焦支付业务:商户管理、通道配置、统一支付、易支付兼容、商户通知等。管理后台提供管理员认证、菜单、系统配置、通道与插件管理;对外提供 OpenAPI 与易支付标准接口
## 2. 技术架构
@@ -10,46 +10,38 @@ MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理
| 类别 | 技术/框架 | 版本 | 用途 | 来源 |
|------|-----------|------|------|------|
| 基础框架 | Webman | ^2.1 | 高性能HTTP服务框架 | composer.json:28 |
| PHP版本 | PHP | >=8.1 | 开发语言 | composer.json:27 |
| 数据库 | webman/database | ^2.1 | 数据库操作 | composer.json:31 |
| 缓存 | Redis | ^2.1 | 缓存存储 | composer.json:32 |
| 缓存 | webman/cache | ^2.1 | 缓存管理 | composer.json:34 |
| 认证 | JWT | ^7.0 | 用户认证 | composer.json:42 |
| 验证码 | webman/captcha | ^1.0 | 登录验证码 | composer.json:37 |
| 事件系统 | webman/event | ^1.0 | 事件管理 | composer.json:38 |
| 配置管理 | vlucas/phpdotenv | ^5.6 | 环境变量 | composer.json:39 |
| 定时任务 | workerman/crontab | ^1.0 | 定时任务 | composer.json:40 |
| 队列 | webman/redis-queue | ^2.1 | 消息队列 | composer.json:41 |
| 验证 | topthink/think-validate | ^3.0 | 数据验证 | composer.json:36 |
| 容器 | php-di/php-di | 7.0 | 依赖注入 | composer.json:30 |
| 日志 | monolog/monolog | ^2.0 | 日志管理 | composer.json:29 |
| 控制台 | webman/console | ^2.1 | 命令行工具 | composer.json:35 |
| 基础框架 | 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:61 |
| 语言 | TypeScript | ^5.2.2 | 开发语言 | package.json:103 |
| 构建工具 | Vite | ^6.3.5 | 构建工具 | package.json:107 |
| UI框架 | Arco Design | ^2.57.0 | 界面组件库 | package.json:72 |
| 状态管理 | Pinia | ^2.3.0 | 状态管理 | package.json:53 |
| 路由 | Vue Router | ^4.3.0 | 前端路由 | package.json:66 |
| HTTP客户端 | Axios | ^1.6.8 | API调用 | package.json:47 |
| 表单生成 | @form-create/arco-design | ^3.2.37 | 动态表单 | package.json:41 |
| 图表 | @visactor/vchart | ^1.11.0 | 数据可视化 | package.json:42 |
| 代码编辑器 | CodeMirror | ^6.0.1 | 代码编辑 | package.json:48 |
| 富文本编辑器 | @wangeditor/editor | ^5.1.23 | 内容编辑 | package.json:45 |
| 国际化 | vue-i18n | 10.0.0-alpha.3 | 多语言支持 | package.json:64 |
| 工具库 | @vueuse/core | ^12.4.0 | 实用工具 | package.json:44 |
| 指纹识别 | @fingerprintjs/fingerprintjs | ^4.6.2 | 设备识别 | package.json:40 |
| 二维码 | qrcode | ^1.5.4 | 二维码生成 | package.json:57 |
| 条码 | jsbarcode | ^3.11.6 | 条码生成 | package.json:51 |
| 打印 | print-js | ^1.6.0 | 页面打印 | package.json:56 |
| 进度条 | nprogress | ^0.2.0 | 加载进度 | package.json:52 |
| 中文转拼音 | pinyin-pro | ^3.26.0 | 拼音转换 | package.json:55 |
| 引导 | driver.js | ^1.3.1 | 功能引导 | package.json:49 |
| 基础框架 | 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. 项目结构
@@ -57,267 +49,264 @@ MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理
```
d:\phpstudy_pro\WWW\mpay\mpay_v2_webman\
├── app/ # 应用代码
│ ├── common/ # 通用代码
│ │ ├── base/ # 基础类
├── app/ # 应用代码
│ ├── common/ # 通用代码
│ │ ├── base/ # 基础类
│ │ │ ├── BaseController.php
│ │ │ ├── BaseModel.php
│ │ │ ├── BaseRepository.php
│ │ │ └── BaseService.php
│ │ ├── constants/ # 常量
│ │ │ ── YesNo.php
│ │ ├── enums/ # 枚举
│ │ │ └── MenuType.php
│ │ ├── middleware/ # 中间件
│ │ │ ├── Cors.php
│ │ │ └── StaticFile.php
│ │ └── utils/ # 工具类
│ │ └── JwtUtil.php
│ ├── events/ # 事件
│ └── SystemConfig.php
│ ├── exceptions/ # 异常处理
│ │ ── ValidationException.php
├── http/ # HTTP相关
│ │ ├── admin/ # 后台管理
│ │ │ ├── controller/ # 控制器
│ │ ├── 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
│ │ │ │ ── UserController.php
│ │ │ └── middleware/ # 中间件
│ │ │ │ ── ChannelController.php
│ │ │ │ └── PluginController.php
│ │ │ └── middleware/
│ │ │ └── AuthMiddleware.php
├── models/ # 数据模型
│ │ ├── SystemConfig.php
│ │ └── User.php
├── process/ # 进程管理
│ │ ├── Http.php
│ │ └── Monitor.php
├── repositories/ # 数据仓库
│ ├── SystemConfigRepository.php
│ │ └── UserRepository.php
│ ├── routes/ # 路由配置
│ └── 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/ # 业务逻辑
│ ├── services/ # 业务逻辑
│ │ ├── AuthService.php
│ │ ├── AdminService.php
│ │ ├── CaptchaService.php
│ │ ├── MenuService.php
│ │ ├── SystemConfigService.php
│ │ ├── SystemSettingService.php
│ │ ── UserService.php
└── validation/ # 数据验证
── SystemConfigValidator.php
├── config/ # 配置文
│ ├── base-config/ # 基础配置
│ │ ── basic.json
│ │ ├── email.json
│ ├── permission.json
│ │ ── tabs.json
├── plugin/ # 插件配置
│ ├── webman/
├── console/
├── event/
│ │ ├── redis-queue/
└── validation/
│ ├── system-file/ # 系统文件
│ ├── dict.json
│ ├── menu.json
│ └── menu.md
── app.php
│ ├── autoload.php
│ ├── bootstrap.php
── cache.php
│ ├── container.php
│ ├── database.php
│ ├── dependence.php
│ ├── event.php
│ ├── exception.php
│ ├── jwt.php
│ ├── log.php
│ ├── menu.php
│ ├── middleware.php
│ ├── process.php
│ ├── redis.php
│ ├── route.php
│ ├── server.php
│ ├── session.php
│ ├── static.php
│ ├── translation.php
│ └── view.php
├── database/ # 数据库文件
│ └── ma_system_config.sql
├── doc/ # 文档
│ ├── event.md
│ └── exception.md
├── public/ # 静态资源
│ └── favicon.ico
├── resource/ # 资源文件
│ └── mpay_v2_admin/ # 前端项目
├── .env # 环境变量
├── composer.json # PHP依赖
└── composer.lock # 依赖锁定
│ │ ── 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 前端目录结构
### 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 前端目录结构
```
d:\phpstudy_pro\WWW\mpay\mpay_v2_webman\resource\mpay_v2_admin\
├── src/ # 源代码
│ ├── api/ # API调用
│ ├── assets/ # 静态资源
│ ├── components/ # 组件
│ ├── config/ # 配置
│ ├── directives/ # 指令
│ ├── hooks/ # 钩子
│ ├── lang/ # 国际化
│ ├── layout/ # 布局
│ ├── mock/ # 模拟数据
│ ├── router/ # 路由
│ ├── store/ # 状态管理
├── style/ # 样式
│ ├── typings/ # 类型定义
── utils/ # 工具函数
│ ├── views/ # 页面
│ ├── App.vue # 根组件
│ ├── auto-import.d.ts # 自动导入
│ ├── components.d.ts # 组件声明
│ ├── main.ts # 入口文件
│ └── style.css # 全局样式
├── build/ # 构建配置
│ ├── optimize.ts
│ └── vite-plugin.ts
├── .env # 环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .env.test # 测试环境变量
├── eslint.config.js # ESLint配置
├── index.html # HTML模板
├── package.json # 前端依赖
└── vite.config.ts # Vite配置
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 后端核心模块
### 4.1 支付业务流程约定
| 模块 | 主要功能 | 文件位置 | 来源 |
|------|----------|----------|------|
| 认证模块 | 用户登录、验证码生成 | app/http/admin/controller/AuthController.php | app/routes/admin.php:20-21 |
| 用户模块 | 获取用户信息 | app/http/admin/controller/UserController.php | app/routes/admin.php:26 |
| 菜单模块 | 获取路由菜单 | app/http/admin/controller/MenuController.php | app/routes/admin.php:29 |
| 系统模块 | 字典管理、配置管理 | app/http/admin/controller/SystemController.php | app/routes/admin.php:32-37 |
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 前端核心模块
### 4.2 支付插件接口
| 模块 | 主要功能 | 文件位置 | 来源 |
|------|----------|----------|------|
| 布局模块 | 系统整体布局 | src/layout/ | resource/mpay_v2_admin/src/layout/ |
| 认证模块 | 登录、权限控制 | src/views/login/ | resource/mpay_v2_admin/src/views/ |
| 首页模块 | 数据概览 | src/views/home/ | resource/mpay_v2_admin/src/views/home/ |
| 财务管理 | 结算、对账、发票 | src/views/finance/ | resource/mpay_v2_admin/src/views/finance/ |
| 渠道管理 | 通道配置、支付方式 | src/views/channel/ | resource/mpay_v2_admin/src/views/channel/ |
| 数据分析 | 交易分析、商户分析 | src/views/analysis/ | resource/mpay_v2_admin/src/views/analysis/ |
| 系统设置 | 系统配置、字典管理 | src/views/system/ | resource/mpay_v2_admin/src/views/ |
- `app/common/contracts/PayPluginInterface.php`
- `app/common/contracts/AbstractPayPlugin.php`
- 示例实现:`app/common/payment/LakalaPayment.php`
## 5. API接口设计
插件需实现:`getName``getSupportedMethods``getConfigSchema``getSupportedProducts``init``unifiedOrder``refund``verifyNotify` 等。
### 5.1 认证接口
### 4.3 后端核心模块
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|------|------|-----------|------|------|------|
| /adminapi/captcha | GET | AuthController | 获取验证码 | 无 | app/routes/admin.php:20 |
| /adminapi/login | POST | AuthController | 用户登录 | 无 | app/routes/admin.php:21 |
| 模块 | 主要功能 | 文件位置 |
|------|----------|----------|
| 认证 | 管理员登录、验证码 | AuthController, AuthService |
| 管理员 | 获取管理员信息 | AdminController, AdminService, Admin 模型 |
| 菜单 | 获取路由菜单 | MenuController, MenuService |
| 系统 | 字典、配置管理 | SystemController, SystemConfigService |
| 通道管理 | 通道列表、详情、保存 | ChannelController, PaymentChannelRepository |
| 插件管理 | 插件列表、配置 Schema、产品列表 | PluginController, PluginService |
| 易支付 | submit.php/mapi.php/api.php | EpayController, EpayService |
### 5.2 用户接口
### 4.4 前端核心模块
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|------|------|-----------|------|------|------|
| /adminapi/user/getUserInfo | GET | UserController | 获取用户信息 | JWT | app/routes/admin.php:26 |
| 模块 | 主要功能 | 位置 |
|------|----------|------|
| 布局 | 系统整体布局 | src/layout/ |
| 认证 | 登录、权限控制 | src/views/login/ |
| 首页 | 数据概览 | src/views/home/ |
| 财务管理 | 结算、对账、发票 | src/views/finance/ |
| 渠道管理 | 通道配置、支付方式 | src/views/channel/ |
| 数据分析 | 交易分析、商户分析 | src/views/analysis/ |
| 系统设置 | 系统配置、字典管理 | src/views/system/ |
### 5.3 菜单接口
## 5. API 接口设计
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|------|------|-----------|------|------|------|
| /adminapi/menu/getRouters | GET | MenuController | 获取路由菜单 | JWT | app/routes/admin.php:29 |
### 5.1 管理后台(/adminapi
### 5.4 系统接口
| 路径 | 方法 | 控制器 | 功能 | 权限 |
|------|------|--------|------|------|
| /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 |
| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 |
|------|------|-----------|------|------|------|
| /adminapi/system/getDict[/{code}] | GET | SystemController | 获取字典数据 | JWT | app/routes/admin.php:32 |
| /adminapi/system/base-config/tabs | GET | SystemController | 获取配置标签 | JWT | app/routes/admin.php:35 |
| /adminapi/system/base-config/form/{tabKey} | GET | SystemController | 获取表单配置 | JWT | app/routes/admin.php:36 |
| /adminapi/system/base-config/submit/{tabKey} | POST | SystemController | 提交配置 | JWT | app/routes/admin.php:37 |
### 5.2 易支付接口(对外 API
## 6. 技术特点
| 路径 | 方法 | 控制器 | 功能 | 说明 |
|------|------|--------|------|------|
| /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 退款 |
### 6.1 后端特点
易支付约定:`pid` 映射为 `app_id`(商户应用标识),`key``app_secret`
1. **高性能架构**:基于 Webman 框架,使用 Workerman 作为底层,支持高并发处理
2. **模块化设计**:采用分层架构,清晰分离控制器、服务、仓库和模型
3. **JWT认证**:使用 JSON Web Token 实现无状态认证
4. **中间件机制**:通过中间件实现请求拦截和权限控制
5. **Redis集成**:使用 Redis 作为缓存和队列存储
6. **事件系统**:支持事件驱动架构
7. **定时任务**:内置定时任务管理功能
8. **数据验证**:使用 think-validate 进行数据验证
9. **依赖注入**:使用 PHP-DI 实现依赖注入
10. **日志管理**:使用 Monolog 进行日志管理
## 6. 命名与约定
### 6.2 前端特点
### 6.1 模型与仓储命名
1. **Vue 3 + TypeScript**:使用最新的 Vue 3 组合式 API 和 TypeScript 提供类型安全
2. **Arco Design**:采用字节跳动开源的 Arco Design UI 组件库,提供美观的界面
3. **Pinia 状态管理**:使用 Pinia 替代 Vuex提供更简洁的状态管理方案
4. **Vite 构建工具**:使用 Vite 提供快速的开发体验和优化的构建输出
5. **国际化支持**:内置多语言支持,可轻松切换语言
6. **响应式设计**:适配不同屏幕尺寸的设备
7. **丰富的功能组件**:集成多种实用组件,如二维码生成、条码生成、富文本编辑等
8. **权限控制**:基于指令的权限控制机制
9. **Mock 数据**:内置 Mock 数据,方便开发和测试
- 业务语义命名:`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. **配置环境**复制 `.env.example``.env` 并配置相关参数
4. **启动服务**`php start.php start`
5. **代码结构**:遵循 Webman 框架规范,按模块组织代码
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`
5. **代码结构**:遵循 Vue 3 项目规范,按功能模块组织代码
1. **环境**Node.js 18.12+PNPM 8.7+
2. **依赖**`pnpm install`
3. **开发**`pnpm dev`
4. **构建**`pnpm build:prod`
## 8. 部署与配置
## 8. 相关文档
### 8.1 后端部署
1. **服务器要求**Linux/Unix 系统PHP 8.1+MySQL 5.7+Redis 5.0+
2. **Nginx 配置**:配置反向代理指向 Webman 服务
3. **启动方式**
- 开发环境:`php start.php start`
- 生产环境:`php start.php start -d`
4. **监控管理**:可使用 Supervisor 管理进程
### 8.2 前端部署
1. **构建**`pnpm build:prod`
2. **部署**:将 `dist` 目录部署到 Web 服务器
3. **Nginx 配置**:配置静态文件服务和路由重写
| 文件 | 说明 |
|------|------|
| doc/epay.md | 易支付接口说明 |
| doc/payment_flow.md | 支付流程说明 |
| doc/payment_system_implementation.md | 支付系统实现说明 |
| doc/validation.md | 验证规则说明 |
| database/mvp_payment_tables.sql | 支付系统表结构 |
## 9. 总结
MPAY V2 项目采用现代化的技术栈和架构设计,后端使用 Webman 框架提供高性能的 API 服务,前端使用 Vue 3 + TypeScript + Arco Design 提供美观、响应式的用户界面。项目结构清晰,模块化程度高,便于维护和扩展
核心功能覆盖了支付管理系统的主要业务场景,包括用户认证、菜单管理、系统配置、财务管理、渠道管理和数据分析等模块,为支付业务的运营和管理提供了完整的解决方案。
技术特点包括高性能架构、模块化设计、JWT认证、Redis集成、Vue 3组合式API、TypeScript类型安全、Arco Design UI组件库、Pinia状态管理、Vite构建工具等确保了系统的稳定性、安全性和可扩展性。
该项目适合作为支付管理系统的基础框架,可根据具体业务需求进行定制和扩展。
MPAY V2 以支付业务为核心,采用 Webman + Vue 3 技术栈后端分层清晰Controller → Service → Repository → Model支持支付插件扩展与易支付兼容。管理后台基于 JWT 认证提供通道、插件、系统配置等管理能力对外提供易支付标准接口submit/mapi/api便于第三方商户接入

395
doc/validation.md Normal file
View File

@@ -0,0 +1,395 @@
验证器 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 默认仅包含主键字段。