From 404260f9d781e213b473cafacdff2e3b2b3976d4 Mon Sep 17 00:00:00 2001 From: "1173117610@qq.com" Date: Wed, 24 Jun 2026 14:35:56 +0800 Subject: [PATCH] =?UTF-8?q?=20=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +- docs/AI_INTEGRATION_GUIDE.md | 307 ++++++++++++++++++++++++++++++ src/Api/ApiFactory.php | 2 + src/Service/StatsService.php | 17 +- src/Service/TimeRangeResolver.php | 64 +++++++ src/Tools/OrderSummaryTool.php | 15 +- 6 files changed, 406 insertions(+), 6 deletions(-) create mode 100644 docs/AI_INTEGRATION_GUIDE.md create mode 100644 src/Service/TimeRangeResolver.php diff --git a/README.md b/README.md index e741b90..113b0ed 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,10 @@ Returns merchant ranking by `orderCount`, `successOrderCount`, `totalOrderAmount ## Common Parameters -- `date`: date range, for example `2026/06/23-2026/06/23`; defaults to today. +- `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`. - `keyword`: search keyword, sent to upstream API as `real_name`. - `fieldKey`: upstream `field_key`; defaults to `all`. @@ -84,5 +87,5 @@ Returns merchant ranking by `orderCount`, `successOrderCount`, `totalOrderAmount ```bash curl -X POST http://127.0.0.1:8080/api/merchant-ranking \ -H "Content-Type: application/json" \ - -d '{"date":"2026/06/23-2026/06/23","sortBy":"totalRealPay","limit":10}' + -d '{"timePreset":"today","sortBy":"totalRealPay","limit":10}' ``` diff --git a/docs/AI_INTEGRATION_GUIDE.md b/docs/AI_INTEGRATION_GUIDE.md new file mode 100644 index 0000000..4fca11d --- /dev/null +++ b/docs/AI_INTEGRATION_GUIDE.md @@ -0,0 +1,307 @@ +# 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` + +用途:订单状态分布、支付方式分布、订单类型分布、平均订单金额、平均实付金额、最大订单金额。 + +### 3.3 商户排行榜 + +```http +POST /api/merchant-ranking +``` + +对应工具:`get_merchant_ranking` + +用途:按订单数、成功订单数、订单金额或实付金额生成商户排行。 + +## 4. 请求格式 + +所有统计接口都使用: + +```http +Content-Type: application/json +``` + +统一请求示例: + +```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` | + +## 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 调用约束 + +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. 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}' +``` + +## 12. 推荐给 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。 +``` diff --git a/src/Api/ApiFactory.php b/src/Api/ApiFactory.php index 7d9a366..be1cc65 100644 --- a/src/Api/ApiFactory.php +++ b/src/Api/ApiFactory.php @@ -10,6 +10,7 @@ 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; @@ -24,6 +25,7 @@ final class ApiFactory new OrderSummaryTool($orders), new AnalyzeOfflineOrdersTool($orders), new MerchantRankingTool($orders), + new TimeRangeResolver(), ); return new ApiKernel( diff --git a/src/Service/StatsService.php b/src/Service/StatsService.php index 67f0923..7889282 100644 --- a/src/Service/StatsService.php +++ b/src/Service/StatsService.php @@ -15,6 +15,7 @@ final readonly class StatsService private OrderSummaryTool $orderSummaryTool, private AnalyzeOfflineOrdersTool $offlineStatsTool, private MerchantRankingTool $merchantRankingTool, + private TimeRangeResolver $timeRangeResolver, ) { } @@ -24,7 +25,7 @@ final readonly class StatsService public function getOrderSummary(HttpRequest $request): array { return ($this->orderSummaryTool)( - $request->string('date'), + $this->date($request), $request->int('status'), $request->string('keyword'), $request->string('fieldKey'), @@ -40,7 +41,7 @@ final readonly class StatsService public function getOfflineStats(HttpRequest $request): array { return ($this->offlineStatsTool)( - $request->string('date'), + $this->date($request), $request->int('status'), $request->string('keyword'), $request->string('fieldKey'), @@ -56,7 +57,7 @@ final readonly class StatsService public function getMerchantRanking(HttpRequest $request): array { return ($this->merchantRankingTool)( - $request->string('date'), + $this->date($request), $request->int('status'), $request->string('keyword'), $request->string('fieldKey'), @@ -67,4 +68,14 @@ final readonly class StatsService $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'), + ); + } } diff --git a/src/Service/TimeRangeResolver.php b/src/Service/TimeRangeResolver.php new file mode 100644 index 0000000..cabd89e --- /dev/null +++ b/src/Service/TimeRangeResolver.php @@ -0,0 +1,64 @@ +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) !== ''; + } +} diff --git a/src/Tools/OrderSummaryTool.php b/src/Tools/OrderSummaryTool.php index a6fa53e..9a05089 100644 --- a/src/Tools/OrderSummaryTool.php +++ b/src/Tools/OrderSummaryTool.php @@ -75,7 +75,20 @@ final readonly class OrderSummaryTool return [ 'date' => [ 'type' => 'string', - 'description' => '查询日期,格式如 2026/06/23-2026/06/23(起止日期用 - 连接)。不传默认当天。', + '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',