Compare commits
No commits in common. "6d5191b5e867541d3bec3eb1799e18342239b300" and "e17684e7763a96530a1751a432fce1079c91b62f" have entirely different histories.
6d5191b5e8
...
e17684e776
|
|
@ -1,5 +0,0 @@
|
|||
MERCHANT_API_BASE_URI=https://tpoint.agrimedia.cn
|
||||
MERCHANT_API_TOKEN=your-admin-api-token
|
||||
MERCHANT_API_TIMEOUT=60
|
||||
|
||||
OFFLINE_ORDER_PAGE_LIMIT=100
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
.env
|
||||
vendor/
|
||||
.idea/
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
RewriteEngine On
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ public/index.php [QSA,L]
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ClaudeCodeTabState">
|
||||
<option name="tabSessions">
|
||||
<map>
|
||||
<entry key="0">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="codex" />
|
||||
<option name="sessionId" value="019ef354-0d94-7a71-8df9-3e4860ac548a" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="gpt-5.5" />
|
||||
<option name="permissionMode" value="default" />
|
||||
<option name="reasoningEffort" value="high" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -4,15 +4,34 @@
|
|||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/mcp/sdk" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/opis/json-schema" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/opis/string" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/opis/uri" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/discovery" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpdoc-parser" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/clock" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-handler" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-middleware" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/uid" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
|
|
|||
|
|
@ -12,10 +12,29 @@
|
|||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/psr/clock" />
|
||||
<path value="$PROJECT_DIR$/vendor/mcp/sdk" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-server-handler" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/discovery" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/log" />
|
||||
<path value="$PROJECT_DIR$/vendor/opis/uri" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-server-middleware" />
|
||||
<path value="$PROJECT_DIR$/vendor/opis/string" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/vendor/opis/json-schema" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/container" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/uid" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
|
||||
<path value="$PROJECT_DIR$/vendor/webmozart/assert" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
|
||||
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
|
||||
|
|
@ -24,7 +43,7 @@
|
|||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.2">
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.0">
|
||||
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings" defaultProject="true" />
|
||||
</project>
|
||||
97
README.md
97
README.md
|
|
@ -1,97 +0,0 @@
|
|||
# Merchant AI HTTP API
|
||||
|
||||
PHP 8.2 JSON API service for merchant order statistics, offline order analysis, and merchant ranking.
|
||||
|
||||
## Environment
|
||||
|
||||
Copy `.env.example` to `.env`, then fill your real token:
|
||||
|
||||
```bash
|
||||
MERCHANT_API_TOKEN=your-admin-api-token
|
||||
MERCHANT_API_BASE_URI=https://tpoint.agrimedia.cn
|
||||
MERCHANT_API_TIMEOUT=60
|
||||
OFFLINE_ORDER_PAGE_LIMIT=100
|
||||
```
|
||||
|
||||
`MERCHANT_API_TOKEN` is required. `MERCHANT_API_BASE_URI` and `MERCHANT_API_TIMEOUT` are optional.
|
||||
`OFFLINE_ORDER_PAGE_LIMIT` controls each upstream pagination request size.
|
||||
|
||||
If the request contains `Authorization: Bearer <token>`, that token is used for the upstream merchant API request first. If the header is missing, `MERCHANT_API_TOKEN` from `.env` is used.
|
||||
|
||||
## Start Locally
|
||||
|
||||
```bash
|
||||
php -S 127.0.0.1:8080 -t public
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
Success:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
Error:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "server_error",
|
||||
"message": "error message"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
|
||||
### `GET /health`
|
||||
|
||||
Returns service health status.
|
||||
|
||||
### `GET /tools`
|
||||
|
||||
Returns available API tools and input schemas.
|
||||
|
||||
### `POST /api/order-summary`
|
||||
|
||||
Returns order count, active merchant count, successful order count, and amount totals.
|
||||
|
||||
### `POST /api/offline-stats`
|
||||
|
||||
Returns status, payment type, order type, and amount distribution analysis.
|
||||
|
||||
### `POST /api/merchant-ranking`
|
||||
|
||||
Returns merchant ranking by `orderCount`, `successOrderCount`, `totalOrderAmount`, or `totalRealPay`.
|
||||
|
||||
## Common Parameters
|
||||
|
||||
- `timePreset`: preferred AI time intent. Supported values: `today`, `yesterday`, `last_7_days`, `last_30_days`, `this_week`, `last_week`, `this_month`, `last_month`, `custom`. Defaults to `today`.
|
||||
- `startDate`: custom start date, required when `timePreset=custom`, format `YYYY-MM-DD`.
|
||||
- `endDate`: custom end date, required when `timePreset=custom`, format `YYYY-MM-DD`.
|
||||
- `date`: legacy compatible date range, for example `2026/06/23-2026/06/23`; if provided, it is sent to the upstream order API as-is.
|
||||
- `status`: order status; defaults to `-1` (all). Values: `0` = pending payment, `1` = completed, `2` = cancelled, `3` = pending verification/use, `4` = refunded.
|
||||
- `keyword`: search keyword, sent to upstream API as `real_name`.
|
||||
- `fieldKey`: upstream `field_key`; defaults to `all`.
|
||||
- `payType`: upstream `pay_type`.
|
||||
- `type`: upstream order `type`.
|
||||
- `btcId`: upstream merchant category ID.
|
||||
|
||||
`/api/merchant-ranking` also supports:
|
||||
|
||||
- `sortBy`: `orderCount`, `successOrderCount`, `totalOrderAmount`, or `totalRealPay`; defaults to `totalRealPay`.
|
||||
- `limit`: number of merchants to return; defaults to `10`, max `100`.
|
||||
|
||||
## POST Example
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8080/api/merchant-ranking \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"timePreset":"today","sortBy":"totalRealPay","limit":10}'
|
||||
```
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"mcp/sdk": "^0.6.0",
|
||||
"guzzlehttp/guzzle": "^7.12"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,443 +0,0 @@
|
|||
# Merchant AI HTTP API 调用说明
|
||||
|
||||
本文档用于喂给外部“解读 AI”或其他服务平台,让 AI 在对话中正确调用本项目的 HTTP JSON API 进行订单统计、线下订单分析和商户排行分析。
|
||||
|
||||
## 1. 服务定位
|
||||
|
||||
本项目是一个中间层 HTTP JSON API 服务。AI 不直接请求后台订单系统,也不直接拼接后台订单接口参数。
|
||||
|
||||
本项目负责:
|
||||
|
||||
- 接收 AI 的结构化分析请求
|
||||
- 解析时间意图
|
||||
- 调用后台订单接口
|
||||
- 复用本项目内部统计逻辑
|
||||
- 返回适合 AI 解读的 JSON 结果
|
||||
|
||||
## 2. 启动方式
|
||||
|
||||
本地开发启动:
|
||||
|
||||
```bash
|
||||
php -S 127.0.0.1:8080 -t public public/index.php
|
||||
```
|
||||
|
||||
健康检查:
|
||||
|
||||
```http
|
||||
GET http://127.0.0.1:8080/health
|
||||
```
|
||||
|
||||
正常返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status": "ok",
|
||||
"service": "merchant-ai-api"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 接口列表
|
||||
|
||||
### 3.1 订单汇总
|
||||
|
||||
```http
|
||||
POST /api/order-summary
|
||||
```
|
||||
|
||||
对应工具:`get_order_summary`
|
||||
|
||||
用途:订单总数、活跃商户数、成功订单数、订单金额合计、实付金额合计。
|
||||
|
||||
### 3.2 线下订单分析
|
||||
|
||||
```http
|
||||
POST /api/offline-stats
|
||||
```
|
||||
|
||||
对应工具:`get_offline_stats`
|
||||
|
||||
用途:订单状态分布、支付方式分布、订单类型分布、营销中台分布、退款状态分布、抵扣金额、手续费、`diamond` 字段代币值汇总、平均订单金额、平均实付金额、最大订单金额、活跃商户数、实付金额 Top 商户。
|
||||
|
||||
### 3.3 商户排行榜
|
||||
|
||||
```http
|
||||
POST /api/merchant-ranking
|
||||
```
|
||||
|
||||
对应工具:`get_merchant_ranking`
|
||||
|
||||
用途:按订单数、成功订单数、订单金额或实付金额生成商户排行。
|
||||
|
||||
## 4. 请求格式
|
||||
|
||||
所有统计接口都使用:
|
||||
|
||||
```http
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
如果调用方请求头中传入:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <后台订单接口 token>
|
||||
```
|
||||
|
||||
本服务会优先使用该 token 请求后台订单接口;如果没有传 `Authorization`,则使用服务器 `.env` 中的 `MERCHANT_API_TOKEN`。
|
||||
|
||||
注意:只有可信后端服务可以传该 header,不建议在浏览器前端或公开客户端暴露后台 token。
|
||||
|
||||
统一请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "today",
|
||||
"status": -1,
|
||||
"keyword": "",
|
||||
"fieldKey": "all",
|
||||
"payType": "",
|
||||
"type": "",
|
||||
"btcId": null
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 时间参数规则
|
||||
|
||||
AI 应优先传 `timePreset`,不要直接传自然语言时间。
|
||||
|
||||
推荐:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "today"
|
||||
}
|
||||
```
|
||||
|
||||
支持的 `timePreset`:
|
||||
|
||||
| 值 | 含义 |
|
||||
| --- | --- |
|
||||
| `today` | 今天 |
|
||||
| `yesterday` | 昨天 |
|
||||
| `last_7_days` | 最近 7 天,包含今天 |
|
||||
| `last_30_days` | 最近 30 天,包含今天 |
|
||||
| `this_week` | 本周,周一到今天 |
|
||||
| `last_week` | 上周,周一到周日 |
|
||||
| `this_month` | 本月,1 号到今天 |
|
||||
| `last_month` | 上月,1 号到月末 |
|
||||
| `custom` | 自定义时间范围 |
|
||||
|
||||
自定义时间:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "custom",
|
||||
"startDate": "2026-06-01",
|
||||
"endDate": "2026-06-23"
|
||||
}
|
||||
```
|
||||
|
||||
自定义时间约束:
|
||||
|
||||
- `startDate` 格式必须是 `YYYY-MM-DD`
|
||||
- `endDate` 格式必须是 `YYYY-MM-DD`
|
||||
- `startDate` 不能晚于 `endDate`
|
||||
- 不要传 `2026/06/01`,自定义日期统一使用 `2026-06-01`
|
||||
|
||||
兼容旧字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"date": "2026/06/01-2026/06/23"
|
||||
}
|
||||
```
|
||||
|
||||
AI 默认不要使用 `date`,除非调用平台只能传固定后台日期格式。
|
||||
|
||||
## 6. AI 对话到接口参数映射
|
||||
|
||||
用户说“今天订单怎么样?”时调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "today"
|
||||
}
|
||||
```
|
||||
|
||||
推荐接口:`POST /api/order-summary`
|
||||
|
||||
用户说“分析一下昨天线下订单”时调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "yesterday"
|
||||
}
|
||||
```
|
||||
|
||||
推荐接口:`POST /api/offline-stats`
|
||||
|
||||
用户说“看本月商户排行”时调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "this_month",
|
||||
"sortBy": "totalRealPay",
|
||||
"limit": 10
|
||||
}
|
||||
```
|
||||
|
||||
推荐接口:`POST /api/merchant-ranking`
|
||||
|
||||
用户说“查 6 月 1 日到 6 月 23 日订单统计”时调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "custom",
|
||||
"startDate": "2026-06-01",
|
||||
"endDate": "2026-06-23"
|
||||
}
|
||||
```
|
||||
|
||||
推荐接口:`POST /api/order-summary`
|
||||
|
||||
## 7. 常用筛选参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `status` | integer | 订单状态,默认 `-1` 表示全部 |
|
||||
| `keyword` | string | 搜索关键词,当前会传给后台接口的 `real_name` |
|
||||
| `fieldKey` | string | 搜索字段,默认 `all` |
|
||||
| `payType` | string | 支付方式,对应后台 `pay_type` |
|
||||
| `type` | string | 订单类型,对应后台 `type` |
|
||||
| `btcId` | integer | 商户分类 ID,对应后台 `btc_id` |
|
||||
|
||||
### 7.1 `diamond` 字段与 `btc_id` 的关系
|
||||
|
||||
后台订单里存在 `diamond` 字段。该字段不是固定只表示“钻石”,需要结合 `btc_id` 判断业务含义:
|
||||
|
||||
| btc_id | `diamond` 字段含义 |
|
||||
| --- | --- |
|
||||
| `1` | 钻石值 |
|
||||
| `3` | 绿宝石值 |
|
||||
| `4` | 算力值 |
|
||||
|
||||
`/api/offline-stats` 会返回:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `totalTokenValue` | 所有订单 `diamond` 字段本身的合计 |
|
||||
| `tokenValueSummary` | 按 `btc_id` 解释后的 `diamond` 字段汇总,例如钻石值、绿宝石值、算力值 |
|
||||
|
||||
注意:
|
||||
|
||||
- `diamond` 表示本单关联/附赠/记录的代币值。
|
||||
- `diamond_ded` 表示消费券/钻石抵扣金额。
|
||||
- 两者不是同一个字段,不要混淆。
|
||||
|
||||
### 7.2 订单状态 `status` 取值
|
||||
|
||||
| status | 含义 | 用户常见说法 |
|
||||
| --- | --- | --- |
|
||||
| `-1` | 全部订单 | 全部、所有订单、不限状态 |
|
||||
| `0` | 待支付 | 待支付、未付款、未支付订单 |
|
||||
| `1` | 已完成/成功 | 已完成、成功订单、已支付完成 |
|
||||
| `2` | 已取消 | 已取消、取消订单 |
|
||||
| `3` | 待核销/待使用 | 待核销、待使用、未使用 |
|
||||
| `4` | 已退款 | 已退款、退款订单 |
|
||||
|
||||
AI 选择规则:
|
||||
|
||||
- 用户没有提到订单状态时,必须传 `status=-1`。
|
||||
- 用户问“成功订单”“已完成订单”时,传 `status=1`。
|
||||
- 用户问“待支付订单”“未付款订单”时,传 `status=0`。
|
||||
- 用户问“取消订单”时,传 `status=2`。
|
||||
- 用户问“待核销”“待使用”时,传 `status=3`。
|
||||
- 用户问“退款订单”时,传 `status=4`。
|
||||
- 如果用户说“订单汇总”“今日数据”“最近7天数据”但没有限定状态,传 `status=-1`。
|
||||
|
||||
示例:
|
||||
|
||||
用户说“今天成功订单有多少?”时调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "today",
|
||||
"status": 1
|
||||
}
|
||||
```
|
||||
|
||||
用户说“最近7天退款订单金额是多少?”时调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "last_7_days",
|
||||
"status": 4
|
||||
}
|
||||
```
|
||||
|
||||
用户说“本月所有订单汇总”时调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"timePreset": "this_month",
|
||||
"status": -1
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 商户排行参数
|
||||
|
||||
`/api/merchant-ranking` 额外支持:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `sortBy` | string | 排序字段 |
|
||||
| `limit` | integer | 返回商户数量,默认 `10`,最大 `100` |
|
||||
|
||||
`sortBy` 可选值:
|
||||
|
||||
| 值 | 含义 |
|
||||
| --- | --- |
|
||||
| `orderCount` | 按订单数排序 |
|
||||
| `successOrderCount` | 按成功订单数排序 |
|
||||
| `totalOrderAmount` | 按订单金额排序 |
|
||||
| `totalRealPay` | 按实付金额排序,默认值 |
|
||||
|
||||
## 9. 响应格式
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
失败响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "server_error",
|
||||
"message": "错误信息"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
常见错误:
|
||||
|
||||
| code | 含义 | 处理方式 |
|
||||
| --- | --- | --- |
|
||||
| `not_found` | 路由不存在 | 检查 URL |
|
||||
| `method_not_allowed` | 请求方法错误 | 统计接口必须使用 POST |
|
||||
| `server_error` | 服务端或后台接口异常 | 根据 message 排查 |
|
||||
|
||||
## 10. 返回数据解读规则
|
||||
|
||||
AI 必须严格按照接口返回的 JSON 数据解读,不允许编造、放大、补全或推测不存在的数据。
|
||||
|
||||
例如接口返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"orderCount": 100,
|
||||
"activeMerchantCount": 5,
|
||||
"successOrderCount": 47,
|
||||
"totalOrderAmount": 882.89,
|
||||
"totalRealPay": 662.39,
|
||||
"_debug_query": {
|
||||
"time": "2026/06/18-2026/06/24"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
AI 只能解读为:
|
||||
|
||||
- 查询时间范围:`2026/06/18-2026/06/24`
|
||||
- 当前接口返回订单数:`100`
|
||||
- 活跃商户数:`5`
|
||||
- 成功订单数:`47`
|
||||
- 订单总金额:`882.89`
|
||||
- 实付总金额:`662.39`
|
||||
|
||||
AI 可以基于已返回字段做简单计算,但必须说明是“根据当前返回数据计算”:
|
||||
|
||||
- 成功率:`47 / 100 = 47%`
|
||||
- 实付占比:`662.39 / 882.89 ≈ 75.03%`
|
||||
- 平均订单金额:`882.89 / 100 ≈ 8.83`
|
||||
- 平均实付金额:`662.39 / 100 ≈ 6.62`
|
||||
|
||||
AI 禁止输出以下内容,除非接口明确返回:
|
||||
|
||||
- 禁止把 `100` 改写成 `3850`
|
||||
- 禁止编造 `3427` 个成功订单
|
||||
- 禁止编造 `186` 家商户
|
||||
- 禁止编造 `日均订单 550 单`
|
||||
- 禁止编造 `日均流水 38.5 万元`
|
||||
- 禁止编造“整体交易状况良好”等过度结论
|
||||
- 禁止把未返回的后台总数当作事实
|
||||
|
||||
注意:当前接口会根据后台返回的 `data.count` 自动分页拉取订单列表,直到取完当前筛选条件下的订单数据后再统计。`_debug_query` 只表示筛选条件,不再表示只统计第 1 页。
|
||||
|
||||
## 11. AI 调用约束
|
||||
|
||||
AI 必须遵守以下规则:
|
||||
|
||||
1. 统计接口必须使用 `POST`。
|
||||
2. 不要用浏览器 `GET` 方式访问统计接口。
|
||||
3. 时间优先使用 `timePreset`。
|
||||
4. 只有用户明确给出起止日期时,才使用 `timePreset=custom`。
|
||||
5. 自定义日期必须使用 `YYYY-MM-DD`。
|
||||
6. 不要传 `今天`、`昨天`、`本月` 这类中文自然语言给接口。
|
||||
7. 不要伪造不存在的筛选字段。
|
||||
8. 不要把后台系统 token 暴露给用户。
|
||||
9. 不要直接调用后台订单接口,只调用本项目 HTTP API。
|
||||
10. 如果用户问题不包含时间,默认使用 `timePreset=today`。
|
||||
11. 解读结果时只能使用接口返回的字段,不允许编造不存在的订单数、金额、商户数、日均值或趋势结论。
|
||||
12. 如果要计算成功率、平均值、占比,必须基于接口返回字段计算,并说明“根据当前返回数据计算”。
|
||||
13. 如果接口返回 `_debug_query.time`,可以把它作为本次查询时间范围。
|
||||
|
||||
## 12. PowerShell 调用示例
|
||||
|
||||
订单汇总:
|
||||
|
||||
```powershell
|
||||
Invoke-RestMethod `
|
||||
-Uri "http://127.0.0.1:8080/api/order-summary" `
|
||||
-Method POST `
|
||||
-ContentType "application/json" `
|
||||
-Body '{"timePreset":"today"}'
|
||||
```
|
||||
|
||||
线下订单分析:
|
||||
|
||||
```powershell
|
||||
Invoke-RestMethod `
|
||||
-Uri "http://127.0.0.1:8080/api/offline-stats" `
|
||||
-Method POST `
|
||||
-ContentType "application/json" `
|
||||
-Body '{"timePreset":"yesterday"}'
|
||||
```
|
||||
|
||||
商户排行:
|
||||
|
||||
```powershell
|
||||
Invoke-RestMethod `
|
||||
-Uri "http://127.0.0.1:8080/api/merchant-ranking" `
|
||||
-Method POST `
|
||||
-ContentType "application/json" `
|
||||
-Body '{"timePreset":"this_month","sortBy":"totalRealPay","limit":10}'
|
||||
```
|
||||
|
||||
## 13. 推荐给 AI 的简短系统提示词
|
||||
|
||||
```text
|
||||
你需要通过 Merchant AI HTTP API 分析订单数据。统计接口只能使用 POST。不要直接请求后台订单接口,不要暴露 token。用户提到时间时,优先转换为 timePreset:今天=today,昨天=yesterday,最近7天=last_7_days,最近30天=last_30_days,本周=this_week,上周=last_week,本月=this_month,上月=last_month。只有明确起止日期时使用 custom,并传 startDate/endDate,格式 YYYY-MM-DD。不要把“今天”“昨天”“本月”等自然语言直接作为参数传给接口。如果用户没说时间,默认 timePreset=today。订单状态 status:全部=-1,待支付=0,已完成/成功=1,已取消=2,待核销/待使用=3,已退款=4;如果用户没有限定状态,默认 status=-1。解读接口返回时,必须只使用 JSON 中真实存在的字段和数值,不允许编造订单数、成功订单数、商户数、金额、日均值、趋势或业务结论。可以基于返回字段做简单计算,但必须说明“根据当前返回数据计算”。如果返回 _debug_query.time,可以作为查询时间范围。当前接口会自动分页拉取当前筛选条件下的订单数据后再统计,_debug_query 只表示筛选条件。
|
||||
```
|
||||
|
|
@ -1,390 +0,0 @@
|
|||
# Merchant AI API 返回 JSON 解读说明
|
||||
|
||||
本文档用于喂给“结果解读 AI”。该 AI 的任务不是调用接口,而是接收已经真实调用接口后得到的 JSON,并把 JSON 解读成用户能看懂的中文结果。
|
||||
|
||||
## 1. 核心原则
|
||||
|
||||
AI 必须严格遵守:
|
||||
|
||||
1. 只能基于输入 JSON 中真实存在的字段和数值回答。
|
||||
2. 不允许编造、猜测、放大、补全任何订单数、金额、商户数、排行、趋势或业务结论。
|
||||
3. 如果 JSON 中没有某个字段,就不能当作事实输出。
|
||||
4. 可以做简单计算,但必须基于 JSON 已有字段,并说明“根据当前返回数据计算”。
|
||||
5. 如果接口返回失败,必须先说明失败原因,不要继续分析业务数据。
|
||||
6. 如果 JSON 中存在 `_debug_query.time`,可以把它作为本次查询时间范围。
|
||||
7. 当前接口会根据后台 `data.count` 自动分页拉取当前筛选条件下的订单列表,再进行统计。`_debug_query` 只表示筛选条件。
|
||||
|
||||
## 2. 输入数据格式
|
||||
|
||||
成功响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
失败响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "server_error",
|
||||
"message": "错误信息"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 失败响应解读规则
|
||||
|
||||
如果 `success=false`,AI 只能说明接口调用失败。
|
||||
|
||||
示例输入:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "method_not_allowed",
|
||||
"message": "This endpoint only supports POST."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
推荐输出:
|
||||
|
||||
```text
|
||||
接口调用失败,原因是请求方法不正确:This endpoint only supports POST。请使用 POST 方法重新请求该统计接口。
|
||||
```
|
||||
|
||||
禁止输出订单统计、金额分析或商户排行。
|
||||
|
||||
## 4. 订单汇总 JSON 字段说明
|
||||
|
||||
接口:`POST /api/order-summary`
|
||||
|
||||
常见返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"orderCount": 100,
|
||||
"activeMerchantCount": 5,
|
||||
"successOrderCount": 47,
|
||||
"totalOrderAmount": 882.89,
|
||||
"totalRealPay": 662.39,
|
||||
"_debug_query": {
|
||||
"status": -1,
|
||||
"field_key": "all",
|
||||
"page": 1,
|
||||
"limit": 100,
|
||||
"time": "2026/06/18-2026/06/24"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `orderCount` | 当前筛选条件下参与统计的订单数量 |
|
||||
| `activeMerchantCount` | 当前筛选条件下的活跃商户数量 |
|
||||
| `successOrderCount` | 当前筛选条件下的成功订单数量 |
|
||||
| `totalOrderAmount` | 当前筛选条件下的订单总金额 |
|
||||
| `totalRealPay` | 当前筛选条件下的实付总金额 |
|
||||
| `_debug_query.time` | 本次查询时间范围 |
|
||||
|
||||
推荐解读模板:
|
||||
|
||||
```text
|
||||
本次查询时间范围:{_debug_query.time}
|
||||
|
||||
订单汇总如下:
|
||||
- 订单数:{orderCount} 单
|
||||
- 活跃商户数:{activeMerchantCount} 家
|
||||
- 成功订单数:{successOrderCount} 单
|
||||
- 订单总金额:{totalOrderAmount}
|
||||
- 实付总金额:{totalRealPay}
|
||||
|
||||
根据当前接口统计结果计算:
|
||||
- 成功率:{successOrderCount / orderCount}
|
||||
- 实付占比:{totalRealPay / totalOrderAmount}
|
||||
|
||||
注意:以上结果基于接口按当前筛选条件自动分页获取后的数据统计。
|
||||
```
|
||||
|
||||
当 `orderCount=0` 时,不要计算成功率和平均值,应该说明当前范围内没有返回订单数据。
|
||||
|
||||
## 5. 线下订单分析 JSON 字段说明
|
||||
|
||||
接口:`POST /api/offline-stats`
|
||||
|
||||
常见返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"orderCount": 100,
|
||||
"statusDistribution": {
|
||||
"1 已完成": 47,
|
||||
"2 已取消": 10
|
||||
},
|
||||
"payTypeDistribution": {
|
||||
"7 蜜传微信支付": 50,
|
||||
"2 微信支付": 20
|
||||
},
|
||||
"typeDistribution": {
|
||||
"0 扫码订单": 70,
|
||||
"5 视频团单": 30
|
||||
},
|
||||
"btcDistribution": {
|
||||
"3 绿宝石中台(绿宝石)": 80,
|
||||
"4 算力中台(算力)": 20
|
||||
},
|
||||
"refundDistribution": {
|
||||
"0 未退款": 95,
|
||||
"1 已退款": 5
|
||||
},
|
||||
"totalOrderAmount": 882.89,
|
||||
"totalRealPay": 662.39,
|
||||
"totalCoinDeduction": 100.00,
|
||||
"totalBrokerDeduction": 10.00,
|
||||
"totalDiamondDeduction": 20.00,
|
||||
"totalTokenValue": 123.456789,
|
||||
"tokenValueSummary": {
|
||||
"diamond 钻石值": 10.000000,
|
||||
"diamond 绿宝石值": 100.000000,
|
||||
"diamond 算力值": 13.456789
|
||||
},
|
||||
"totalPreferentialAmount": 5.00,
|
||||
"totalMerchantRatePrice": 2.50,
|
||||
"totalDeductionAmount": 135.00,
|
||||
"averageOrderAmount": 8.83,
|
||||
"averageRealPay": 6.62,
|
||||
"maxOrderAmount": 99.99,
|
||||
"activeMerchantCount": 5,
|
||||
"topMerchantsByRealPay": [
|
||||
{
|
||||
"merchantId": 110,
|
||||
"merchantName": "示例商户",
|
||||
"orderCount": 20,
|
||||
"totalOrderAmount": 200.00,
|
||||
"totalRealPay": 180.00
|
||||
}
|
||||
],
|
||||
"_debug_query": {
|
||||
"time": "2026/06/18-2026/06/24"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `orderCount` | 当前筛选条件下参与统计的订单数量 |
|
||||
| `statusDistribution` | 订单状态分布 |
|
||||
| `payTypeDistribution` | 支付方式分布 |
|
||||
| `typeDistribution` | 订单类型分布 |
|
||||
| `btcDistribution` | 营销代币中台分布,如积分、钻石、绿宝石、算力 |
|
||||
| `refundDistribution` | 退款状态分布 |
|
||||
| `totalOrderAmount` | 订单总金额 |
|
||||
| `totalRealPay` | 实付总金额 |
|
||||
| `totalCoinDeduction` | 抵用券/积分抵扣金额合计,对应 `coin_ded` |
|
||||
| `totalBrokerDeduction` | 佣金抵扣金额合计,对应 `broker_ded` |
|
||||
| `totalDiamondDeduction` | 消费券/钻石抵扣金额合计,对应 `diamond_ded` |
|
||||
| `totalTokenValue` | `diamond` 字段本身的总值,不是抵扣金额 |
|
||||
| `tokenValueSummary` | 按 `btc_id` 解释后的 `diamond` 字段汇总 |
|
||||
| `totalPreferentialAmount` | 平台让利抵扣券金额合计,对应 `pr_amount` |
|
||||
| `totalMerchantRatePrice` | 商户承担通道手续费合计,对应 `merchant_rate_price` |
|
||||
| `totalDeductionAmount` | 抵扣类金额合计,包含抵用券、佣金、消费券、平台让利抵扣券 |
|
||||
| `averageOrderAmount` | 平均订单金额 |
|
||||
| `averageRealPay` | 平均实付金额 |
|
||||
| `maxOrderAmount` | 最大订单金额 |
|
||||
| `activeMerchantCount` | 当前筛选条件下的活跃商户数量 |
|
||||
| `topMerchantsByRealPay` | 按实付金额排序的前 10 个商户 |
|
||||
|
||||
状态值参考:
|
||||
|
||||
| 状态值 | 含义 |
|
||||
| --- | --- |
|
||||
| `-1` | 全部,仅用于请求筛选 |
|
||||
| `0` | 待支付 |
|
||||
| `1` | 已完成或成功 |
|
||||
| `2` | 已取消 |
|
||||
| `3` | 待核销或待使用 |
|
||||
| `4` | 已退款 |
|
||||
|
||||
如果分布中的 key 无法确认含义,保持原值输出,不要强行翻译。
|
||||
|
||||
推荐解读模板:
|
||||
|
||||
```text
|
||||
本次查询时间范围:{_debug_query.time}
|
||||
|
||||
线下订单分析如下:
|
||||
- 订单数:{orderCount} 单
|
||||
- 订单总金额:{totalOrderAmount}
|
||||
- 实付总金额:{totalRealPay}
|
||||
- 抵用券/积分抵扣:{totalCoinDeduction}
|
||||
- 佣金抵扣:{totalBrokerDeduction}
|
||||
- 消费券/钻石抵扣:{totalDiamondDeduction}
|
||||
- diamond 字段总值:{totalTokenValue}
|
||||
- diamond 字段分中台汇总:{tokenValueSummary 中逐项列出}
|
||||
- 平台让利抵扣券:{totalPreferentialAmount}
|
||||
- 通道手续费:{totalMerchantRatePrice}
|
||||
- 抵扣合计:{totalDeductionAmount}
|
||||
- 平均订单金额:{averageOrderAmount}
|
||||
- 平均实付金额:{averageRealPay}
|
||||
- 最大订单金额:{maxOrderAmount}
|
||||
- 活跃商户数:{activeMerchantCount}
|
||||
|
||||
状态分布:
|
||||
- {statusDistribution 中逐项列出}
|
||||
|
||||
支付方式分布:
|
||||
- {payTypeDistribution 中逐项列出}
|
||||
|
||||
订单类型分布:
|
||||
- {typeDistribution 中逐项列出}
|
||||
|
||||
营销中台分布:
|
||||
- {btcDistribution 中逐项列出}
|
||||
|
||||
退款状态分布:
|
||||
- {refundDistribution 中逐项列出}
|
||||
|
||||
实付金额 Top 商户:
|
||||
- {topMerchantsByRealPay 中逐项列出}
|
||||
|
||||
注意:以上结果基于接口按当前筛选条件自动分页获取后的数据统计。
|
||||
```
|
||||
|
||||
`diamond` 字段业务解释规则:
|
||||
|
||||
| btc_id | `diamond` 字段含义 |
|
||||
| --- | --- |
|
||||
| `1` | 钻石值 |
|
||||
| `3` | 绿宝石值 |
|
||||
| `4` | 算力值 |
|
||||
|
||||
注意:`diamond` 和 `diamond_ded` 不是同一个含义。
|
||||
|
||||
- `diamond`:本单关联/附赠/记录的代币值,需结合 `btc_id` 判断是钻石、绿宝石还是算力。
|
||||
- `diamond_ded`:消费券/钻石抵扣金额,属于抵扣金额。
|
||||
|
||||
## 6. 商户排行榜 JSON 字段说明
|
||||
|
||||
接口:`POST /api/merchant-ranking`
|
||||
|
||||
常见返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"sortBy": "totalRealPay",
|
||||
"ranking": [
|
||||
{
|
||||
"merchantId": 1,
|
||||
"merchantName": "示例商户",
|
||||
"orderCount": 20,
|
||||
"successOrderCount": 10,
|
||||
"totalOrderAmount": 200.00,
|
||||
"totalRealPay": 180.00
|
||||
}
|
||||
],
|
||||
"_debug_query": {
|
||||
"time": "2026/06/18-2026/06/24"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `sortBy` | 当前排行榜排序字段 |
|
||||
| `ranking` | 商户排行列表 |
|
||||
| `merchantId` | 商户 ID |
|
||||
| `merchantName` | 商户名称,可能为空 |
|
||||
| `orderCount` | 该商户在当前筛选条件下的订单数 |
|
||||
| `successOrderCount` | 该商户在当前筛选条件下的成功订单数 |
|
||||
| `totalOrderAmount` | 该商户在当前筛选条件下的订单金额 |
|
||||
| `totalRealPay` | 该商户在当前筛选条件下的实付金额 |
|
||||
|
||||
推荐解读模板:
|
||||
|
||||
```text
|
||||
本次查询时间范围:{_debug_query.time}
|
||||
|
||||
商户排行榜如下,排序字段:{sortBy}
|
||||
|
||||
1. {merchantName 或 merchantId}
|
||||
- 订单数:{orderCount}
|
||||
- 成功订单数:{successOrderCount}
|
||||
- 订单金额:{totalOrderAmount}
|
||||
- 实付金额:{totalRealPay}
|
||||
|
||||
注意:以上排行基于接口按当前筛选条件自动分页获取后的数据统计。
|
||||
```
|
||||
|
||||
如果 `ranking` 为空,输出:
|
||||
|
||||
```text
|
||||
当前查询条件下没有返回可排行的商户数据。
|
||||
```
|
||||
|
||||
## 7. 允许的计算
|
||||
|
||||
AI 可以进行以下简单计算:
|
||||
|
||||
| 指标 | 计算方式 |
|
||||
| --- | --- |
|
||||
| 成功率 | `successOrderCount / orderCount * 100%` |
|
||||
| 实付占比 | `totalRealPay / totalOrderAmount * 100%` |
|
||||
| 平均订单金额 | `totalOrderAmount / orderCount` |
|
||||
| 平均实付金额 | `totalRealPay / orderCount` |
|
||||
|
||||
计算规则:
|
||||
|
||||
- 分母为 `0` 时不要计算,说明无法计算。
|
||||
- 百分比建议保留 2 位小数。
|
||||
- 金额建议保留 2 位小数。
|
||||
- 必须说明计算基于“当前返回数据”。
|
||||
|
||||
## 8. 禁止行为
|
||||
|
||||
AI 禁止:
|
||||
|
||||
- 禁止说“我已经调用接口”,除非系统确实提供了真实接口返回 JSON。
|
||||
- 禁止把示例 JSON 当成真实数据。
|
||||
- 禁止模拟、假设、补全数据。
|
||||
- 禁止把 `orderCount=100` 写成其他数值。
|
||||
- 禁止编造日均订单、日均流水、增长率、环比、同比。
|
||||
- 禁止编造“业务表现良好”“交易状况良好”等没有数据支撑的结论。
|
||||
- 禁止脱离接口返回 JSON 编造后台总量。
|
||||
- 禁止输出 JSON 中不存在的商户、订单状态或支付方式。
|
||||
|
||||
## 9. 没有真实 JSON 时的回答
|
||||
|
||||
如果没有收到真实接口返回 JSON,AI 必须回答:
|
||||
|
||||
```text
|
||||
我目前还没有拿到真实接口返回数据,不能生成具体订单统计结果。请先调用对应 API,并把返回 JSON 提供给我,我会基于真实数据进行解读。
|
||||
```
|
||||
|
||||
不要输出示例统计结果。
|
||||
|
||||
## 10. 推荐系统提示词
|
||||
|
||||
```text
|
||||
你是 Merchant AI API 的结果解读助手。你只负责解读已经真实调用接口后返回的 JSON。你不能自己调用接口,也不能声称已经调用接口,除非系统消息中提供了真实 JSON。你必须严格使用 JSON 中存在的字段和数值,不得编造、模拟、放大或补全任何订单数、金额、商户数、日均值、增长率、排行、趋势或业务结论。如果没有真实 JSON,必须说明尚未获取真实接口数据。可以基于 JSON 字段做简单计算,例如成功率、实付占比、平均金额,但必须说明“根据当前接口统计结果计算”。如果存在 _debug_query.time,将其作为查询时间范围。当前接口会自动分页拉取当前筛选条件下的数据后再统计,_debug_query 只表示筛选条件。
|
||||
```
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use App\Api\ApiFactory;
|
||||
use App\Config\Env;
|
||||
use App\Http\HttpRequest;
|
||||
|
||||
Env::load(dirname(__DIR__) . '/.env');
|
||||
|
||||
$request = HttpRequest::fromGlobals();
|
||||
|
||||
ApiFactory::create($request->bearerToken())->handle($request);
|
||||
81
server.php
81
server.php
|
|
@ -2,4 +2,83 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/public/index.php';
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Mcp\Server;
|
||||
use Mcp\Server\Transport\StdioTransport;
|
||||
|
||||
$token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwd2QiOiI2ODI5N2FjYzUwZDczYWQzZmNmMTNlM2Y4MWYxMWQyZSIsImlzcyI6InRwb2ludC5hZ3JpbWVkaWEuY24iLCJhdWQiOiJ0cG9pbnQuYWdyaW1lZGlhLmNuIiwiaWF0IjoxNzgwODk3Mjk3LCJuYmYiOjE3ODA4OTcyOTcsImV4cCI6MTc4MzQwMjg5NywianRpIjp7ImlkIjo0LCJ0eXBlIjoiYWRtaW4ifX0.G2yB38bYkf0b37kpAbQViDuXY4QHeRjm99WNXjSh4L0';
|
||||
|
||||
$server = Server::builder()
|
||||
->setServerInfo('Merchant MCP', '1.0.0')
|
||||
->addTool(
|
||||
handler: function () use ($token): array {
|
||||
$client = new Client([
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
$response = $client->get(
|
||||
'https://tpoint.agrimedia.cn/adminapi/offline/list',
|
||||
[
|
||||
'headers' => [
|
||||
'accept' => 'application/json',
|
||||
'authori-zation' => 'Bearer ' . $token,
|
||||
],
|
||||
'query' => [
|
||||
'status' => -1,
|
||||
'field_key' => 'all',
|
||||
'page' => 1,
|
||||
'limit' => 100,
|
||||
'time' => '2026/06/23-2026/06/23',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$data = json_decode(
|
||||
(string) $response->getBody(),
|
||||
true
|
||||
);
|
||||
|
||||
$orders = $data['data']['list'] ?? [];
|
||||
|
||||
$activeMerchants = [];
|
||||
$successOrderCount = 0;
|
||||
$totalOrderAmount = 0.0;
|
||||
$totalRealPay = 0.0;
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$merchantId = (int) ($order['merchant_id'] ?? 0);
|
||||
|
||||
if ($merchantId > 0) {
|
||||
$activeMerchants[$merchantId] = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* 这里暂时按 status = 1 作为成功订单。
|
||||
* 后面再确认你项目的真实状态含义。
|
||||
*/
|
||||
if ((int) ($order['status'] ?? -1) === 1) {
|
||||
$successOrderCount++;
|
||||
}
|
||||
|
||||
$totalOrderAmount += (float) ($order['order_price'] ?? 0);
|
||||
$totalRealPay += (float) ($order['real_pay'] ?? 0);
|
||||
}
|
||||
|
||||
return [
|
||||
'orderCount' => count($orders),
|
||||
'activeMerchantCount' => count($activeMerchants),
|
||||
'successOrderCount' => $successOrderCount,
|
||||
'totalOrderAmount' => round($totalOrderAmount, 2),
|
||||
'totalRealPay' => round($totalRealPay, 2),
|
||||
];
|
||||
},
|
||||
name: 'get_order_summary',
|
||||
description: '获取订单总数、活跃商户数、成功订单数和金额统计'
|
||||
)
|
||||
->build();
|
||||
|
||||
$transport = new StdioTransport();
|
||||
|
||||
$server->run($transport);
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api;
|
||||
|
||||
use App\Config\ApiConfig;
|
||||
use App\Controller\HealthController;
|
||||
use App\Controller\StatsController;
|
||||
use App\Http\MerchantApiClient;
|
||||
use App\Repositories\OfflineOrderRepository;
|
||||
use App\Service\StatsService;
|
||||
use App\Service\TimeRangeResolver;
|
||||
use App\Tools\AnalyzeOfflineOrdersTool;
|
||||
use App\Tools\MerchantRankingTool;
|
||||
use App\Tools\OrderSummaryTool;
|
||||
|
||||
final class ApiFactory
|
||||
{
|
||||
public static function create(?string $tokenOverride = null): ApiKernel
|
||||
{
|
||||
$apiClient = new MerchantApiClient(ApiConfig::fromEnvironment($tokenOverride));
|
||||
$orders = new OfflineOrderRepository($apiClient);
|
||||
$statsService = new StatsService(
|
||||
new OrderSummaryTool($orders),
|
||||
new AnalyzeOfflineOrdersTool($orders),
|
||||
new MerchantRankingTool($orders),
|
||||
new TimeRangeResolver(),
|
||||
);
|
||||
|
||||
return new ApiKernel(
|
||||
new HealthController(),
|
||||
new StatsController($statsService),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Api;
|
||||
|
||||
use App\Controller\HealthController;
|
||||
use App\Controller\StatsController;
|
||||
use App\Http\HttpRequest;
|
||||
use App\Http\JsonResponse;
|
||||
use App\Tools\AnalyzeOfflineOrdersTool;
|
||||
use App\Tools\MerchantRankingTool;
|
||||
use App\Tools\OrderSummaryTool;
|
||||
|
||||
final readonly class ApiKernel
|
||||
{
|
||||
public function __construct(
|
||||
private HealthController $healthController,
|
||||
private StatsController $statsController,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(HttpRequest $request): void
|
||||
{
|
||||
try {
|
||||
match ($request->path) {
|
||||
'/', '/health' => $this->ok($this->healthController->show()),
|
||||
'/tools' => $this->ok($this->tools()),
|
||||
'/api/order-summary' => $this->post($request, fn (): array => $this->statsController->orderSummary($request)),
|
||||
'/api/offline-stats' => $this->post($request, fn (): array => $this->statsController->offlineStats($request)),
|
||||
'/api/merchant-ranking' => $this->post($request, fn (): array => $this->statsController->merchantRanking($request)),
|
||||
default => JsonResponse::send([
|
||||
'success' => false,
|
||||
'error' => [
|
||||
'code' => 'not_found',
|
||||
'message' => 'API route not found.',
|
||||
],
|
||||
], 404),
|
||||
};
|
||||
} catch (\Throwable $exception) {
|
||||
JsonResponse::send([
|
||||
'success' => false,
|
||||
'error' => [
|
||||
'code' => 'server_error',
|
||||
'message' => $exception->getMessage(),
|
||||
],
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure(): array<string, mixed> $handler
|
||||
*/
|
||||
private function post(HttpRequest $request, \Closure $handler): void
|
||||
{
|
||||
if ($request->method !== 'POST') {
|
||||
JsonResponse::send([
|
||||
'success' => false,
|
||||
'error' => [
|
||||
'code' => 'method_not_allowed',
|
||||
'message' => 'This endpoint only supports POST.',
|
||||
],
|
||||
], 405);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ok($handler());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function ok(array $data): void
|
||||
{
|
||||
JsonResponse::send([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function tools(): array
|
||||
{
|
||||
return [
|
||||
'tools' => [
|
||||
[
|
||||
'name' => OrderSummaryTool::NAME,
|
||||
'description' => OrderSummaryTool::DESCRIPTION,
|
||||
'method' => 'POST',
|
||||
'path' => '/api/order-summary',
|
||||
'inputSchema' => OrderSummaryTool::inputSchema(),
|
||||
],
|
||||
[
|
||||
'name' => AnalyzeOfflineOrdersTool::NAME,
|
||||
'description' => AnalyzeOfflineOrdersTool::DESCRIPTION,
|
||||
'method' => 'POST',
|
||||
'path' => '/api/offline-stats',
|
||||
'inputSchema' => AnalyzeOfflineOrdersTool::inputSchema(),
|
||||
],
|
||||
[
|
||||
'name' => MerchantRankingTool::NAME,
|
||||
'description' => MerchantRankingTool::DESCRIPTION,
|
||||
'method' => 'POST',
|
||||
'path' => '/api/merchant-ranking',
|
||||
'inputSchema' => MerchantRankingTool::inputSchema(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Config;
|
||||
|
||||
final readonly class ApiConfig
|
||||
{
|
||||
public function __construct(
|
||||
public string $baseUri,
|
||||
public string $token,
|
||||
public int $timeout = 60,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromEnvironment(?string $tokenOverride = null): self
|
||||
{
|
||||
$baseUri = getenv('MERCHANT_API_BASE_URI') ?: 'https://tpoint.agrimedia.cn';
|
||||
$token = $tokenOverride ?: (getenv('MERCHANT_API_TOKEN') ?: '');
|
||||
$timeout = (int) (getenv('MERCHANT_API_TIMEOUT') ?: 60);
|
||||
|
||||
if ($token === '') {
|
||||
throw new \RuntimeException('Missing MERCHANT_API_TOKEN environment variable.');
|
||||
}
|
||||
|
||||
return new self(
|
||||
baseUri: rtrim($baseUri, '/'),
|
||||
token: $token,
|
||||
timeout: $timeout > 0 ? $timeout : 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Config;
|
||||
|
||||
final class Env
|
||||
{
|
||||
public static function load(string $path): void
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
|
||||
if ($lines === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
|
||||
if ($line === '' || str_starts_with($line, '#') || !str_contains($line, '=')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$key, $value] = explode('=', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
|
||||
if ($key === '' || getenv($key) !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = trim($value, "\"'");
|
||||
|
||||
putenv($key . '=' . $value);
|
||||
$_ENV[$key] = $value;
|
||||
$_SERVER[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
final class HealthController
|
||||
{
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function show(): array
|
||||
{
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'service' => 'merchant-ai-api',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Http\HttpRequest;
|
||||
use App\Service\StatsService;
|
||||
|
||||
final readonly class StatsController
|
||||
{
|
||||
public function __construct(private StatsService $statsService)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function orderSummary(HttpRequest $request): array
|
||||
{
|
||||
return $this->statsService->getOrderSummary($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function offlineStats(HttpRequest $request): array
|
||||
{
|
||||
return $this->statsService->getOfflineStats($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function merchantRanking(HttpRequest $request): array
|
||||
{
|
||||
return $this->statsService->getMerchantRanking($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
|
||||
final class DemoTools
|
||||
{
|
||||
#[McpTool(
|
||||
name: 'hello',
|
||||
description: '返回一段测试文字'
|
||||
)]
|
||||
public function hello(string $name = '朋友'): string
|
||||
{
|
||||
return "你好,{$name},PHP MCP Server 运行成功!";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
final readonly class HttpRequest
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
* @param array<string, string> $headers
|
||||
*/
|
||||
public function __construct(
|
||||
public string $method,
|
||||
public string $path,
|
||||
public array $input,
|
||||
public array $headers = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromGlobals(): self
|
||||
{
|
||||
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||
$uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
|
||||
$path = parse_url($uri, PHP_URL_PATH) ?: '/';
|
||||
$scriptName = (string) ($_SERVER['SCRIPT_NAME'] ?? '');
|
||||
|
||||
if ($scriptName !== '' && $scriptName !== '/index.php' && str_starts_with($path, dirname($scriptName))) {
|
||||
$path = substr($path, strlen(dirname($scriptName))) ?: '/';
|
||||
}
|
||||
|
||||
$input = $_GET;
|
||||
|
||||
if (in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
|
||||
$body = trim((string) file_get_contents('php://input'));
|
||||
|
||||
if ($body !== '') {
|
||||
$decoded = json_decode($body, true);
|
||||
|
||||
if (is_array($decoded)) {
|
||||
$input = array_merge($input, $decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new self($method, $path, $input, self::headersFromGlobals());
|
||||
}
|
||||
|
||||
public function bearerToken(): ?string
|
||||
{
|
||||
$authorization = $this->headers['authorization'] ?? '';
|
||||
|
||||
if (preg_match('/^Bearer\s+(.+)$/i', $authorization, $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = trim($matches[1]);
|
||||
|
||||
return $token !== '' ? $token : null;
|
||||
}
|
||||
|
||||
public function string(string $key): ?string
|
||||
{
|
||||
if (!array_key_exists($key, $this->input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = $this->input[$key];
|
||||
|
||||
if ($value === null || is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
public function int(string $key): ?int
|
||||
{
|
||||
if (!array_key_exists($key, $this->input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = $this->input[$key];
|
||||
|
||||
if ($value === null || $value === '' || is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private static function headersFromGlobals(): array
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (!is_string($value) || !str_starts_with($key, 'HTTP_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = strtolower(str_replace('_', '-', substr($key, 5)));
|
||||
$headers[$name] = $value;
|
||||
}
|
||||
|
||||
if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && is_string($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||
$headers['authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && is_string($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||
$headers['authorization'] = $_SERVER['HTTP_AUTHORIZATION'];
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
final class JsonResponse
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
public static function send(array $payload, int $statusCode = 200): void
|
||||
{
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use App\Config\ApiConfig;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
final class MerchantApiClient
|
||||
{
|
||||
private Client $client;
|
||||
|
||||
public function __construct(private readonly ApiConfig $config)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'base_uri' => $this->config->baseUri,
|
||||
'timeout' => $this->config->timeout,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $query
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getOfflineOrders(array $query): array
|
||||
{
|
||||
try {
|
||||
$response = $this->client->get('/adminapi/offline/list', [
|
||||
'headers' => [
|
||||
'accept' => 'application/json',
|
||||
'authori-zation' => 'Bearer ' . $this->config->token,
|
||||
],
|
||||
'query' => $query,
|
||||
]);
|
||||
} catch (GuzzleException $exception) {
|
||||
throw new \RuntimeException('Merchant API request failed: ' . $exception->getMessage(), 0, $exception);
|
||||
}
|
||||
|
||||
$data = json_decode((string) $response->getBody(), true);
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new \RuntimeException('Merchant API returned invalid JSON.');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Http\MerchantApiClient;
|
||||
|
||||
final readonly class OfflineOrderRepository
|
||||
{
|
||||
public function __construct(private MerchantApiClient $apiClient)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $query
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public function list(array $query): array
|
||||
{
|
||||
$page = 1;
|
||||
$orders = [];
|
||||
$totalCount = null;
|
||||
|
||||
do {
|
||||
$data = $this->apiClient->getOfflineOrders($query + [
|
||||
'page' => $page,
|
||||
'limit' => $this->pageLimit(),
|
||||
]);
|
||||
$pageOrders = $data['data']['list'] ?? [];
|
||||
|
||||
if (!is_array($pageOrders) || $pageOrders === []) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($totalCount === null) {
|
||||
$totalCount = isset($data['data']['count']) ? (int) $data['data']['count'] : null;
|
||||
}
|
||||
|
||||
$orders = array_merge($orders, array_values(array_filter($pageOrders, is_array(...))));
|
||||
$page++;
|
||||
} while ($this->shouldFetchNextPage($orders, $pageOrders, $totalCount));
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $orders
|
||||
* @param mixed $pageOrders
|
||||
*/
|
||||
private function shouldFetchNextPage(array $orders, mixed $pageOrders, ?int $totalCount): bool
|
||||
{
|
||||
if ($totalCount !== null) {
|
||||
return count($orders) < $totalCount;
|
||||
}
|
||||
|
||||
return is_array($pageOrders) && count($pageOrders) >= $this->pageLimit();
|
||||
}
|
||||
|
||||
private function pageLimit(): int
|
||||
{
|
||||
$limit = (int) (getenv('OFFLINE_ORDER_PAGE_LIMIT') ?: 100);
|
||||
|
||||
return $limit > 0 ? $limit : 100;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Http\HttpRequest;
|
||||
use App\Tools\AnalyzeOfflineOrdersTool;
|
||||
use App\Tools\MerchantRankingTool;
|
||||
use App\Tools\OrderSummaryTool;
|
||||
|
||||
final readonly class StatsService
|
||||
{
|
||||
public function __construct(
|
||||
private OrderSummaryTool $orderSummaryTool,
|
||||
private AnalyzeOfflineOrdersTool $offlineStatsTool,
|
||||
private MerchantRankingTool $merchantRankingTool,
|
||||
private TimeRangeResolver $timeRangeResolver,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getOrderSummary(HttpRequest $request): array
|
||||
{
|
||||
return ($this->orderSummaryTool)(
|
||||
$this->date($request),
|
||||
$request->int('status'),
|
||||
$request->string('keyword'),
|
||||
$request->string('fieldKey'),
|
||||
$request->string('payType'),
|
||||
$request->string('type'),
|
||||
$request->int('btcId'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getOfflineStats(HttpRequest $request): array
|
||||
{
|
||||
return ($this->offlineStatsTool)(
|
||||
$this->date($request),
|
||||
$request->int('status'),
|
||||
$request->string('keyword'),
|
||||
$request->string('fieldKey'),
|
||||
$request->string('payType'),
|
||||
$request->string('type'),
|
||||
$request->int('btcId'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getMerchantRanking(HttpRequest $request): array
|
||||
{
|
||||
return ($this->merchantRankingTool)(
|
||||
$this->date($request),
|
||||
$request->int('status'),
|
||||
$request->string('keyword'),
|
||||
$request->string('fieldKey'),
|
||||
$request->string('payType'),
|
||||
$request->string('type'),
|
||||
$request->int('btcId'),
|
||||
$request->string('sortBy'),
|
||||
$request->int('limit'),
|
||||
);
|
||||
}
|
||||
|
||||
private function date(HttpRequest $request): string
|
||||
{
|
||||
return $this->timeRangeResolver->resolve(
|
||||
$request->string('date'),
|
||||
$request->string('timePreset'),
|
||||
$request->string('startDate'),
|
||||
$request->string('endDate'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
final class TimeRangeResolver
|
||||
{
|
||||
private const DATE_FORMAT = 'Y/m/d';
|
||||
|
||||
public function __construct(private readonly string $timezone = 'Asia/Shanghai')
|
||||
{
|
||||
}
|
||||
|
||||
public function resolve(?string $date, ?string $timePreset, ?string $startDate, ?string $endDate): string
|
||||
{
|
||||
if ($this->filled($date)) {
|
||||
return $date;
|
||||
}
|
||||
|
||||
$now = new \DateTimeImmutable('now', new \DateTimeZone($this->timezone));
|
||||
$preset = $this->filled($timePreset) ? $timePreset : 'today';
|
||||
|
||||
[$start, $end] = match ($preset) {
|
||||
'today' => [$now, $now],
|
||||
'yesterday' => [$now->modify('-1 day'), $now->modify('-1 day')],
|
||||
'last_7_days' => [$now->modify('-6 days'), $now],
|
||||
'last_30_days' => [$now->modify('-29 days'), $now],
|
||||
'this_week' => [$now->modify('monday this week'), $now],
|
||||
'last_week' => [$now->modify('monday last week'), $now->modify('sunday last week')],
|
||||
'this_month' => [$now->modify('first day of this month'), $now],
|
||||
'last_month' => [$now->modify('first day of last month'), $now->modify('last day of last month')],
|
||||
'custom' => [$this->parseDate($startDate), $this->parseDate($endDate)],
|
||||
default => throw new \InvalidArgumentException('Unsupported timePreset: ' . $preset),
|
||||
};
|
||||
|
||||
if ($start > $end) {
|
||||
throw new \InvalidArgumentException('startDate cannot be later than endDate.');
|
||||
}
|
||||
|
||||
return $start->format(self::DATE_FORMAT) . '-' . $end->format(self::DATE_FORMAT);
|
||||
}
|
||||
|
||||
private function parseDate(?string $date): \DateTimeImmutable
|
||||
{
|
||||
if (!$this->filled($date)) {
|
||||
throw new \InvalidArgumentException('startDate and endDate are required when timePreset is custom.');
|
||||
}
|
||||
|
||||
$normalized = str_replace('/', '-', $date);
|
||||
$parsed = \DateTimeImmutable::createFromFormat('!Y-m-d', $normalized, new \DateTimeZone($this->timezone));
|
||||
|
||||
if (!$parsed instanceof \DateTimeImmutable) {
|
||||
throw new \InvalidArgumentException('Invalid date format, expected YYYY-MM-DD.');
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
private function filled(?string $value): bool
|
||||
{
|
||||
return $value !== null && trim($value) !== '';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
final class OfflineOrderQuery
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function build(
|
||||
?string $date = null,
|
||||
?int $status = null,
|
||||
?string $keyword = null,
|
||||
?string $fieldKey = null,
|
||||
?string $payType = null,
|
||||
?string $type = null,
|
||||
?int $btcId = null,
|
||||
): array {
|
||||
$query = [
|
||||
'status' => $status ?? -1,
|
||||
'field_key' => self::filled($fieldKey) ? $fieldKey : 'all',
|
||||
'time' => self::filled($date) ? $date : date('Y/m/d') . '-' . date('Y/m/d'),
|
||||
];
|
||||
|
||||
if (self::filled($keyword)) {
|
||||
$query['real_name'] = $keyword;
|
||||
}
|
||||
|
||||
if (self::filled($payType)) {
|
||||
$query['pay_type'] = $payType;
|
||||
}
|
||||
|
||||
if (self::filled($type)) {
|
||||
$query['type'] = $type;
|
||||
}
|
||||
|
||||
if ($btcId !== null && $btcId > 0) {
|
||||
$query['btc_id'] = $btcId;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private static function filled(?string $value): bool
|
||||
{
|
||||
return $value !== null && trim($value) !== '';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tools;
|
||||
|
||||
use App\Repositories\OfflineOrderRepository;
|
||||
use App\Support\OfflineOrderQuery;
|
||||
|
||||
final readonly class AnalyzeOfflineOrdersTool
|
||||
{
|
||||
public const NAME = 'get_offline_stats';
|
||||
|
||||
public const DESCRIPTION = '分析线下订单的状态、支付方式、订单类型和金额分布。';
|
||||
|
||||
public function __construct(private OfflineOrderRepository $orders)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
?string $date = null,
|
||||
?int $status = null,
|
||||
?string $keyword = null,
|
||||
?string $fieldKey = null,
|
||||
?string $payType = null,
|
||||
?string $type = null,
|
||||
?int $btcId = null,
|
||||
): array {
|
||||
$query = OfflineOrderQuery::build($date, $status, $keyword, $fieldKey, $payType, $type, $btcId);
|
||||
$orders = $this->orders->list($query);
|
||||
|
||||
$statusDistribution = [];
|
||||
$payTypeDistribution = [];
|
||||
$typeDistribution = [];
|
||||
$btcDistribution = [];
|
||||
$refundDistribution = [];
|
||||
$merchantStats = [];
|
||||
$totalOrderAmount = 0.0;
|
||||
$totalRealPay = 0.0;
|
||||
$totalCoinDeduction = 0.0;
|
||||
$totalBrokerDeduction = 0.0;
|
||||
$totalDiamondDeduction = 0.0;
|
||||
$totalTokenValue = 0.0;
|
||||
$tokenValueSummary = [];
|
||||
$totalPreferentialAmount = 0.0;
|
||||
$totalMerchantRatePrice = 0.0;
|
||||
$maxOrderAmount = 0.0;
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$statusKey = $this->statusLabel($order);
|
||||
$payTypeKey = $this->payTypeLabel($order['pay_type'] ?? null);
|
||||
$typeKey = $this->orderTypeLabel($order['type'] ?? null, $order['is_backend'] ?? null);
|
||||
$btcKey = $this->btcLabel($order['btc_id'] ?? null, $order['btc_type'] ?? null);
|
||||
$refundKey = $this->refundLabel($order['refund_status'] ?? null);
|
||||
$orderAmount = (float) ($order['order_price'] ?? 0);
|
||||
$realPay = (float) ($order['real_pay'] ?? 0);
|
||||
$coinDeduction = (float) ($order['coin_ded'] ?? 0);
|
||||
$brokerDeduction = (float) ($order['broker_ded'] ?? 0);
|
||||
$diamondDeduction = (float) ($order['diamond_ded'] ?? 0);
|
||||
$tokenValue = (float) ($order['diamond'] ?? 0);
|
||||
$preferentialAmount = (float) ($order['pr_amount'] ?? 0);
|
||||
$merchantRatePrice = (float) ($order['merchant_rate_price'] ?? 0);
|
||||
$merchantId = (int) ($order['merchant_id'] ?? 0);
|
||||
$tokenValueKey = $this->tokenValueLabel($order['btc_id'] ?? null);
|
||||
|
||||
$statusDistribution[$statusKey] = ($statusDistribution[$statusKey] ?? 0) + 1;
|
||||
$payTypeDistribution[$payTypeKey] = ($payTypeDistribution[$payTypeKey] ?? 0) + 1;
|
||||
$typeDistribution[$typeKey] = ($typeDistribution[$typeKey] ?? 0) + 1;
|
||||
$btcDistribution[$btcKey] = ($btcDistribution[$btcKey] ?? 0) + 1;
|
||||
$refundDistribution[$refundKey] = ($refundDistribution[$refundKey] ?? 0) + 1;
|
||||
$totalOrderAmount += $orderAmount;
|
||||
$totalRealPay += $realPay;
|
||||
$totalCoinDeduction += $coinDeduction;
|
||||
$totalBrokerDeduction += $brokerDeduction;
|
||||
$totalDiamondDeduction += $diamondDeduction;
|
||||
$totalTokenValue += $tokenValue;
|
||||
$tokenValueSummary[$tokenValueKey] = round(($tokenValueSummary[$tokenValueKey] ?? 0) + $tokenValue, 6);
|
||||
$totalPreferentialAmount += $preferentialAmount;
|
||||
$totalMerchantRatePrice += $merchantRatePrice;
|
||||
$maxOrderAmount = max($maxOrderAmount, $orderAmount);
|
||||
|
||||
if ($merchantId > 0) {
|
||||
if (!isset($merchantStats[$merchantId])) {
|
||||
$merchantStats[$merchantId] = [
|
||||
'merchantId' => $merchantId,
|
||||
'merchantName' => (string) ($order['merchant_name'] ?? ''),
|
||||
'orderCount' => 0,
|
||||
'totalOrderAmount' => 0.0,
|
||||
'totalRealPay' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
$merchantStats[$merchantId]['orderCount']++;
|
||||
$merchantStats[$merchantId]['totalOrderAmount'] += $orderAmount;
|
||||
$merchantStats[$merchantId]['totalRealPay'] += $realPay;
|
||||
}
|
||||
}
|
||||
|
||||
$orderCount = count($orders);
|
||||
$topMerchants = array_values($merchantStats);
|
||||
|
||||
usort($topMerchants, static fn (array $left, array $right): int => $right['totalRealPay'] <=> $left['totalRealPay']);
|
||||
$topMerchants = array_slice($topMerchants, 0, 10);
|
||||
|
||||
foreach ($topMerchants as $index => $merchant) {
|
||||
$topMerchants[$index]['totalOrderAmount'] = round((float) $merchant['totalOrderAmount'], 2);
|
||||
$topMerchants[$index]['totalRealPay'] = round((float) $merchant['totalRealPay'], 2);
|
||||
}
|
||||
|
||||
return [
|
||||
'orderCount' => $orderCount,
|
||||
'statusDistribution' => $statusDistribution,
|
||||
'payTypeDistribution' => $payTypeDistribution,
|
||||
'typeDistribution' => $typeDistribution,
|
||||
'btcDistribution' => $btcDistribution,
|
||||
'refundDistribution' => $refundDistribution,
|
||||
'totalOrderAmount' => round($totalOrderAmount, 2),
|
||||
'totalRealPay' => round($totalRealPay, 2),
|
||||
'totalCoinDeduction' => round($totalCoinDeduction, 2),
|
||||
'totalBrokerDeduction' => round($totalBrokerDeduction, 2),
|
||||
'totalDiamondDeduction' => round($totalDiamondDeduction, 2),
|
||||
'totalTokenValue' => round($totalTokenValue, 6),
|
||||
'tokenValueSummary' => $tokenValueSummary,
|
||||
'totalPreferentialAmount' => round($totalPreferentialAmount, 2),
|
||||
'totalMerchantRatePrice' => round($totalMerchantRatePrice, 2),
|
||||
'totalDeductionAmount' => round($totalCoinDeduction + $totalBrokerDeduction + $totalDiamondDeduction + $totalPreferentialAmount, 2),
|
||||
'averageOrderAmount' => $orderCount > 0 ? round($totalOrderAmount / $orderCount, 2) : 0,
|
||||
'averageRealPay' => $orderCount > 0 ? round($totalRealPay / $orderCount, 2) : 0,
|
||||
'maxOrderAmount' => round($maxOrderAmount, 2),
|
||||
'activeMerchantCount' => count($merchantStats),
|
||||
'topMerchantsByRealPay' => $topMerchants,
|
||||
'_debug_query' => $query,
|
||||
];
|
||||
}
|
||||
|
||||
public static function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => OrderSummaryTool::filterProperties(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
*/
|
||||
private function statusLabel(array $order): string
|
||||
{
|
||||
$status = (int) ($order['status'] ?? -999);
|
||||
$refundStatus = (int) ($order['refund_status'] ?? 0);
|
||||
|
||||
if ($status === 1 && $refundStatus === 1) {
|
||||
return '1 已完成(已退款)';
|
||||
}
|
||||
|
||||
if ($status === 1 && $refundStatus > 1) {
|
||||
return '1 已完成(部分退款)';
|
||||
}
|
||||
|
||||
return match ($status) {
|
||||
0 => '0 待付款',
|
||||
1 => '1 已完成',
|
||||
2 => '2 已取消',
|
||||
3 => '3 待使用/待核销',
|
||||
4 => '4 已退款',
|
||||
default => 'unknown 未知状态',
|
||||
};
|
||||
}
|
||||
|
||||
private function payTypeLabel(mixed $payType): string
|
||||
{
|
||||
return match ((int) $payType) {
|
||||
0 => '0 中台微信',
|
||||
1 => '1 中台支付宝',
|
||||
2 => '2 微信支付',
|
||||
3 => '3 公众号支付',
|
||||
4 => '4 中台公众号',
|
||||
7 => '7 蜜传微信支付',
|
||||
default => 'unknown 未知支付渠道',
|
||||
};
|
||||
}
|
||||
|
||||
private function orderTypeLabel(mixed $type, mixed $isBackend): string
|
||||
{
|
||||
$orderType = (int) $type;
|
||||
|
||||
if ($orderType === 0 && (int) $isBackend === 1) {
|
||||
return '0 后台扫码订单';
|
||||
}
|
||||
|
||||
if ($orderType === 0) {
|
||||
return '0 扫码订单';
|
||||
}
|
||||
|
||||
return match ($orderType) {
|
||||
1 => '1 点餐订单',
|
||||
2 => '2 团购订单',
|
||||
3 => '3 收银系统订单',
|
||||
4 => '4 曹操出行订单',
|
||||
5 => '5 视频团单',
|
||||
default => 'unknown 未知订单类型',
|
||||
};
|
||||
}
|
||||
|
||||
private function btcLabel(mixed $btcId, mixed $btcType): string
|
||||
{
|
||||
$label = match ((int) $btcId) {
|
||||
0 => '0 积分中台',
|
||||
1 => '1 钻石中台',
|
||||
3 => '3 绿宝石中台',
|
||||
4 => '4 算力中台',
|
||||
default => 'unknown 未知中台',
|
||||
};
|
||||
|
||||
if ($btcType !== null && trim((string) $btcType) !== '') {
|
||||
return $label . '(' . $btcType . ')';
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
private function tokenValueLabel(mixed $btcId): string
|
||||
{
|
||||
return match ((int) $btcId) {
|
||||
1 => 'diamond 钻石值',
|
||||
3 => 'diamond 绿宝石值',
|
||||
4 => 'diamond 算力值',
|
||||
default => 'diamond 其他中台值',
|
||||
};
|
||||
}
|
||||
|
||||
private function refundLabel(mixed $refundStatus): string
|
||||
{
|
||||
return match ((int) $refundStatus) {
|
||||
0 => '0 未退款',
|
||||
1 => '1 已退款',
|
||||
2 => '2 部分退款',
|
||||
default => 'unknown 未知退款状态',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tools;
|
||||
|
||||
use App\Repositories\OfflineOrderRepository;
|
||||
use App\Support\OfflineOrderQuery;
|
||||
|
||||
final readonly class MerchantRankingTool
|
||||
{
|
||||
public const NAME = 'get_merchant_ranking';
|
||||
|
||||
public const DESCRIPTION = '按订单数、订单金额或实付金额生成商户排行榜。';
|
||||
|
||||
public function __construct(private OfflineOrderRepository $orders)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
?string $date = null,
|
||||
?int $status = null,
|
||||
?string $keyword = null,
|
||||
?string $fieldKey = null,
|
||||
?string $payType = null,
|
||||
?string $type = null,
|
||||
?int $btcId = null,
|
||||
?string $sortBy = null,
|
||||
?int $limit = null,
|
||||
): array {
|
||||
$query = OfflineOrderQuery::build($date, $status, $keyword, $fieldKey, $payType, $type, $btcId);
|
||||
$orders = $this->orders->list($query);
|
||||
$ranking = [];
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$merchantId = (int) ($order['merchant_id'] ?? 0);
|
||||
|
||||
if ($merchantId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($ranking[$merchantId])) {
|
||||
$ranking[$merchantId] = [
|
||||
'merchantId' => $merchantId,
|
||||
'merchantName' => (string) ($order['merchant_name'] ?? $order['mer_name'] ?? ''),
|
||||
'orderCount' => 0,
|
||||
'successOrderCount' => 0,
|
||||
'totalOrderAmount' => 0.0,
|
||||
'totalRealPay' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
$ranking[$merchantId]['orderCount']++;
|
||||
$ranking[$merchantId]['totalOrderAmount'] += (float) ($order['order_price'] ?? 0);
|
||||
$ranking[$merchantId]['totalRealPay'] += (float) ($order['real_pay'] ?? 0);
|
||||
|
||||
if ((int) ($order['status'] ?? -1) === 1) {
|
||||
$ranking[$merchantId]['successOrderCount']++;
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_values($ranking);
|
||||
$sortField = $this->normalizeSortField($sortBy);
|
||||
|
||||
usort($items, static function (array $left, array $right) use ($sortField): int {
|
||||
return $right[$sortField] <=> $left[$sortField];
|
||||
});
|
||||
|
||||
$maxItems = $limit !== null && $limit > 0 ? min($limit, 100) : 10;
|
||||
$items = array_slice($items, 0, $maxItems);
|
||||
|
||||
foreach ($items as $index => $item) {
|
||||
$items[$index]['totalOrderAmount'] = round((float) $item['totalOrderAmount'], 2);
|
||||
$items[$index]['totalRealPay'] = round((float) $item['totalRealPay'], 2);
|
||||
}
|
||||
|
||||
return [
|
||||
'sortBy' => $sortField,
|
||||
'ranking' => $items,
|
||||
'_debug_query' => $query,
|
||||
];
|
||||
}
|
||||
|
||||
public static function inputSchema(): array
|
||||
{
|
||||
$properties = OrderSummaryTool::filterProperties();
|
||||
$properties['sortBy'] = [
|
||||
'type' => 'string',
|
||||
'description' => '排序字段:orderCount、successOrderCount、totalOrderAmount、totalRealPay。不传默认 totalRealPay。',
|
||||
];
|
||||
$properties['limit'] = [
|
||||
'type' => 'integer',
|
||||
'description' => '返回商户数量,默认 10,最大 100。',
|
||||
];
|
||||
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => $properties,
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeSortField(?string $sortBy): string
|
||||
{
|
||||
$allowed = ['orderCount', 'successOrderCount', 'totalOrderAmount', 'totalRealPay'];
|
||||
|
||||
if ($sortBy !== null && in_array($sortBy, $allowed, true)) {
|
||||
return $sortBy;
|
||||
}
|
||||
|
||||
return 'totalRealPay';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tools;
|
||||
|
||||
use App\Repositories\OfflineOrderRepository;
|
||||
use App\Support\OfflineOrderQuery;
|
||||
|
||||
final readonly class OrderSummaryTool
|
||||
{
|
||||
public const NAME = 'get_order_summary';
|
||||
|
||||
public const DESCRIPTION = '获取订单总数、活跃商户数、成功订单数和金额统计。可按日期、状态、关键词、搜索字段、支付方式、订单类型、商户分类ID筛选。';
|
||||
|
||||
public function __construct(private OfflineOrderRepository $orders)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
?string $date = null,
|
||||
?int $status = null,
|
||||
?string $keyword = null,
|
||||
?string $fieldKey = null,
|
||||
?string $payType = null,
|
||||
?string $type = null,
|
||||
?int $btcId = null,
|
||||
): array {
|
||||
$query = OfflineOrderQuery::build($date, $status, $keyword, $fieldKey, $payType, $type, $btcId);
|
||||
$orders = $this->orders->list($query);
|
||||
|
||||
$activeMerchants = [];
|
||||
$successOrderCount = 0;
|
||||
$totalOrderAmount = 0.0;
|
||||
$totalRealPay = 0.0;
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$merchantId = (int) ($order['merchant_id'] ?? 0);
|
||||
|
||||
if ($merchantId > 0) {
|
||||
$activeMerchants[$merchantId] = true;
|
||||
}
|
||||
|
||||
if ((int) ($order['status'] ?? -1) === 1) {
|
||||
$successOrderCount++;
|
||||
}
|
||||
|
||||
$totalOrderAmount += (float) ($order['order_price'] ?? 0);
|
||||
$totalRealPay += (float) ($order['real_pay'] ?? 0);
|
||||
}
|
||||
|
||||
return [
|
||||
'orderCount' => count($orders),
|
||||
'activeMerchantCount' => count($activeMerchants),
|
||||
'successOrderCount' => $successOrderCount,
|
||||
'totalOrderAmount' => round($totalOrderAmount, 2),
|
||||
'totalRealPay' => round($totalRealPay, 2),
|
||||
'_debug_query' => $query,
|
||||
];
|
||||
}
|
||||
|
||||
public static function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => self::filterProperties(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public static function filterProperties(): array
|
||||
{
|
||||
return [
|
||||
'date' => [
|
||||
'type' => 'string',
|
||||
'description' => '兼容旧参数:后台接口日期范围,格式如 2026/06/23-2026/06/23。不建议 AI 优先传此字段;建议传 timePreset 或 custom + startDate/endDate。',
|
||||
],
|
||||
'timePreset' => [
|
||||
'type' => 'string',
|
||||
'description' => '推荐给 AI 使用的时间意图。支持:today(今天)、yesterday(昨天)、last_7_days(最近7天)、last_30_days(最近30天)、this_week(本周)、last_week(上周)、this_month(本月)、last_month(上月)、custom(自定义)。不传默认 today。',
|
||||
'enum' => ['today', 'yesterday', 'last_7_days', 'last_30_days', 'this_week', 'last_week', 'this_month', 'last_month', 'custom'],
|
||||
],
|
||||
'startDate' => [
|
||||
'type' => 'string',
|
||||
'description' => '自定义开始日期,仅 timePreset=custom 时需要,格式 YYYY-MM-DD,例如 2026-06-01。',
|
||||
],
|
||||
'endDate' => [
|
||||
'type' => 'string',
|
||||
'description' => '自定义结束日期,仅 timePreset=custom 时需要,格式 YYYY-MM-DD,例如 2026-06-23。',
|
||||
],
|
||||
'status' => [
|
||||
'type' => 'integer',
|
||||
'description' => '订单状态,-1 表示全部。不传默认 -1。',
|
||||
],
|
||||
'fieldKey' => [
|
||||
'type' => 'string',
|
||||
'description' => '搜索字段,对应接口的 field_key 参数。常用值:order_sn(按订单号搜索)、all(全部字段)。不传默认 all。',
|
||||
],
|
||||
'keyword' => [
|
||||
'type' => 'string',
|
||||
'description' => '搜索关键词。当前接口按 real_name 参数传递。',
|
||||
],
|
||||
'payType' => [
|
||||
'type' => 'string',
|
||||
'description' => '支付方式,对应接口的 pay_type 参数。',
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'string',
|
||||
'description' => '订单类型,对应接口的 type 参数。',
|
||||
],
|
||||
'btcId' => [
|
||||
'type' => 'integer',
|
||||
'description' => '商户分类ID,对应接口的 btc_id 参数。',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,5 +9,6 @@ return array(
|
|||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,11 +6,27 @@ $vendorDir = dirname(__DIR__);
|
|||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-common/src'),
|
||||
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
|
||||
'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'),
|
||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||
'Symfony\\Component\\Uid\\' => array($vendorDir . '/symfony/uid'),
|
||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
|
||||
'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-middleware/src', $vendorDir . '/psr/http-server-handler/src'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
|
||||
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
|
||||
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
|
||||
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
|
||||
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
|
||||
'PHPStan\\PhpDocParser\\' => array($vendorDir . '/phpstan/phpdoc-parser/src'),
|
||||
'Opis\\Uri\\' => array($vendorDir . '/opis/uri/src'),
|
||||
'Opis\\String\\' => array($vendorDir . '/opis/string/src'),
|
||||
'Opis\\JsonSchema\\' => array($vendorDir . '/opis/json-schema/src'),
|
||||
'Mcp\\' => array($vendorDir . '/mcp/sdk/src'),
|
||||
'Http\\Discovery\\' => array($vendorDir . '/php-http/discovery/src'),
|
||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||
'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/src'),
|
||||
'App\\' => array($baseDir . '/src'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,18 +10,49 @@ class ComposerStaticInit842ab640c1f6a67d129686b79420231b
|
|||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'09f6b20656683369174dd6fa83b7e5fb' => __DIR__ . '/..' . '/symfony/polyfill-uuid/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'p' =>
|
||||
array (
|
||||
'phpDocumentor\\Reflection\\' => 25,
|
||||
),
|
||||
'W' =>
|
||||
array (
|
||||
'Webmozart\\Assert\\' => 17,
|
||||
),
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Polyfill\\Uuid\\' => 22,
|
||||
'Symfony\\Polyfill\\Php80\\' => 23,
|
||||
'Symfony\\Component\\Uid\\' => 22,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\Log\\' => 8,
|
||||
'Psr\\Http\\Server\\' => 16,
|
||||
'Psr\\Http\\Message\\' => 17,
|
||||
'Psr\\Http\\Client\\' => 16,
|
||||
'Psr\\EventDispatcher\\' => 20,
|
||||
'Psr\\Container\\' => 14,
|
||||
'Psr\\Clock\\' => 10,
|
||||
'PHPStan\\PhpDocParser\\' => 21,
|
||||
),
|
||||
'O' =>
|
||||
array (
|
||||
'Opis\\Uri\\' => 9,
|
||||
'Opis\\String\\' => 12,
|
||||
'Opis\\JsonSchema\\' => 16,
|
||||
),
|
||||
'M' =>
|
||||
array (
|
||||
'Mcp\\' => 4,
|
||||
),
|
||||
'H' =>
|
||||
array (
|
||||
'Http\\Discovery\\' => 15,
|
||||
),
|
||||
'G' =>
|
||||
array (
|
||||
|
|
@ -29,6 +60,10 @@ class ComposerStaticInit842ab640c1f6a67d129686b79420231b
|
|||
'GuzzleHttp\\Promise\\' => 19,
|
||||
'GuzzleHttp\\' => 11,
|
||||
),
|
||||
'D' =>
|
||||
array (
|
||||
'Doctrine\\Deprecations\\' => 22,
|
||||
),
|
||||
'A' =>
|
||||
array (
|
||||
'App\\' => 4,
|
||||
|
|
@ -36,10 +71,37 @@ class ComposerStaticInit842ab640c1f6a67d129686b79420231b
|
|||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'phpDocumentor\\Reflection\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
|
||||
1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
|
||||
2 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
|
||||
),
|
||||
'Webmozart\\Assert\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/webmozart/assert/src',
|
||||
),
|
||||
'Symfony\\Polyfill\\Uuid\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-uuid',
|
||||
),
|
||||
'Symfony\\Polyfill\\Php80\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
|
||||
),
|
||||
'Symfony\\Component\\Uid\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/uid',
|
||||
),
|
||||
'Psr\\Log\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/log/src',
|
||||
),
|
||||
'Psr\\Http\\Server\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-server-middleware/src',
|
||||
1 => __DIR__ . '/..' . '/psr/http-server-handler/src',
|
||||
),
|
||||
'Psr\\Http\\Message\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-factory/src',
|
||||
|
|
@ -49,6 +111,42 @@ class ComposerStaticInit842ab640c1f6a67d129686b79420231b
|
|||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-client/src',
|
||||
),
|
||||
'Psr\\EventDispatcher\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
|
||||
),
|
||||
'Psr\\Container\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/container/src',
|
||||
),
|
||||
'Psr\\Clock\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/clock/src',
|
||||
),
|
||||
'PHPStan\\PhpDocParser\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/phpstan/phpdoc-parser/src',
|
||||
),
|
||||
'Opis\\Uri\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/opis/uri/src',
|
||||
),
|
||||
'Opis\\String\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/opis/string/src',
|
||||
),
|
||||
'Opis\\JsonSchema\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/opis/json-schema/src',
|
||||
),
|
||||
'Mcp\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/mcp/sdk/src',
|
||||
),
|
||||
'Http\\Discovery\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/php-http/discovery/src',
|
||||
),
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
|
||||
|
|
@ -61,6 +159,10 @@ class ComposerStaticInit842ab640c1f6a67d129686b79420231b
|
|||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
|
||||
),
|
||||
'Doctrine\\Deprecations\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/doctrine/deprecations/src',
|
||||
),
|
||||
'App\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/src',
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,9 +1,9 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '7984fdc92f51bf7a1b1cac42da4ec23fb57ccf8d',
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
|
@ -11,14 +11,23 @@
|
|||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '7984fdc92f51bf7a1b1cac42da4ec23fb57ccf8d',
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'doctrine/deprecations' => array(
|
||||
'pretty_version' => '1.1.6',
|
||||
'version' => '1.1.6.0',
|
||||
'reference' => 'd4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../doctrine/deprecations',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'guzzlehttp/guzzle' => array(
|
||||
'pretty_version' => '7.12.1',
|
||||
'version' => '7.12.1.0',
|
||||
|
|
@ -46,6 +55,126 @@
|
|||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'mcp/sdk' => array(
|
||||
'pretty_version' => 'v0.6.0',
|
||||
'version' => '0.6.0.0',
|
||||
'reference' => '433c84b58af346dd32f15f9909679e96a46ebe23',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../mcp/sdk',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'opis/json-schema' => array(
|
||||
'pretty_version' => '2.6.0',
|
||||
'version' => '2.6.0.0',
|
||||
'reference' => '8458763e0dd0b6baa310e04f1829fc73da4e8c8a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../opis/json-schema',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'opis/string' => array(
|
||||
'pretty_version' => '2.1.0',
|
||||
'version' => '2.1.0.0',
|
||||
'reference' => '3e4d2aaff518ac518530b89bb26ed40f4503635e',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../opis/string',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'opis/uri' => array(
|
||||
'pretty_version' => '1.1.0',
|
||||
'version' => '1.1.0.0',
|
||||
'reference' => '0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../opis/uri',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'php-http/async-client-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'php-http/client-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'php-http/discovery' => array(
|
||||
'pretty_version' => '1.20.0',
|
||||
'version' => '1.20.0.0',
|
||||
'reference' => '82fe4c73ef3363caed49ff8dd1539ba06044910d',
|
||||
'type' => 'composer-plugin',
|
||||
'install_path' => __DIR__ . '/../php-http/discovery',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpdocumentor/reflection-common' => array(
|
||||
'pretty_version' => '2.2.0',
|
||||
'version' => '2.2.0.0',
|
||||
'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpdocumentor/reflection-common',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpdocumentor/reflection-docblock' => array(
|
||||
'pretty_version' => '6.0.3',
|
||||
'version' => '6.0.3.0',
|
||||
'reference' => '7bae67520aa9f5ecc506d646810bd40d9da54582',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpdocumentor/reflection-docblock',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpdocumentor/type-resolver' => array(
|
||||
'pretty_version' => '2.0.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => '327a05bbee54120d4786a0dc67aad30226ad4cf9',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpdocumentor/type-resolver',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpstan/phpdoc-parser' => array(
|
||||
'pretty_version' => '2.3.2',
|
||||
'version' => '2.3.2.0',
|
||||
'reference' => 'a004701b11273a26cd7955a61d67a7f1e525a45a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpstan/phpdoc-parser',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/clock' => array(
|
||||
'pretty_version' => '1.0.0',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/clock',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/container' => array(
|
||||
'pretty_version' => '2.0.2',
|
||||
'version' => '2.0.2.0',
|
||||
'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/container',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/event-dispatcher' => array(
|
||||
'pretty_version' => '1.0.0',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/event-dispatcher',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-client' => array(
|
||||
'pretty_version' => '1.0.3',
|
||||
'version' => '1.0.3.0',
|
||||
|
|
@ -58,7 +187,8 @@
|
|||
'psr/http-client-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '1.0',
|
||||
0 => '*',
|
||||
1 => '1.0',
|
||||
),
|
||||
),
|
||||
'psr/http-factory' => array(
|
||||
|
|
@ -73,7 +203,8 @@
|
|||
'psr/http-factory-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '1.0',
|
||||
0 => '*',
|
||||
1 => '1.0',
|
||||
),
|
||||
),
|
||||
'psr/http-message' => array(
|
||||
|
|
@ -88,9 +219,37 @@
|
|||
'psr/http-message-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '1.0',
|
||||
0 => '*',
|
||||
1 => '1.0',
|
||||
),
|
||||
),
|
||||
'psr/http-server-handler' => array(
|
||||
'pretty_version' => '1.0.2',
|
||||
'version' => '1.0.2.0',
|
||||
'reference' => '84c4fb66179be4caaf8e97bd239203245302e7d4',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-server-handler',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-server-middleware' => array(
|
||||
'pretty_version' => '1.0.2',
|
||||
'version' => '1.0.2.0',
|
||||
'reference' => 'c1481f747daaa6a0782775cd6a8c26a1bf4a3829',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-server-middleware',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/log' => array(
|
||||
'pretty_version' => '3.0.2',
|
||||
'version' => '3.0.2.0',
|
||||
'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/log',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'ralouphie/getallheaders' => array(
|
||||
'pretty_version' => '3.0.3',
|
||||
'version' => '3.0.3.0',
|
||||
|
|
@ -118,5 +277,32 @@
|
|||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-uuid' => array(
|
||||
'pretty_version' => 'v1.37.0',
|
||||
'version' => '1.37.0.0',
|
||||
'reference' => '26dfec253c4cf3e51b541b52ddf7e42cb0908e94',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-uuid',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/uid' => array(
|
||||
'pretty_version' => 'v7.4.9',
|
||||
'version' => '7.4.9.0',
|
||||
'reference' => '2676b524340abcfe4d6151ec698463cebafee439',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/uid',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'webmozart/assert' => array(
|
||||
'pretty_version' => '2.4.1',
|
||||
'version' => '2.4.1.0',
|
||||
'reference' => '2ccb7c2e821038c03a3e6e1700c570c158c55f70',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../webmozart/assert',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 433c84b58af346dd32f15f9909679e96a46ebe23
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 8458763e0dd0b6baa310e04f1829fc73da4e8c8a
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3e4d2aaff518ac518530b89bb26ed40f4503635e
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 82fe4c73ef3363caed49ff8dd1539ba06044910d
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 1d01c49d4ed62f25aa84a747ad35d5a16924662b
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7bae67520aa9f5ecc506d646810bd40d9da54582
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 327a05bbee54120d4786a0dc67aad30226ad4cf9
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit a004701b11273a26cd7955a61d67a7f1e525a45a
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit e41a24703d4560fd0acb709162f73b8adfc3aa0d
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c71ecc56dfe541dbd90c5360474fbc405f8d5963
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit dbefd12671e8a14ec7f180cab83036ed26714bb0
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 84c4fb66179be4caaf8e97bd239203245302e7d4
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c1481f747daaa6a0782775cd6a8c26a1bf4a3829
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f16e1d5863e37f8d8c2a01719f5b34baa2b714d3
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 26dfec253c4cf3e51b541b52ddf7e42cb0908e94
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2676b524340abcfe4d6151ec698463cebafee439
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2ccb7c2e821038c03a3e6e1700c570c158c55f70
|
||||
Loading…
Reference in New Issue