95 lines
3.4 KiB
PHP
95 lines
3.4 KiB
PHP
<?php
|
||
|
||
namespace App\Service\wx;
|
||
|
||
use App\Service\common\RedisService;
|
||
use App\Service\common\UtilsService;
|
||
use Illuminate\Support\Facades\Http;
|
||
|
||
/**
|
||
* 微信小程序开放接口客户端
|
||
*
|
||
* AppID / AppSecret 来自 nl_wx_app(加密存储),不再硬编码在源码里。
|
||
* access_token 有 7200 秒有效期且并发刷新会互相顶掉,所以缓存进 Redis 复用。
|
||
*/
|
||
class WxMiniService
|
||
{
|
||
private static mixed $_instance;
|
||
|
||
private string $baseUrl = 'https://api.weixin.qq.com/';
|
||
|
||
public static function getInstance(): null|static
|
||
{
|
||
$name = get_called_class();
|
||
if (!isset(self::$_instance[$name])) {
|
||
self::$_instance[$name] = new static();
|
||
}
|
||
return self::$_instance[$name];
|
||
}
|
||
|
||
/**
|
||
* code 换 openid 与 session_key
|
||
*/
|
||
public function jscode2session(string $code): array
|
||
{
|
||
$app = WxAppService::getInstance()->current();
|
||
if (($app['app_id'] ?? '') === '' || ($app['app_secret'] ?? '') === '') {
|
||
UtilsService::getInstance()->errorThrow('小程序 AppID/AppSecret 未配置');
|
||
}
|
||
$result = Http::asJson()->get($this->baseUrl . 'sns/jscode2session', [
|
||
'appid' => $app['app_id'],
|
||
'secret' => $app['app_secret'],
|
||
'js_code' => $code,
|
||
'grant_type' => 'authorization_code',
|
||
])->json();
|
||
if (!empty($result['errcode'])) {
|
||
UtilsService::getInstance()->errorThrow('微信登录失败:' . ($result['errmsg'] ?? '未知错误'));
|
||
}
|
||
if (empty($result['openid'])) {
|
||
UtilsService::getInstance()->errorThrow('微信登录失败:未拿到 openid');
|
||
}
|
||
$result['app_code'] = (string) ($app['code'] ?? '');
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 手机号快速验证:前端拿到的 code 换真实号码
|
||
*/
|
||
public function getPhoneNumber(string $phoneCode): string
|
||
{
|
||
$token = $this->accessToken();
|
||
$result = Http::asJson()->post(
|
||
$this->baseUrl . 'wxa/business/getuserphonenumber?access_token=' . $token,
|
||
['code' => $phoneCode]
|
||
)->json();
|
||
if (!empty($result['errcode'])) {
|
||
UtilsService::getInstance()->errorThrow('获取手机号失败:' . ($result['errmsg'] ?? '未知错误'));
|
||
}
|
||
return (string) ($result['phone_info']['phoneNumber'] ?? '');
|
||
}
|
||
|
||
/**
|
||
* 接口调用凭证,按 appid 缓存
|
||
*/
|
||
public function accessToken(): string
|
||
{
|
||
$app = WxAppService::getInstance()->current();
|
||
$redis = RedisService::getInstance()->init(config('nl.redis.wechat_token'));
|
||
$cached = $redis->get($app['app_id']);
|
||
if (!empty($cached)) {
|
||
return (string) $cached;
|
||
}
|
||
$result = Http::asJson()->get($this->baseUrl . 'cgi-bin/token', [
|
||
'grant_type' => 'client_credential',
|
||
'appid' => $app['app_id'],
|
||
'secret' => $app['app_secret'],
|
||
])->json();
|
||
if (empty($result['access_token'])) {
|
||
UtilsService::getInstance()->errorThrow('获取 access_token 失败:' . ($result['errmsg'] ?? '未知错误'));
|
||
}
|
||
// 提前 300 秒过期,避免临界点上拿到即将失效的 token
|
||
$redis->set($app['app_id'], $result['access_token'], max(60, (int) ($result['expires_in'] ?? 7200) - 300));
|
||
return (string) $result['access_token'];
|
||
}
|
||
}
|