添加其他字段
This commit is contained in:
parent
b5c68be1a6
commit
6d5191b5e8
|
|
@ -0,0 +1,5 @@
|
|||
MERCHANT_API_BASE_URI=https://tpoint.agrimedia.cn
|
||||
MERCHANT_API_TOKEN=your-admin-api-token
|
||||
MERCHANT_API_TIMEOUT=60
|
||||
|
||||
OFFLINE_ORDER_PAGE_LIMIT=100
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.env
|
||||
vendor/
|
||||
.idea/
|
||||
|
|
@ -4,13 +4,19 @@ PHP 8.2 JSON API service for merchant order statistics, offline order analysis,
|
|||
|
||||
## 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=10
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,16 @@ POST /api/merchant-ranking
|
|||
Content-Type: application/json
|
||||
```
|
||||
|
||||
如果调用方请求头中传入:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <后台订单接口 token>
|
||||
```
|
||||
|
||||
本服务会优先使用该 token 请求后台订单接口;如果没有传 `Authorization`,则使用服务器 `.env` 中的 `MERCHANT_API_TOKEN`。
|
||||
|
||||
注意:只有可信后端服务可以传该 header,不建议在浏览器前端或公开客户端暴露后台 token。
|
||||
|
||||
统一请求示例:
|
||||
|
||||
```json
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ declare(strict_types=1);
|
|||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use App\Api\ApiFactory;
|
||||
use App\Config\Env;
|
||||
use App\Http\HttpRequest;
|
||||
|
||||
ApiFactory::create()->handle(HttpRequest::fromGlobals());
|
||||
Env::load(dirname(__DIR__) . '/.env');
|
||||
|
||||
$request = HttpRequest::fromGlobals();
|
||||
|
||||
ApiFactory::create($request->bearerToken())->handle($request);
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ use App\Tools\OrderSummaryTool;
|
|||
|
||||
final class ApiFactory
|
||||
{
|
||||
public static function create(): ApiKernel
|
||||
public static function create(?string $tokenOverride = null): ApiKernel
|
||||
{
|
||||
$apiClient = new MerchantApiClient(ApiConfig::fromEnvironment());
|
||||
$apiClient = new MerchantApiClient(ApiConfig::fromEnvironment($tokenOverride));
|
||||
$orders = new OfflineOrderRepository($apiClient);
|
||||
$statsService = new StatsService(
|
||||
new OrderSummaryTool($orders),
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ final readonly class ApiConfig
|
|||
public function __construct(
|
||||
public string $baseUri,
|
||||
public string $token,
|
||||
public int $timeout = 10,
|
||||
public int $timeout = 60,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromEnvironment(): self
|
||||
public static function fromEnvironment(?string $tokenOverride = null): self
|
||||
{
|
||||
$baseUri = getenv('MERCHANT_API_BASE_URI') ?: 'https://tpoint.agrimedia.cn';
|
||||
$token = getenv('MERCHANT_API_TOKEN') ?: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwd2QiOiI2ODI5N2FjYzUwZDczYWQzZmNmMTNlM2Y4MWYxMWQyZSIsImlzcyI6InRwb2ludC5hZ3JpbWVkaWEuY24iLCJhdWQiOiJ0cG9pbnQuYWdyaW1lZGlhLmNuIiwiaWF0IjoxNzgwODk3Mjk3LCJuYmYiOjE3ODA4OTcyOTcsImV4cCI6MTc4MzQwMjg5NywianRpIjp7ImlkIjo0LCJ0eXBlIjoiYWRtaW4ifX0.G2yB38bYkf0b37kpAbQViDuXY4QHeRjm99WNXjSh4L0';
|
||||
$timeout = (int) (getenv('MERCHANT_API_TIMEOUT') ?: 10);
|
||||
$token = $tokenOverride ?: (getenv('MERCHANT_API_TOKEN') ?: '');
|
||||
$timeout = (int) (getenv('MERCHANT_API_TIMEOUT') ?: 60);
|
||||
|
||||
if ($token === '') {
|
||||
throw new \RuntimeException('Missing MERCHANT_API_TOKEN environment variable.');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@ 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 = [],
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +43,20 @@ final readonly class HttpRequest
|
|||
}
|
||||
}
|
||||
|
||||
return new self($method, $path, $input);
|
||||
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
|
||||
|
|
@ -73,4 +88,31 @@ final readonly class HttpRequest
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ use App\Http\MerchantApiClient;
|
|||
|
||||
final readonly class OfflineOrderRepository
|
||||
{
|
||||
private const PAGE_LIMIT = 100;
|
||||
|
||||
public function __construct(private MerchantApiClient $apiClient)
|
||||
{
|
||||
}
|
||||
|
|
@ -27,7 +25,7 @@ final readonly class OfflineOrderRepository
|
|||
do {
|
||||
$data = $this->apiClient->getOfflineOrders($query + [
|
||||
'page' => $page,
|
||||
'limit' => self::PAGE_LIMIT,
|
||||
'limit' => $this->pageLimit(),
|
||||
]);
|
||||
$pageOrders = $data['data']['list'] ?? [];
|
||||
|
||||
|
|
@ -56,6 +54,13 @@ final readonly class OfflineOrderRepository
|
|||
return count($orders) < $totalCount;
|
||||
}
|
||||
|
||||
return is_array($pageOrders) && count($pageOrders) >= self::PAGE_LIMIT;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue