Files
MrleeAdmin/backend/app/service/api/auth/WechatService.php
T

433 lines
13 KiB
PHP
Raw Normal View History

2026-08-31 11:33:59 +08:00
<?php
declare(strict_types=1);
namespace app\service\api\auth;
use app\model\member\Member;
use app\model\member\MemberThirdParty;
use app\enum\common\EnabledStatus;
use app\enum\member\ThirdPartyType;
use core\base\BaseService;
use core\jwt\JwtToken;
use support\Redis;
/**
* 微信认证服务
*/
class WechatService extends BaseService
{
/**
* 微信授权登录
*/
public function wechatLogin(array $data): array
{
if (empty($data['code'])) {
throw new \Exception('授权码不能为空', 400);
}
// 获取微信用户信息
$wechatUserInfo = $this->getWechatUserInfo($data['code'], $data['encrypted_data'] ?? '', $data['iv'] ?? '');
// 查找或创建用户
$member = $this->findOrCreateMemberByWechat($wechatUserInfo);
// 验证用户状态
if ($member->enabled !== EnabledStatus::ENABLED->value) {
throw new \Exception('用户已被禁用', 401);
}
// 更新登录信息
$this->updateLoginInfo($member);
// 生成token
$token = $this->generateToken($member->id);
return [
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => $token,
'user_info' => [
'id' => $member->id,
'username' => $member->username,
'nickname' => $member->nickname,
'avatar' => $member->avatar,
'phone' => $member->phone,
'email' => $member->email,
'points' => $member->points,
'balance' => $member->balance,
],
],
];
}
/**
* 微信小程序登录
*/
public function weappLogin(array $data): array
{
if (empty($data['code'])) {
throw new \Exception('登录code不能为空', 400);
}
// 获取小程序用户信息
$weappUserInfo = $this->getWeappUserInfo($data['code'], $data['encrypted_data'] ?? '', $data['iv'] ?? '');
// 查找或创建用户
$member = $this->findOrCreateMemberByWeapp($weappUserInfo);
// 验证用户状态
if ($member->enabled !== EnabledStatus::ENABLED->value) {
throw new \Exception('用户已被禁用', 401);
}
// 更新登录信息
$this->updateLoginInfo($member);
// 生成token
$token = $this->generateToken($member->id);
return [
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => $token,
'user_info' => [
'id' => $member->id,
'username' => $member->username,
'nickname' => $member->nickname,
'avatar' => $member->avatar,
'phone' => $member->phone,
'email' => $member->email,
'points' => $member->points,
'balance' => $member->balance,
],
],
];
}
/**
* 生成微信扫码登录二维码
*/
public function generateWechatQrCode(): array
{
$sceneId = uniqid('wechat_scan_');
$qrCodeUrl = $this->generateQrCodeUrl($sceneId);
// 存储扫码状态
Redis::setex('wechat_scan:' . $sceneId, 300, json_encode([
'status' => 'waiting',
'created_at' => time(),
]));
return [
'scene_id' => $sceneId,
'qr_code_url' => $qrCodeUrl,
'expire_time' => 300,
];
}
/**
* 检查微信扫码状态
*/
public function checkWechatScanStatus(string $sceneId): array
{
if (empty($sceneId)) {
throw new \Exception('场景ID不能为空');
}
$scanData = Redis::get('wechat_scan:' . $sceneId);
if (!$scanData) {
throw new \Exception('二维码已过期');
}
$data = json_decode($scanData, true);
if ($data['status'] === 'scanned') {
// 扫码成功,返回用户信息
$memberId = $data['member_id'] ?? 0;
$member = Member::find($memberId);
if ($member) {
// 更新登录信息
$this->updateLoginInfo($member);
// 生成token
$userInfo = [
'id' => $member->id,
'username' => $member->username,
'nickname' => $member->nickname,
'avatar' => $member->avatar,
'phone' => $member->phone,
'email' => $member->email,
'client' => 'web',
'access_exp' => 7200,
'refresh_exp' => 604800,
];
// 使用新的 JwtToken 生成 token
$jwt = new JwtToken();
$tokenObj = $jwt->generate((string)$member->id, 'api', $userInfo);
$token = [
'access_token' => $tokenObj->accessToken,
'refresh_token' => $tokenObj->refreshToken,
'expires_in' => $tokenObj->expiresIn
];
// 删除扫码记录
Redis::del('wechat_scan:' . $sceneId);
$msg = '扫描成功';
$data = [
'status' => 'success',
'token' => $token,
'user_info' => [
'id' => $member->id,
'username' => $member->username,
'nickname' => $member->nickname,
'avatar' => $member->avatar,
],
];
return compact('msg', 'data');
}
}
$msg = '查询成功';
$data = [
'status' => $data['status'],
];
return compact('msg', 'data');
}
/**
* 检查微信登录可用性
*/
public function checkWechatAvailability(): array
{
$config = config('wechat');
return [
'wechat_enabled' => !empty($config['app_id']) && !empty($config['app_secret']),
'weapp_enabled' => !empty($config['mini_program']['app_id']) && !empty($config['mini_program']['app_secret']),
];
}
/**
* 获取微信用户信息
*/
private function getWechatUserInfo(string $code, string $encryptedData = '', string $iv = ''): array
{
$config = config('wechat');
if (empty($config['app_id']) || empty($config['app_secret'])) {
throw new \Exception('微信配置未完成', 500);
}
// 获取access_token
$accessToken = $this->getWechatAccessToken($code);
// 获取用户信息
$userInfo = $this->getWechatUserInfoByToken($accessToken);
return [
'openid' => $userInfo['openid'] ?? '',
'unionid' => $userInfo['unionid'] ?? '',
'nickname' => $userInfo['nickname'] ?? '',
'avatar' => $userInfo['headimgurl'] ?? '',
'gender' => $userInfo['sex'] ?? 0,
'type' => ThirdPartyType::WECHAT->value,
];
}
/**
* 获取小程序用户信息
*/
private function getWeappUserInfo(string $code, string $encryptedData = '', string $iv = ''): array
{
$config = config('wechat.mini_program');
if (empty($config['app_id']) || empty($config['app_secret'])) {
throw new \Exception('小程序配置未完成', 500);
}
// 获取session_key
$sessionInfo = $this->getWeappSession($code);
// 解密用户信息
$userInfo = $this->decryptWeappData($encryptedData, $sessionInfo['session_key'], $iv);
return [
'openid' => $sessionInfo['openid'] ?? '',
'unionid' => $sessionInfo['unionid'] ?? '',
'nickname' => $userInfo['nickName'] ?? '',
'avatar' => $userInfo['avatarUrl'] ?? '',
'gender' => $userInfo['gender'] ?? 0,
'type' => ThirdPartyType::WEAPP->value,
];
}
/**
* 根据微信信息查找或创建用户
*/
private function findOrCreateMemberByWechat(array $wechatInfo): Member
{
// 查找第三方绑定
$thirdParty = MemberThirdParty::where('openid', $wechatInfo['openid'])
->where('type', ThirdPartyType::WECHAT->value)
->first();
if ($thirdParty) {
// 返回已绑定的用户
return Member::find($thirdParty->member_id);
}
// 创建新用户
$memberData = [
'username' => 'wx_' . substr($wechatInfo['openid'], -8),
'password' => substr(md5(uniqid()), 0, 8),
'nickname' => $wechatInfo['nickname'] ?? '微信用户',
'avatar' => $wechatInfo['avatar'] ?? '',
'enabled' => EnabledStatus::ENABLED->value,
];
$member = Member::create($memberData);
// 创建第三方绑定
MemberThirdParty::create([
'member_id' => $member->id,
'type' => ThirdPartyType::WECHAT->value,
'openid' => $wechatInfo['openid'],
'unionid' => $wechatInfo['unionid'] ?? '',
'nickname' => $wechatInfo['nickname'] ?? '',
'avatar' => $wechatInfo['avatar'] ?? '',
]);
return $member;
}
/**
* 根据小程序信息查找或创建用户
*/
private function findOrCreateMemberByWeapp(array $weappInfo): Member
{
// 查找第三方绑定
$thirdParty = MemberThirdParty::where('openid', $weappInfo['openid'])
->where('type', ThirdPartyType::WEAPP->value)
->first();
if ($thirdParty) {
// 返回已绑定的用户
return Member::find($thirdParty->member_id);
}
// 创建新用户
$memberData = [
'username' => 'wxapp_' . substr($weappInfo['openid'], -8),
'password' => substr(md5(uniqid()), 0, 8),
'nickname' => $weappInfo['nickname'] ?? '小程序用户',
'avatar' => $weappInfo['avatar'] ?? '',
'enabled' => EnabledStatus::ENABLED->value,
];
$member = Member::create($memberData);
// 创建第三方绑定
MemberThirdParty::create([
'member_id' => $member->id,
'type' => ThirdPartyType::WEAPP->value,
'openid' => $weappInfo['openid'],
'unionid' => $weappInfo['unionid'] ?? '',
'nickname' => $weappInfo['nickname'] ?? '',
'avatar' => $weappInfo['avatar'] ?? '',
]);
return $member;
}
/**
* 生成二维码URL
*/
private function generateQrCodeUrl(string $sceneId): string
{
$config = config('wechat');
$appId = $config['app_id'] ?? '';
// 这里应该调用微信API生成二维码,暂时返回模拟URL
return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={$sceneId}";
}
/**
* 获取微信access_token
*/
private function getWechatAccessToken(string $code): string
{
// 这里应该调用微信API获取access_token
// 暂时返回模拟数据
return 'mock_access_token_' . $code;
}
/**
* 根据token获取微信用户信息
*/
private function getWechatUserInfoByToken(string $accessToken): array
{
// 这里应该调用微信API获取用户信息
// 暂时返回模拟数据
return [
'openid' => 'mock_openid_' . uniqid(),
'nickname' => '微信用户',
'headimgurl' => '',
'sex' => 0,
];
}
/**
* 获取小程序session
*/
private function getWeappSession(string $code): array
{
// 这里应该调用微信小程序API获取session
// 暂时返回模拟数据
return [
'openid' => 'mock_weapp_openid_' . uniqid(),
'session_key' => 'mock_session_key',
];
}
/**
* 解密小程序数据
*/
private function decryptWeappData(string $encryptedData, string $sessionKey, string $iv): array
{
// 这里应该解密小程序数据
// 暂时返回模拟数据
return [
'nickName' => '小程序用户',
'avatarUrl' => '',
'gender' => 0,
];
}
/**
* 更新登录信息
*/
private function updateLoginInfo(Member $member): void
{
$member->last_login_time = date('Y-m-d H:i:s');
$member->last_login_ip = \request()->getRealIp();
$member->login_count = $member->login_count + 1;
$member->save();
}
/**
* 生成token
*/
private function generateToken(int $memberId): string
{
$token = md5(uniqid() . $memberId . microtime(true));
$expireTime = 7 * 24 * 3600; // 7天过期
// 存储token到Redis
Redis::setex('user_token:' . md5($token), $expireTime, json_encode([
'member_id' => $memberId,
'login_time' => time(),
]));
return $token;
}
}