You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
6.3 KiB

3 days ago
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\WechatUser;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
3 days ago
use Illuminate\Support\Facades\Cache;
3 days ago
use Illuminate\Support\Facades\Http;
3 days ago
use Illuminate\Support\Str;
3 days ago
use Illuminate\Validation\ValidationException;
class H5WechatController extends Controller
{
/**
* 公众号网页授权 code 换用户信息,并签发 Sanctum 令牌WechatUser
*/
public function oauth(Request $request): JsonResponse
{
$data = $request->validate([
'code' => ['required', 'string'],
]);
$appId = (string) config('wechat.app_id');
$secret = (string) config('wechat.app_secret');
if ($appId === '' || $secret === '') {
return response()->json(['message' => '服务端未配置微信公众号 AppId/AppSecret'], 503);
}
$tokenRes = Http::timeout(15)
->get('https://api.weixin.qq.com/sns/oauth2/access_token', [
'appid' => $appId,
'secret' => $secret,
'code' => $data['code'],
'grant_type' => 'authorization_code',
])
->json();
if (! empty($tokenRes['errcode'])) {
throw ValidationException::withMessages([
'code' => [$tokenRes['errmsg'] ?? '微信 code 无效或已使用'],
]);
}
$accessToken = (string) ($tokenRes['access_token'] ?? '');
$openid = (string) ($tokenRes['openid'] ?? '');
if ($accessToken === '' || $openid === '') {
return response()->json(['message' => '微信未返回 access_token'], 422);
}
$unionid = $tokenRes['unionid'] ?? null;
$info = Http::timeout(15)
->get('https://api.weixin.qq.com/sns/userinfo', [
'access_token' => $accessToken,
'openid' => $openid,
'lang' => 'zh_CN',
])
->json();
if (! empty($info['errcode'])) {
$info = [];
}
if ($unionid === null && ! empty($info['unionid'])) {
$unionid = $info['unionid'];
}
$wu = WechatUser::query()->updateOrCreate(
['openid' => $openid],
[
'unionid' => $unionid,
'nickname' => $info['nickname'] ?? null,
'avatar_url' => $info['headimgurl'] ?? null,
]
);
$plain = $wu->createToken('wechat-h5')->plainTextToken;
return response()->json([
'token' => $plain,
'user' => [
'id' => $wu->id,
'nickname' => $wu->nickname,
'avatar_url' => $wu->avatar_url,
],
]);
}
3 days ago
/**
* 公众号 JS-SDK 签名分享、getLocation 等),与前端 wx.config 字段一致。
*
* @see https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
*/
public function jssdkSignature(Request $request): JsonResponse
{
$data = $request->validate([
'url' => ['required', 'string', 'max:2048'],
]);
$appId = (string) config('wechat.app_id');
$secret = (string) config('wechat.app_secret');
if ($appId === '' || $secret === '') {
return response()->json(['message' => '服务端未配置微信公众号 AppId/AppSecret'], 503);
}
$url = $data['url'];
$ticket = $this->getWechatJsapiTicket($appId, $secret);
if ($ticket === '') {
return response()->json(['message' => '获取微信 jsapi_ticket 失败,请检查 AppId/AppSecret 与网络'], 503);
}
$timestamp = time();
$nonceStr = Str::random(16);
$plain = "jsapi_ticket={$ticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
$signature = sha1($plain);
return response()->json([
'appId' => $appId,
'timestamp' => $timestamp,
'nonceStr' => $nonceStr,
'signature' => $signature,
]);
}
private function getWechatJsapiTicket(string $appId, string $secret): string
{
try {
return Cache::remember('wechat_jsapi_ticket_'.$appId, 7000, function () use ($appId, $secret) {
$token = $this->getWechatAccessToken($appId, $secret);
if ($token === '') {
throw new \RuntimeException('empty access_token');
}
$res = Http::timeout(15)
->get('https://api.weixin.qq.com/cgi-bin/ticket/getticket', [
'access_token' => $token,
'type' => 'jsapi',
])
->json();
if (! empty($res['errcode']) && (int) $res['errcode'] !== 0) {
throw new \RuntimeException($res['errmsg'] ?? 'getticket failed');
}
$ticket = (string) ($res['ticket'] ?? '');
if ($ticket === '') {
throw new \RuntimeException('empty ticket');
}
return $ticket;
});
} catch (\Throwable) {
Cache::forget('wechat_jsapi_ticket_'.$appId);
return '';
}
}
private function getWechatAccessToken(string $appId, string $secret): string
{
try {
return Cache::remember('wechat_access_token_'.$appId, 7000, function () use ($appId, $secret) {
$res = Http::timeout(15)
->get('https://api.weixin.qq.com/cgi-bin/token', [
'grant_type' => 'client_credential',
'appid' => $appId,
'secret' => $secret,
])
->json();
if (! empty($res['errcode'])) {
throw new \RuntimeException($res['errmsg'] ?? 'access_token failed');
}
$token = (string) ($res['access_token'] ?? '');
if ($token === '') {
throw new \RuntimeException('empty access_token');
}
return $token;
});
} catch (\Throwable) {
Cache::forget('wechat_access_token_'.$appId);
return '';
}
}
3 days ago
}