初始化
This commit is contained in:
parent
3b4674ed92
commit
8484851bd9
|
|
@ -2,4 +2,4 @@ RewriteEngine On
|
||||||
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
RewriteRule ^ server.php [QSA,L]
|
RewriteRule ^ public/index.php [QSA,L]
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -15,7 +15,7 @@ MERCHANT_API_TIMEOUT=10
|
||||||
## Start Locally
|
## Start Locally
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php -S 127.0.0.1:8080 server.php
|
php -S 127.0.0.1:8080 -t public
|
||||||
```
|
```
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
|
|
@ -44,19 +44,23 @@ Error:
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
|
|
||||||
### `GET|POST /tools`
|
### `GET /health`
|
||||||
|
|
||||||
|
Returns service health status.
|
||||||
|
|
||||||
|
### `GET /tools`
|
||||||
|
|
||||||
Returns available API tools and input schemas.
|
Returns available API tools and input schemas.
|
||||||
|
|
||||||
### `GET|POST /api/order-summary`
|
### `POST /api/order-summary`
|
||||||
|
|
||||||
Returns order count, active merchant count, successful order count, and amount totals.
|
Returns order count, active merchant count, successful order count, and amount totals.
|
||||||
|
|
||||||
### `GET|POST /api/offline-orders/analyze`
|
### `POST /api/offline-stats`
|
||||||
|
|
||||||
Returns status, payment type, order type, and amount distribution analysis.
|
Returns status, payment type, order type, and amount distribution analysis.
|
||||||
|
|
||||||
### `GET|POST /api/merchant-ranking`
|
### `POST /api/merchant-ranking`
|
||||||
|
|
||||||
Returns merchant ranking by `orderCount`, `successOrderCount`, `totalOrderAmount`, or `totalRealPay`.
|
Returns merchant ranking by `orderCount`, `successOrderCount`, `totalOrderAmount`, or `totalRealPay`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
use App\Api\ApiFactory;
|
||||||
|
use App\Http\HttpRequest;
|
||||||
|
|
||||||
|
ApiFactory::create()->handle(HttpRequest::fromGlobals());
|
||||||
|
|
@ -2,9 +2,4 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/public/index.php';
|
||||||
|
|
||||||
use App\Api\ApiFactory;
|
|
||||||
use App\Http\HttpRequest;
|
|
||||||
|
|
||||||
ApiFactory::create()->handle(HttpRequest::fromGlobals());
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@ declare(strict_types=1);
|
||||||
namespace App\Api;
|
namespace App\Api;
|
||||||
|
|
||||||
use App\Config\ApiConfig;
|
use App\Config\ApiConfig;
|
||||||
|
use App\Controller\HealthController;
|
||||||
|
use App\Controller\StatsController;
|
||||||
use App\Http\MerchantApiClient;
|
use App\Http\MerchantApiClient;
|
||||||
use App\Repositories\OfflineOrderRepository;
|
use App\Repositories\OfflineOrderRepository;
|
||||||
|
use App\Service\StatsService;
|
||||||
use App\Tools\AnalyzeOfflineOrdersTool;
|
use App\Tools\AnalyzeOfflineOrdersTool;
|
||||||
use App\Tools\MerchantRankingTool;
|
use App\Tools\MerchantRankingTool;
|
||||||
use App\Tools\OrderSummaryTool;
|
use App\Tools\OrderSummaryTool;
|
||||||
|
|
@ -17,11 +20,15 @@ final class ApiFactory
|
||||||
{
|
{
|
||||||
$apiClient = new MerchantApiClient(ApiConfig::fromEnvironment());
|
$apiClient = new MerchantApiClient(ApiConfig::fromEnvironment());
|
||||||
$orders = new OfflineOrderRepository($apiClient);
|
$orders = new OfflineOrderRepository($apiClient);
|
||||||
|
$statsService = new StatsService(
|
||||||
return new ApiKernel(
|
|
||||||
new OrderSummaryTool($orders),
|
new OrderSummaryTool($orders),
|
||||||
new AnalyzeOfflineOrdersTool($orders),
|
new AnalyzeOfflineOrdersTool($orders),
|
||||||
new MerchantRankingTool($orders),
|
new MerchantRankingTool($orders),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return new ApiKernel(
|
||||||
|
new HealthController(),
|
||||||
|
new StatsController($statsService),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api;
|
namespace App\Api;
|
||||||
|
|
||||||
|
use App\Controller\HealthController;
|
||||||
|
use App\Controller\StatsController;
|
||||||
use App\Http\HttpRequest;
|
use App\Http\HttpRequest;
|
||||||
use App\Http\JsonResponse;
|
use App\Http\JsonResponse;
|
||||||
use App\Tools\AnalyzeOfflineOrdersTool;
|
use App\Tools\AnalyzeOfflineOrdersTool;
|
||||||
|
|
@ -13,9 +15,8 @@ use App\Tools\OrderSummaryTool;
|
||||||
final readonly class ApiKernel
|
final readonly class ApiKernel
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private OrderSummaryTool $orderSummaryTool,
|
private HealthController $healthController,
|
||||||
private AnalyzeOfflineOrdersTool $analyzeOfflineOrdersTool,
|
private StatsController $statsController,
|
||||||
private MerchantRankingTool $merchantRankingTool,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,11 +24,11 @@ final readonly class ApiKernel
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
match ($request->path) {
|
match ($request->path) {
|
||||||
'/', '/health' => $this->ok(['status' => 'ok', 'service' => 'merchant-ai-api']),
|
'/', '/health' => $this->ok($this->healthController->show()),
|
||||||
'/tools' => $this->ok($this->tools()),
|
'/tools' => $this->ok($this->tools()),
|
||||||
'/api/order-summary' => $this->ok($this->orderSummary($request)),
|
'/api/order-summary' => $this->post($request, fn (): array => $this->statsController->orderSummary($request)),
|
||||||
'/api/offline-orders/analyze' => $this->ok($this->analyzeOfflineOrders($request)),
|
'/api/offline-stats' => $this->post($request, fn (): array => $this->statsController->offlineStats($request)),
|
||||||
'/api/merchant-ranking' => $this->ok($this->merchantRanking($request)),
|
'/api/merchant-ranking' => $this->post($request, fn (): array => $this->statsController->merchantRanking($request)),
|
||||||
default => JsonResponse::send([
|
default => JsonResponse::send([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => [
|
'error' => [
|
||||||
|
|
@ -47,6 +48,26 @@ final readonly class ApiKernel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
* @param array<string, mixed> $data
|
||||||
*/
|
*/
|
||||||
|
|
@ -68,75 +89,25 @@ final readonly class ApiKernel
|
||||||
[
|
[
|
||||||
'name' => OrderSummaryTool::NAME,
|
'name' => OrderSummaryTool::NAME,
|
||||||
'description' => OrderSummaryTool::DESCRIPTION,
|
'description' => OrderSummaryTool::DESCRIPTION,
|
||||||
'method' => 'GET|POST',
|
'method' => 'POST',
|
||||||
'path' => '/api/order-summary',
|
'path' => '/api/order-summary',
|
||||||
'inputSchema' => OrderSummaryTool::inputSchema(),
|
'inputSchema' => OrderSummaryTool::inputSchema(),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => AnalyzeOfflineOrdersTool::NAME,
|
'name' => AnalyzeOfflineOrdersTool::NAME,
|
||||||
'description' => AnalyzeOfflineOrdersTool::DESCRIPTION,
|
'description' => AnalyzeOfflineOrdersTool::DESCRIPTION,
|
||||||
'method' => 'GET|POST',
|
'method' => 'POST',
|
||||||
'path' => '/api/offline-orders/analyze',
|
'path' => '/api/offline-stats',
|
||||||
'inputSchema' => AnalyzeOfflineOrdersTool::inputSchema(),
|
'inputSchema' => AnalyzeOfflineOrdersTool::inputSchema(),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => MerchantRankingTool::NAME,
|
'name' => MerchantRankingTool::NAME,
|
||||||
'description' => MerchantRankingTool::DESCRIPTION,
|
'description' => MerchantRankingTool::DESCRIPTION,
|
||||||
'method' => 'GET|POST',
|
'method' => 'POST',
|
||||||
'path' => '/api/merchant-ranking',
|
'path' => '/api/merchant-ranking',
|
||||||
'inputSchema' => MerchantRankingTool::inputSchema(),
|
'inputSchema' => MerchantRankingTool::inputSchema(),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
private function orderSummary(HttpRequest $request): array
|
|
||||||
{
|
|
||||||
return ($this->orderSummaryTool)(
|
|
||||||
$request->string('date'),
|
|
||||||
$request->int('status'),
|
|
||||||
$request->string('keyword'),
|
|
||||||
$request->string('fieldKey'),
|
|
||||||
$request->string('payType'),
|
|
||||||
$request->string('type'),
|
|
||||||
$request->int('btcId'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
private function analyzeOfflineOrders(HttpRequest $request): array
|
|
||||||
{
|
|
||||||
return ($this->analyzeOfflineOrdersTool)(
|
|
||||||
$request->string('date'),
|
|
||||||
$request->int('status'),
|
|
||||||
$request->string('keyword'),
|
|
||||||
$request->string('fieldKey'),
|
|
||||||
$request->string('payType'),
|
|
||||||
$request->string('type'),
|
|
||||||
$request->int('btcId'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
private function merchantRanking(HttpRequest $request): array
|
|
||||||
{
|
|
||||||
return ($this->merchantRankingTool)(
|
|
||||||
$request->string('date'),
|
|
||||||
$request->int('status'),
|
|
||||||
$request->string('keyword'),
|
|
||||||
$request->string('fieldKey'),
|
|
||||||
$request->string('payType'),
|
|
||||||
$request->string('type'),
|
|
||||||
$request->int('btcId'),
|
|
||||||
$request->string('sortBy'),
|
|
||||||
$request->int('limit'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,12 @@ final readonly class HttpRequest
|
||||||
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||||
$uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
|
$uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
|
||||||
$path = parse_url($uri, PHP_URL_PATH) ?: '/';
|
$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;
|
$input = $_GET;
|
||||||
|
|
||||||
if (in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
|
if (in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?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,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function getOrderSummary(HttpRequest $request): array
|
||||||
|
{
|
||||||
|
return ($this->orderSummaryTool)(
|
||||||
|
$request->string('date'),
|
||||||
|
$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)(
|
||||||
|
$request->string('date'),
|
||||||
|
$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)(
|
||||||
|
$request->string('date'),
|
||||||
|
$request->int('status'),
|
||||||
|
$request->string('keyword'),
|
||||||
|
$request->string('fieldKey'),
|
||||||
|
$request->string('payType'),
|
||||||
|
$request->string('type'),
|
||||||
|
$request->int('btcId'),
|
||||||
|
$request->string('sortBy'),
|
||||||
|
$request->int('limit'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ use App\Support\OfflineOrderQuery;
|
||||||
|
|
||||||
final readonly class AnalyzeOfflineOrdersTool
|
final readonly class AnalyzeOfflineOrdersTool
|
||||||
{
|
{
|
||||||
public const NAME = 'analyze_offline_orders';
|
public const NAME = 'get_offline_stats';
|
||||||
|
|
||||||
public const DESCRIPTION = '分析线下订单的状态、支付方式、订单类型和金额分布。';
|
public const DESCRIPTION = '分析线下订单的状态、支付方式、订单类型和金额分布。';
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue