更新数据库结构

This commit is contained in:
技术老胡
2026-03-10 13:47:28 +08:00
parent 54ad21ac8f
commit 9de902231f
54 changed files with 5070 additions and 501 deletions

View File

@@ -0,0 +1,34 @@
<?php
namespace app\http\admin\controller;
use app\common\base\BaseController;
use app\services\AdminService;
use support\Request;
/**
* 管理员控制器
*/
class AdminController extends BaseController
{
public function __construct(
protected AdminService $adminService
) {
}
/**
* GET /admin/getUserInfo
*
* 获取当前登录管理员信息
*/
public function getUserInfo(Request $request)
{
$adminId = $this->currentUserId($request);
if ($adminId <= 0) {
return $this->fail('未获取到用户信息,请先登录', 401);
}
$data = $this->adminService->getInfoById($adminId);
return $this->success($data);
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace app\http\admin\controller;
use app\common\base\BaseController;
use app\repositories\{PaymentChannelRepository, PaymentMethodRepository};
use app\services\PluginService;
use support\Request;
/**
* 通道管理控制器
*/
class ChannelController extends BaseController
{
public function __construct(
protected PaymentChannelRepository $channelRepository,
protected PaymentMethodRepository $methodRepository,
protected PluginService $pluginService,
) {
}
/**
* 通道列表
* GET /adminapi/channel/list
*/
public function list(Request $request)
{
$merchantId = (int)$request->get('merchant_id', 0);
$appId = (int)$request->get('app_id', 0);
$methodCode = trim((string)$request->get('method_code', ''));
$where = [];
if ($merchantId > 0) {
$where['merchant_id'] = $merchantId;
}
if ($appId > 0) {
$where['merchant_app_id'] = $appId;
}
if ($methodCode !== '') {
$method = $this->methodRepository->findByCode($methodCode);
if ($method) {
$where['method_id'] = $method->id;
}
}
$page = (int)($request->get('page', 1));
$pageSize = (int)($request->get('page_size', 10));
$result = $this->channelRepository->paginate($where, $page, $pageSize);
return $this->success($result);
}
/**
* 通道详情
* GET /adminapi/channel/detail
*/
public function detail(Request $request)
{
$id = (int)$request->get('id', 0);
if (!$id) {
return $this->fail('通道ID不能为空', 400);
}
$channel = $this->channelRepository->find($id);
if (!$channel) {
return $this->fail('通道不存在', 404);
}
$methodCode = '';
if ($channel->method_id) {
$method = $this->methodRepository->find($channel->method_id);
$methodCode = $method ? $method->method_code : '';
}
try {
$configSchema = $this->pluginService->getConfigSchema($channel->plugin_code, $methodCode);
// 合并当前配置值
$currentConfig = $channel->getConfigArray();
if (isset($configSchema['fields']) && is_array($configSchema['fields'])) {
foreach ($configSchema['fields'] as &$field) {
if (isset($field['field']) && isset($currentConfig[$field['field']])) {
$field['value'] = $currentConfig[$field['field']];
}
}
}
return $this->success([
'channel' => $channel,
'config_schema' => $configSchema,
]);
} catch (\Throwable $e) {
return $this->success([
'channel' => $channel,
'config_schema' => ['fields' => []],
]);
}
}
/**
* 保存通道
* POST /adminapi/channel/save
*/
public function save(Request $request)
{
$data = $request->post();
$id = (int)($data['id'] ?? 0);
$pluginCode = $data['plugin_code'] ?? '';
$methodCode = $data['method_code'] ?? '';
$enabledProducts = $data['enabled_products'] ?? [];
if (empty($pluginCode) || empty($methodCode)) {
return $this->fail('插件编码和支付方式不能为空', 400);
}
// 提取配置参数(从表单字段中提取)
try {
$configJson = $this->pluginService->buildConfigFromForm($pluginCode, $methodCode, $data);
} catch (\Throwable $e) {
return $this->fail('插件不存在或配置错误:' . $e->getMessage(), 400);
}
$method = $this->methodRepository->findByCode($methodCode);
if (!$method) {
return $this->fail('支付方式不存在', 400);
}
$configWithProducts = array_merge($configJson, ['enabled_products' => is_array($enabledProducts) ? $enabledProducts : []]);
$channelData = [
'merchant_id' => (int)($data['merchant_id'] ?? 0),
'merchant_app_id' => (int)($data['app_id'] ?? 0),
'chan_code' => $data['channel_code'] ?? $data['chan_code'] ?? '',
'chan_name' => $data['channel_name'] ?? $data['chan_name'] ?? '',
'plugin_code' => $pluginCode,
'method_id' => $method->id,
'config_json' => $configWithProducts,
'split_ratio' => isset($data['split_ratio']) ? (float)$data['split_ratio'] : 100.00,
'chan_cost' => isset($data['channel_cost']) ? (float)$data['channel_cost'] : 0.00,
'chan_mode' => $data['channel_mode'] ?? 'wallet',
'daily_limit' => isset($data['daily_limit']) ? (float)$data['daily_limit'] : 0.00,
'daily_cnt' => isset($data['daily_count']) ? (int)$data['daily_count'] : 0,
'min_amount' => isset($data['min_amount']) && $data['min_amount'] !== '' ? (float)$data['min_amount'] : null,
'max_amount' => isset($data['max_amount']) && $data['max_amount'] !== '' ? (float)$data['max_amount'] : null,
'status' => (int)($data['status'] ?? 1),
'sort' => (int)($data['sort'] ?? 0),
];
if ($id > 0) {
// 更新
$this->channelRepository->updateById($id, $channelData);
} else {
if (empty($channelData['chan_code'])) {
$channelData['chan_code'] = 'CH' . date('YmdHis') . mt_rand(1000, 9999);
}
$this->channelRepository->create($channelData);
}
return $this->success(null, '保存成功');
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace app\http\admin\controller;
use app\common\base\BaseController;
use app\services\PluginService;
use support\Request;
/**
* 插件管理控制器
*/
class PluginController extends BaseController
{
public function __construct(
protected PluginService $pluginService
) {
}
/**
* 获取所有可用插件列表
* GET /adminapi/channel/plugins
*/
public function plugins()
{
$plugins = $this->pluginService->listPlugins();
return $this->success($plugins);
}
/**
* 获取插件配置Schema
* GET /adminapi/channel/plugin/config-schema
*/
public function configSchema(Request $request)
{
$pluginCode = $request->get('plugin_code', '');
$methodCode = $request->get('method_code', '');
if (empty($pluginCode) || empty($methodCode)) {
return $this->fail('插件编码和支付方式不能为空', 400);
}
try {
$schema = $this->pluginService->getConfigSchema($pluginCode, $methodCode);
return $this->success($schema);
} catch (\Throwable $e) {
return $this->fail('获取配置Schema失败' . $e->getMessage(), 400);
}
}
/**
* 获取插件支持的支付产品列表
* GET /adminapi/channel/plugin/products
*/
public function products(Request $request)
{
$pluginCode = $request->get('plugin_code', '');
$methodCode = $request->get('method_code', '');
if (empty($pluginCode) || empty($methodCode)) {
return $this->fail('插件编码和支付方式不能为空', 400);
}
try {
$products = $this->pluginService->getSupportedProducts($pluginCode, $methodCode);
return $this->success($products);
} catch (\Throwable $e) {
return $this->fail('获取产品列表失败:' . $e->getMessage(), 400);
}
}
}

View File

@@ -1,40 +0,0 @@
<?php
namespace app\http\admin\controller;
use app\common\base\BaseController;
use app\services\UserService;
use support\Request;
/**
* 用户接口示例控制器
*
* 主要用于演示 BaseController / Service / Repository / Model 的调用链路。
*/
class UserController extends BaseController
{
public function __construct(
protected UserService $userService
) {
}
/**
* GET /user/getUserInfo
*
* 从 JWT token 中获取当前登录用户信息
* 前端通过 Authorization: Bearer {token} 请求头传递 token
*/
public function getUserInfo(Request $request)
{
// 从JWT中间件注入的用户信息中获取用户ID
$userId = $this->currentUserId($request);
if ($userId <= 0) {
return $this->fail('未获取到用户信息,请先登录', 401);
}
$data = $this->userService->getUserInfoById($userId);
return $this->success($data);
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace app\http\api\controller;
use app\common\base\BaseController;
use app\services\api\EpayService;
use app\validation\EpayValidator;
use support\Request;
use support\Response;
/**
* 易支付控制器
*/
class EpayController extends BaseController
{
public function __construct(
protected EpayService $epayService
) {}
/**
* 页面跳转支付
*/
public function submit(Request $request)
{
$data = array_merge($request->get(), $request->post());
try {
// 参数校验(使用自定义 Validator + 场景)
$params = EpayValidator::make($data)
->withScene('submit')
->validate();
// 业务处理:创建订单并获取支付参数
$result = $this->epayService->submit($params, $request);
$payParams = $result['pay_params'] ?? [];
// 根据支付参数类型返回响应
if (($payParams['type'] ?? '') === 'redirect' && !empty($payParams['url'])) {
return redirect($payParams['url']);
}
if (($payParams['type'] ?? '') === 'form') {
return $this->renderForm($payParams);
}
// 如果没有匹配的类型,返回错误
return $this->fail('支付参数生成失败');
} catch (\Throwable $e) {
return $this->fail($e->getMessage());
}
}
/**
* API接口支付
*/
public function mapi(Request $request)
{
$data = $request->post();
try {
$params = EpayValidator::make($data)
->withScene('mapi')
->validate();
$result = $this->epayService->mapi($params, $request);
return json($result);
} catch (\Throwable $e) {
return json([
'code' => 0,
'msg' => $e->getMessage(),
]);
}
}
/**
* API接口
*/
public function api(Request $request)
{
$data = array_merge($request->get(), $request->post());
try {
$act = strtolower($data['act'] ?? '');
if ($act === 'order') {
$params = EpayValidator::make($data)
->withScene('api_order')
->validate();
$result = $this->epayService->api($params);
} elseif ($act === 'refund') {
$params = EpayValidator::make($data)
->withScene('api_refund')
->validate();
$result = $this->epayService->api($params);
} else {
$result = [
'code' => 0,
'msg' => '不支持的操作类型',
];
}
return json($result);
} catch (\Throwable $e) {
return json([
'code' => 0,
'msg' => $e->getMessage(),
]);
}
}
/**
* 渲染表单提交 HTML用于页面跳转支付
*/
private function renderForm(array $formParams): Response
{
$html = '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>跳转支付</title></head><body>';
$html .= '<form id="payForm" method="' . htmlspecialchars($formParams['method'] ?? 'POST') . '" action="' . htmlspecialchars($formParams['action'] ?? '') . '">';
if (isset($formParams['fields']) && is_array($formParams['fields'])) {
foreach ($formParams['fields'] as $name => $value) {
$html .= '<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars((string)$value) . '">';
}
}
$html .= '</form>';
$html .= '<script>document.getElementById("payForm").submit();</script>';
$html .= '</body></html>';
return response($html)->withHeaders(['Content-Type' => 'text/html; charset=UTF-8']);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace app\http\api\controller;
use app\common\base\BaseController;
use app\services\PayOrderService;
use support\Request;
/**
* 支付控制器OpenAPI
*/
class PayController extends BaseController
{
public function __construct(
protected PayOrderService $payOrderService
) {}
/**
* 创建订单
*/
public function create(Request $request) {}
/**
* 查询订单
*/
public function query(Request $request) {}
/**
* 关闭订单
*/
public function close(Request $request) {}
/**
* 订单退款
*/
public function refund(Request $request) {}
/**
* 异步通知
*/
public function notify(Request $request) {}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace app\http\api\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Request;
use Webman\Http\Response;
use app\exceptions\UnauthorizedException;
use app\repositories\MerchantAppRepository;
/**
* OpenAPI 签名认证中间件
*
* 验证 AppId + 签名
*/
class EpayAuthMiddleware implements MiddlewareInterface
{
protected MerchantAppRepository $merchantAppRepository;
public function __construct()
{
// 延迟加载,避免循环依赖
$this->merchantAppRepository = new MerchantAppRepository();
}
public function process(Request $request, callable $handler): Response
{
$appId = $request->header('X-App-Id', '') ?: ($request->post('app_id', '') ?: $request->get('app_id', ''));
$timestamp = $request->header('X-Timestamp', '') ?: ($request->post('timestamp', '') ?: $request->get('timestamp', ''));
$nonce = $request->header('X-Nonce', '') ?: ($request->post('nonce', '') ?: $request->get('nonce', ''));
$signature = $request->header('X-Signature', '') ?: ($request->post('signature', '') ?: $request->get('signature', ''));
if (empty($appId) || empty($timestamp) || empty($nonce) || empty($signature)) {
throw new UnauthorizedException('缺少认证参数');
}
// 验证时间戳5分钟内有效
if (abs(time() - (int)$timestamp) > 300) {
throw new UnauthorizedException('请求已过期');
}
// 查询应用
$app = $this->merchantAppRepository->findByAppId($appId);
if (!$app) {
throw new UnauthorizedException('应用不存在或已禁用');
}
// 验证签名
$method = $request->method();
$path = $request->path();
$body = $request->rawBody();
$bodySha256 = hash('sha256', $body);
$signString = "app_id={$appId}&timestamp={$timestamp}&nonce={$nonce}&method={$method}&path={$path}&body_sha256={$bodySha256}";
$expectedSignature = hash_hmac('sha256', $signString, $app->app_secret);
if (!hash_equals($expectedSignature, $signature)) {
throw new UnauthorizedException('签名验证失败');
}
// 将应用信息注入到请求对象
$request->app = $app;
$request->merchantId = $app->merchant_id;
$request->appId = $app->id;
return $handler($request);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace app\http\api\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Request;
use Webman\Http\Response;
use app\exceptions\UnauthorizedException;
use app\repositories\MerchantAppRepository;
/**
* OpenAPI 签名认证中间件
*
* 验证 AppId + 签名
*/
class OpenApiAuthMiddleware implements MiddlewareInterface
{
protected MerchantAppRepository $merchantAppRepository;
public function __construct()
{
// 延迟加载,避免循环依赖
$this->merchantAppRepository = new MerchantAppRepository();
}
public function process(Request $request, callable $handler): Response
{
$appId = $request->header('X-App-Id', '') ?: ($request->post('app_id', '') ?: $request->get('app_id', ''));
$timestamp = $request->header('X-Timestamp', '') ?: ($request->post('timestamp', '') ?: $request->get('timestamp', ''));
$nonce = $request->header('X-Nonce', '') ?: ($request->post('nonce', '') ?: $request->get('nonce', ''));
$signature = $request->header('X-Signature', '') ?: ($request->post('signature', '') ?: $request->get('signature', ''));
if (empty($appId) || empty($timestamp) || empty($nonce) || empty($signature)) {
throw new UnauthorizedException('缺少认证参数');
}
// 验证时间戳5分钟内有效
if (abs(time() - (int)$timestamp) > 300) {
throw new UnauthorizedException('请求已过期');
}
// 查询应用
$app = $this->merchantAppRepository->findByAppId($appId);
if (!$app) {
throw new UnauthorizedException('应用不存在或已禁用');
}
// 验证签名
$method = $request->method();
$path = $request->path();
$body = $request->rawBody();
$bodySha256 = hash('sha256', $body);
$signString = "app_id={$appId}&timestamp={$timestamp}&nonce={$nonce}&method={$method}&path={$path}&body_sha256={$bodySha256}";
$expectedSignature = hash_hmac('sha256', $signString, $app->app_secret);
if (!hash_equals($expectedSignature, $signature)) {
throw new UnauthorizedException('签名验证失败');
}
// 将应用信息注入到请求对象
$request->app = $app;
$request->merchantId = $app->merchant_id;
$request->appId = $app->id;
return $handler($request);
}
}