1.配置新版支付模块-菜单和接口都已重构

2.优化充值提现页面
3.菜单翻译问题
4.备份数据库
This commit is contained in:
2026-04-30 11:37:46 +08:00
parent e8c2b9d345
commit c7fc754573
23 changed files with 4042 additions and 400 deletions

View File

@@ -0,0 +1,232 @@
<?php
declare(strict_types=1);
namespace app\common\middleware;
use support\Log;
use Throwable;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 接口调用日志中间件
*
* 目标:
* - 统一记录接口调用(请求 + 响应)
* - 成功记录返回参数
* - 失败记录失败原因
*/
class InterfaceRequestLog implements MiddlewareInterface
{
/**
* 仅记录接口路由(避免静态资源噪音)
*/
private const API_PATH_PREFIXES = ['api/', 'admin/'];
/**
* 需要脱敏的字段名(不区分大小写)
*/
private const SENSITIVE_KEYS = [
'password',
'oldpassword',
'newpassword',
'token',
'user-token',
'user_token',
'auth-token',
'auth_token',
'refresh_token',
'secret',
'signature',
'sign',
];
public function process(Request $request, callable $handler): Response
{
$path = trim($request->path(), '/');
if (!$this->shouldLogPath($path)) {
return $handler($request);
}
$start = microtime(true);
$requestPayload = $this->buildRequestPayload($request);
try {
$response = $handler($request);
} catch (Throwable $e) {
$costMs = $this->costMs($start);
Log::error('[InterfaceRequestLog] ' . $this->encodeJson([
'path' => '/' . $path,
'method' => strtoupper($request->method()),
'ip' => $request->getRealIp(),
'cost_ms' => $costMs,
'request' => $requestPayload,
'success' => false,
'fail_reason' => $e->getMessage(),
'exception_class' => get_class($e),
'exception_trace' => $this->truncateText($e->getTraceAsString(), 3000),
]));
throw $e;
}
$costMs = $this->costMs($start);
$statusCode = method_exists($response, 'getStatusCode') ? intval($response->getStatusCode()) : 200;
$responseBodyRaw = $this->extractResponseBody($response);
$responseDecoded = $this->decodeJsonArray($responseBodyRaw);
$success = false;
$failReason = '';
$responseData = null;
if (is_array($responseDecoded)) {
if (array_key_exists('code', $responseDecoded)) {
$success = intval($responseDecoded['code']) === 1;
} else {
$success = $statusCode >= 200 && $statusCode < 400;
}
$responseData = $responseDecoded['data'] ?? $responseDecoded;
if (!$success) {
$failReasonRaw = $responseDecoded['message'] ?? ($responseDecoded['msg'] ?? '');
$failReason = is_string($failReasonRaw) ? $failReasonRaw : strval($failReasonRaw);
}
} else {
$success = $statusCode >= 200 && $statusCode < 400;
if ($success) {
$responseData = $this->truncateText($responseBodyRaw, 2000);
} else {
$failReason = $this->truncateText($responseBodyRaw, 2000);
}
}
$logPayload = [
'path' => '/' . $path,
'method' => strtoupper($request->method()),
'ip' => $request->getRealIp(),
'status_code' => $statusCode,
'cost_ms' => $costMs,
'request' => $requestPayload,
'success' => $success,
];
if ($success) {
$logPayload['response_data'] = $this->maskMixed($responseData);
Log::info('[InterfaceRequestLog] ' . $this->encodeJson($logPayload));
} else {
$logPayload['fail_reason'] = $failReason !== '' ? $failReason : 'Unknown error';
if (is_array($responseDecoded)) {
$logPayload['api_response'] = $this->maskMixed($responseDecoded);
}
Log::error('[InterfaceRequestLog] ' . $this->encodeJson($logPayload));
}
return $response;
}
private function shouldLogPath(string $path): bool
{
foreach (self::API_PATH_PREFIXES as $prefix) {
if (str_starts_with($path, $prefix)) {
return true;
}
}
return false;
}
private function buildRequestPayload(Request $request): array
{
$query = $request->get();
$post = $request->post();
if (!is_array($query)) {
$query = [];
}
if (!is_array($post)) {
$post = [];
}
$rawBody = method_exists($request, 'rawBody') ? strval($request->rawBody()) : '';
$jsonBody = [];
if ($rawBody !== '') {
$jsonDecoded = json_decode($rawBody, true);
if (is_array($jsonDecoded)) {
$jsonBody = $jsonDecoded;
}
}
return [
'query' => $this->maskMixed($query),
'post' => $this->maskMixed($post),
'json' => $this->maskMixed($jsonBody),
];
}
private function extractResponseBody(Response $response): string
{
if (method_exists($response, 'rawBody')) {
return strval($response->rawBody());
}
return '';
}
private function decodeJsonArray(string $content): ?array
{
if ($content === '') {
return null;
}
$decoded = json_decode($content, true);
return is_array($decoded) ? $decoded : null;
}
private function maskMixed($value, ?string $parentKey = null)
{
if (is_array($value)) {
$result = [];
foreach ($value as $k => $v) {
$keyName = is_string($k) ? $k : $parentKey;
$result[$k] = $this->maskMixed($v, $keyName);
}
return $result;
}
if ($parentKey !== null && $this->isSensitiveKey($parentKey)) {
return '***';
}
return $value;
}
private function isSensitiveKey(string $key): bool
{
$normalized = strtolower(trim($key));
foreach (self::SENSITIVE_KEYS as $sensitive) {
if ($normalized === $sensitive) {
return true;
}
if (str_contains($normalized, $sensitive)) {
return true;
}
}
return false;
}
private function truncateText(string $text, int $maxLen): string
{
if ($maxLen <= 0) {
return '';
}
if (mb_strlen($text) <= $maxLen) {
return $text;
}
return mb_substr($text, 0, $maxLen) . '...';
}
private function encodeJson(array $payload): string
{
return json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
private function costMs(float $start): int
{
return intval((microtime(true) - $start) * 1000);
}
}