feat: add smt module
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\smt\service;
|
||||
|
||||
use app\smt\model\MiniProgram;
|
||||
use app\smt\model\SmokeUserProfile;
|
||||
use app\smt\model\User;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
private const DEFAULT_AVATAR_URL = 'https://linghu-wmr.oss-cn-beijing.aliyuncs.com/smt/avatar.png';
|
||||
|
||||
public function loginWithCode(array $data): array
|
||||
{
|
||||
$miniProgramId = (int) ($data['mini_program_id'] ?? 0);
|
||||
$code = trim((string) ($data['code'] ?? ''));
|
||||
if ($miniProgramId <= 0) {
|
||||
throw new \RuntimeException('mini_program_id is required', 400);
|
||||
}
|
||||
if ($code === '') {
|
||||
throw new \RuntimeException('code is required', 400);
|
||||
}
|
||||
|
||||
$miniProgram = MiniProgram::findActiveById($miniProgramId);
|
||||
if (!$miniProgram) {
|
||||
throw new \RuntimeException('mini program not found', 400);
|
||||
}
|
||||
|
||||
$session = $this->fetchWechatSession((string) $miniProgram->app_id, (string) $miniProgram->app_secret, $code);
|
||||
$openId = trim((string) ($session['openid'] ?? ''));
|
||||
if ($openId === '') {
|
||||
throw new \RuntimeException('微信登录失败,未获取到 openid', 502);
|
||||
}
|
||||
|
||||
$user = User::findByMiniProgramOpenId($miniProgramId, $openId);
|
||||
$isNew = $user === null;
|
||||
if (!$user) {
|
||||
$user = new User();
|
||||
$user->mini_program_id = $miniProgramId;
|
||||
$user->open_id = $openId;
|
||||
$user->created_at = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
}
|
||||
|
||||
$avatarUrl = trim((string) ($data['avatar_url'] ?? ''));
|
||||
$user->union_id = (string) ($session['unionid'] ?? '');
|
||||
$user->nick_name = trim((string) ($data['nickname'] ?? (string) $user->nick_name));
|
||||
$user->avatar_url = $avatarUrl !== '' ? $avatarUrl : ((string) $user->avatar_url !== '' ? (string) $user->avatar_url : self::DEFAULT_AVATAR_URL);
|
||||
$user->gender = array_key_exists('gender', $data) ? (int) ($data['gender'] ?? 0) : (int) ($user->gender ?? 0);
|
||||
$user->phone = trim((string) ($data['phone'] ?? (string) $user->phone));
|
||||
$user->session_key = (string) ($session['session_key'] ?? '');
|
||||
$user->updated_at = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$user->save();
|
||||
|
||||
$payload = $this->formatUser($user);
|
||||
$payload['is_new_user'] = $isNew;
|
||||
$mode = $this->getSmokeMode((int) $user->id);
|
||||
if ($mode !== '') {
|
||||
$payload['mode'] = $mode;
|
||||
}
|
||||
|
||||
return [
|
||||
'user' => $payload,
|
||||
'session_key' => (string) $user->session_key,
|
||||
'mini_program' => [
|
||||
'id' => (int) $miniProgram->id,
|
||||
'name' => (string) $miniProgram->name,
|
||||
'app_id' => (string) $miniProgram->app_id,
|
||||
'description' => (string) $miniProgram->description,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function devLogin(int $miniProgramId): array
|
||||
{
|
||||
if ($miniProgramId <= 0) {
|
||||
throw new \RuntimeException('mini_program_id is required', 400);
|
||||
}
|
||||
|
||||
$miniProgram = MiniProgram::findActiveById($miniProgramId);
|
||||
if (!$miniProgram) {
|
||||
throw new \RuntimeException('mini program not found', 400);
|
||||
}
|
||||
|
||||
$openId = 'dev_test_user';
|
||||
$sessionKey = 'dev_session_' . $miniProgramId;
|
||||
$user = User::findByMiniProgramOpenId($miniProgramId, $openId);
|
||||
if (!$user) {
|
||||
$user = new User();
|
||||
$user->mini_program_id = $miniProgramId;
|
||||
$user->open_id = $openId;
|
||||
$user->nick_name = '开发测试用户';
|
||||
$user->avatar_url = self::DEFAULT_AVATAR_URL;
|
||||
$user->created_at = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
}
|
||||
|
||||
$user->session_key = $sessionKey;
|
||||
$user->updated_at = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$user->save();
|
||||
|
||||
$payload = $this->formatUser($user);
|
||||
$mode = $this->getSmokeMode((int) $user->id);
|
||||
if ($mode !== '') {
|
||||
$payload['mode'] = $mode;
|
||||
}
|
||||
|
||||
return [
|
||||
'user' => $payload,
|
||||
'session_key' => $sessionKey,
|
||||
'mini_program' => [
|
||||
'id' => (int) $miniProgram->id,
|
||||
'name' => (string) $miniProgram->name,
|
||||
'app_id' => (string) $miniProgram->app_id,
|
||||
'description' => (string) $miniProgram->description,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getUserInfo(int $userId): array
|
||||
{
|
||||
$user = User::findActiveById($userId);
|
||||
if (!$user) {
|
||||
throw new \RuntimeException('用户不存在', 404);
|
||||
}
|
||||
|
||||
$payload = $this->formatUser($user);
|
||||
$mode = $this->getSmokeMode((int) $user->id);
|
||||
if ($mode !== '') {
|
||||
$payload['mode'] = $mode;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public function updateProfile(int $userId, array $data): array
|
||||
{
|
||||
$user = User::findActiveById($userId);
|
||||
if (!$user) {
|
||||
throw new \RuntimeException('用户不存在', 404);
|
||||
}
|
||||
|
||||
$nickname = trim((string) ($data['nickname'] ?? ''));
|
||||
$avatarUrl = trim((string) ($data['avatar_url'] ?? ''));
|
||||
if ($nickname === '' && $avatarUrl === '') {
|
||||
throw new \RuntimeException('请提供昵称或头像', 400);
|
||||
}
|
||||
|
||||
if ($nickname !== '') {
|
||||
$user->nick_name = $nickname;
|
||||
}
|
||||
if ($avatarUrl !== '') {
|
||||
$user->avatar_url = $avatarUrl;
|
||||
}
|
||||
$user->save();
|
||||
|
||||
return [
|
||||
'id' => (int) $user->id,
|
||||
'nickname' => (string) $user->nick_name,
|
||||
'avatar_url' => (string) $user->avatar_url,
|
||||
];
|
||||
}
|
||||
|
||||
public function getMiniProgramTestCode(int $userId, string $path, int $width): string
|
||||
{
|
||||
$user = User::findActiveById($userId);
|
||||
if (!$user) {
|
||||
throw new \RuntimeException('用户不存在', 404);
|
||||
}
|
||||
|
||||
$miniProgramId = (int) $user->mini_program_id;
|
||||
if ($miniProgramId <= 0) {
|
||||
throw new \RuntimeException('用户小程序配置缺失', 500);
|
||||
}
|
||||
|
||||
$miniProgram = MiniProgram::findActiveById($miniProgramId);
|
||||
if (!$miniProgram) {
|
||||
throw new \RuntimeException('mini program not found', 400);
|
||||
}
|
||||
|
||||
$path = trim($path);
|
||||
if ($path === '') {
|
||||
$path = 'pages/nsti/test?resume=0';
|
||||
}
|
||||
if ($width <= 0) {
|
||||
$width = 280;
|
||||
}
|
||||
|
||||
return $this->fetchWXACode((string) $miniProgram->app_id, (string) $miniProgram->app_secret, $path, $width);
|
||||
}
|
||||
|
||||
private function getSmokeMode(int $userId): string
|
||||
{
|
||||
$profile = SmokeUserProfile::findByUid($userId);
|
||||
if (!$profile) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Support::normalizedMode((string) $profile->mode);
|
||||
}
|
||||
|
||||
private function formatUser(User $user): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $user->id,
|
||||
'mini_program_id' => (int) $user->mini_program_id,
|
||||
'open_id' => (string) $user->open_id,
|
||||
'union_id' => (string) $user->union_id,
|
||||
'nickname' => (string) $user->nick_name,
|
||||
'avatar_url' => (string) $user->avatar_url,
|
||||
'gender' => (int) $user->gender,
|
||||
'phone' => (string) $user->phone,
|
||||
];
|
||||
}
|
||||
|
||||
private function fetchWechatSession(string $appId, string $appSecret, string $code): array
|
||||
{
|
||||
$url = sprintf(
|
||||
'https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code',
|
||||
urlencode($appId),
|
||||
urlencode($appSecret),
|
||||
urlencode($code)
|
||||
);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
$response = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || $response === '' || $error) {
|
||||
throw new \RuntimeException('微信登录请求失败', 502);
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (!is_array($data)) {
|
||||
throw new \RuntimeException('微信登录响应解析失败', 502);
|
||||
}
|
||||
if (!empty($data['errcode'])) {
|
||||
throw new \RuntimeException(sprintf('微信登录失败:%s', (string) ($data['errmsg'] ?? 'unknown error')), 400);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function fetchAccessToken(string $appId, string $appSecret): string
|
||||
{
|
||||
$url = sprintf(
|
||||
'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s',
|
||||
urlencode($appId),
|
||||
urlencode($appSecret)
|
||||
);
|
||||
|
||||
$data = $this->getJson($url, '微信 access_token 请求失败');
|
||||
if (!empty($data['errcode'])) {
|
||||
throw new \RuntimeException(sprintf('微信 access_token 获取失败:%s', (string) ($data['errmsg'] ?? 'unknown error')), 502);
|
||||
}
|
||||
|
||||
$token = trim((string) ($data['access_token'] ?? ''));
|
||||
if ($token === '') {
|
||||
throw new \RuntimeException('微信 access_token 缺失', 502);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
private function fetchWXACode(string $appId, string $appSecret, string $path, int $width): string
|
||||
{
|
||||
$accessToken = $this->fetchAccessToken($appId, $appSecret);
|
||||
$url = sprintf('https://api.weixin.qq.com/wxa/getwxacode?access_token=%s', urlencode($accessToken));
|
||||
$payload = json_encode([
|
||||
'path' => $path,
|
||||
'width' => $width,
|
||||
'auto_color' => false,
|
||||
'is_hyaline' => true,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
$response = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || $response === '' || $error) {
|
||||
throw new \RuntimeException('微信小程序码请求失败', 502);
|
||||
}
|
||||
if ($status !== 200) {
|
||||
throw new \RuntimeException('微信小程序码接口状态异常', 502);
|
||||
}
|
||||
|
||||
$decoded = json_decode((string) $response, true);
|
||||
if (is_array($decoded) && !empty($decoded['errcode'])) {
|
||||
throw new \RuntimeException(sprintf('微信小程序码获取失败:%s', (string) ($decoded['errmsg'] ?? 'unknown error')), 502);
|
||||
}
|
||||
|
||||
return (string) $response;
|
||||
}
|
||||
|
||||
private function getJson(string $url, string $failMessage): array
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
$response = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || $response === '' || $error || $status !== 200) {
|
||||
throw new \RuntimeException($failMessage, 502);
|
||||
}
|
||||
|
||||
$data = json_decode((string) $response, true);
|
||||
if (!is_array($data)) {
|
||||
throw new \RuntimeException($failMessage, 502);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user