877fd278c2
统一门户 JSON 契约:新增 GET /templates/{id},模板/云文件/保存/打开走虚拟 key;
layer 保存弹层替代 prompt,本地 .soon 已登录后台 POST 登记;修复保存静默失败与 design2 全宽布局。
Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
4.7 KiB
PHP
109 lines
4.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require __DIR__ . '/../src/bootstrap.php';
|
|
|
|
use Soon\Api\Controllers\AuthController;
|
|
use Soon\Api\Controllers\FileController;
|
|
use Soon\Api\Controllers\PayController;
|
|
use Soon\Api\Controllers\PlanController;
|
|
use Soon\Api\Controllers\SettingsController;
|
|
use Soon\Api\Controllers\SoonModelController;
|
|
use Soon\Api\Controllers\TemplateController;
|
|
use Soon\Api\Core\Config;
|
|
use Soon\Api\Core\Json;
|
|
use Soon\Api\Core\Router;
|
|
use Soon\Api\Middleware\Auth;
|
|
use Soon\Api\Middleware\RateLimit;
|
|
|
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
|
$allowed = (array)Config::get('app.cors_origins', []);
|
|
if ($origin !== '' && in_array($origin, $allowed, true)) {
|
|
header('Access-Control-Allow-Origin: ' . $origin);
|
|
header('Vary: Origin');
|
|
header('Access-Control-Allow-Credentials: true');
|
|
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
|
header('Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, Range');
|
|
header('Access-Control-Expose-Headers: ETag, Content-Range, Content-Length');
|
|
}
|
|
header('X-Content-Type-Options: nosniff');
|
|
header('X-Frame-Options: DENY');
|
|
header('Referrer-Policy: no-referrer');
|
|
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
|
|
|
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
|
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
|
|
|
Router::post('/api/v1/auth/register', [AuthController::class, 'register']);
|
|
Router::post('/api/v1/auth/login', [AuthController::class, 'login']);
|
|
Router::post('/api/v1/auth/refresh', [AuthController::class, 'refresh']);
|
|
Router::get('/api/v1/auth/me', [AuthController::class, 'me']);
|
|
|
|
Router::get('/api/v1/files', [FileController::class, 'index']);
|
|
Router::post('/api/v1/files', [FileController::class, 'create']);
|
|
Router::get('/api/v1/files/{id}', [FileController::class, 'show']);
|
|
Router::put('/api/v1/files/{id}', [FileController::class, 'update']);
|
|
Router::delete('/api/v1/files/{id}', [FileController::class, 'delete']);
|
|
Router::get('/api/v1/files/{id}/download', [FileController::class, 'download']);
|
|
|
|
Router::get('/api/v1/plans', [PlanController::class, 'index']);
|
|
Router::get('/api/v1/plans/me', [PlanController::class, 'myPlan']);
|
|
|
|
Router::get('/api/v1/pay/orders', [PayController::class, 'listMyOrders']);
|
|
Router::post('/api/v1/pay/orders', [PayController::class, 'createOrder']);
|
|
Router::get('/api/v1/pay/orders/{order_no}', [PayController::class, 'showOrder']);
|
|
Router::post('/api/v1/pay/orders/{order_no}/checkout', [PayController::class, 'checkoutOrder']);
|
|
Router::post('/api/v1/pay/orders/{order_no}/cancel', [PayController::class, 'cancelOrder']);
|
|
Router::post('/api/v1/pay/orders/{order_no}/refund-request', [PayController::class, 'requestRefund']);
|
|
Router::post('/api/v1/pay/alipay/notify', [PayController::class, 'alipayNotify']);
|
|
Router::post('/api/v1/pay/wechat/notify', [PayController::class, 'wechatNotify']);
|
|
|
|
Router::get('/api/v1/templates', [TemplateController::class, 'index']);
|
|
Router::get('/api/v1/templates/{id}/thumb', [TemplateController::class, 'thumb']);
|
|
Router::get('/api/v1/templates/{id}/file', [TemplateController::class, 'file']);
|
|
Router::get('/api/v1/templates/{id}', [TemplateController::class, 'show']);
|
|
Router::get('/api/v1/soon-models/files/{name}', [SoonModelController::class, 'download']);
|
|
Router::get('/api/v1/soon-models', [TemplateController::class, 'index']);
|
|
Router::get('/api/v1/settings', [SettingsController::class, 'publicSettings']);
|
|
|
|
if (str_starts_with($path, '/api/v1/')) {
|
|
$routeKey = $method . ' ' . $path;
|
|
if (str_starts_with($path, '/api/v1/auth/login') || str_starts_with($path, '/api/v1/auth/register') || str_starts_with($path, '/api/v1/auth/refresh')) {
|
|
RateLimit::check($routeKey, $method, $path, null);
|
|
} elseif (str_starts_with($path, '/api/v1/pay/') && str_contains($path, '/notify')) {
|
|
// 通知不进限速
|
|
} else {
|
|
$uid = null;
|
|
$tok = Auth::bearerFromGlobals();
|
|
if ($tok !== '') {
|
|
$payload = \Soon\Api\Core\Jwt::decode($tok);
|
|
if ($payload !== null) $uid = (int)($payload['sub'] ?? 0) ?: null;
|
|
}
|
|
RateLimit::check($routeKey, $method, $path, $uid);
|
|
}
|
|
}
|
|
|
|
$match = Router::dispatch($method, $path);
|
|
if ($match === null) {
|
|
Json::fail('not_found', '接口不存在', 404);
|
|
}
|
|
[$handler, $params] = $match;
|
|
foreach ($params as $k => $v) {
|
|
if ($k === 'id' && is_string($v) && ctype_digit($v)) {
|
|
$params[$k] = (int)$v;
|
|
}
|
|
}
|
|
if (is_array($handler)) {
|
|
[$class, $action] = $handler;
|
|
$instance = new $class();
|
|
$args = array_values($params);
|
|
$instance->$action(...$args);
|
|
} else {
|
|
($handler)($params);
|
|
}
|