Files
lgp-admin-plus-api/app/Service/wx/WxAppService.php

131 lines
5.4 KiB
PHP
Raw Normal View History

<?php
namespace App\Service\wx;
use App\Models\WxAppModel;
use App\Service\common\FieldEncryptService;
use App\Service\common\UtilsService;
/**
* 小程序应用凭证读取
*
* 请求头 X-App-Code或参数 app_code决定用哪个品牌的 AppID。
* 没带就用 .env WX_DEFAULT_APP_CODE 兜底;都没有就取唯一启用的那条。
* 库表nl_wx_app始终是主源.env 只在「表里完全没有匹配行」时回落兜底,
* 这样单品牌部署可以不进后台配置就跑起来,双品牌共用进程仍以表 + 请求头分流为主。
*/
class WxAppService
{
private static mixed $_instance;
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 取当前请求对应的应用配置(含解密后的密钥)
*
* 解析顺序:
* 1. code请求头 .env 默认值)查 nl_wx_app
* 2. 查不到且 .env 配了 AppID返回仅含 app_id/name/code 的兜底配置,
* 密钥仍尝试从表里「按 app_id 匹配」的那行解密;表里也没有就抛错
* 指引运维去后台填写——密钥不能从 .env 读,安全红线
* 3. .env 也没配:直接抛错
*/
public function current(): array
{
$code = $this->currentCode();
$query = WxAppModel::where('deleted_at', 0)->where('status', 0);
$app = $code !== '' ? $query->where('code', $code)->first() : $query->first();
// 库表命中:解密密钥列后返回
if (!empty($app)) {
return $this->loadWithSecrets($app);
}
// 库表未命中,回落 .env仅 AppID/名称,不含密钥)
$envAppId = trim((string) config('nl.wx.env_app_id', ''));
if ($envAppId === '') {
// .env 没配 AppID 就没法兜底了,直接抛错
UtilsService::getInstance()->errorThrow('未配置小程序应用nl_wx_app请先在后台添加或在 .env 配 WX_APP_ID');
}
$envCode = trim((string) config('nl.wx.default_app_code', ''));
$envName = trim((string) config('nl.wx.env_app_name', ''));
// 兜底场景AppID 来自 .env但密钥仍只能从库里按 AppID 找一行来解密
// 这样老项目硬编码密钥的脏习惯不会回到 .env密钥始终加密入库
$secretRow = WxAppModel::where('app_id', $envAppId)
->where('deleted_at', 0)
->first(['app_secret', 'mch_key', 'mch_private_key']);
if (empty($secretRow) || trim((string) ($secretRow['app_secret'] ?? '')) === '') {
// 表里既没匹配行、或行里没填密钥,只能报错让运维去后台补
UtilsService::getInstance()->errorThrow(
'小程序 AppSecret 未配置:请打开侧栏「小程序 → 应用配置」,新增/编辑 AppID=' .
$envAppId .
' 的记录并填写 AppSecret密钥加密入库不要写进 .env'
);
}
$encrypt = FieldEncryptService::getInstance();
return [
'id' => 0,
'code' => $envCode,
'name' => $envName,
'app_id' => $envAppId,
'app_secret' => $encrypt->decryptFromStorage((string) $secretRow['app_secret'], true),
'mch_id' => '',
'mch_key' => $encrypt->decryptFromStorage((string) ($secretRow['mch_key'] ?? ''), true),
'mch_serial_no' => '',
'mch_private_key' => $encrypt->decryptFromStorage((string) ($secretRow['mch_private_key'] ?? ''), true),
'platform_public_key' => '',
'notify_url' => '',
'template_code' => '',
];
}
/**
* 当前请求声明的品牌标识
*
* 优先级:请求头 X-App-Code > 请求参数 app_code > .env WX_DEFAULT_APP_CODE
* 全部为空时返回空串,调用方会改走「取唯一启用行」逻辑
*/
public function currentCode(): string
{
$code = (string) (request()->header('X-App-Code') ?: request()->input('app_code', ''));
$code = trim($code);
if ($code !== '') {
return $code;
}
// 请求没带品牌标识时回落 .env 默认值(单品牌部署兜底)
return trim((string) config('nl.wx.default_app_code', ''));
}
/**
* 把模型行的密钥列解密后转数组返回
*/
private function loadWithSecrets($app): array
{
$app = $app->toArray();
$raw = WxAppModel::where('id', $app['id'])->first(['app_secret', 'mch_key', 'mch_private_key']);
$encrypt = FieldEncryptService::getInstance();
$secret = $encrypt->decryptFromStorage($raw['app_secret'] ?? '', true);
if (trim((string) $secret) === '') {
UtilsService::getInstance()->errorThrow(
'小程序 AppSecret 未配置:请打开侧栏「小程序 → 应用配置」,编辑 code=' .
($app['code'] ?? '') .
'AppID=' . ($app['app_id'] ?? '') . ')并填写 AppSecret'
);
}
$app['app_secret'] = $secret;
$app['mch_key'] = $encrypt->decryptFromStorage($raw['mch_key'] ?? '', true);
$app['mch_private_key'] = $encrypt->decryptFromStorage($raw['mch_private_key'] ?? '', true);
return $app;
}
}