1. 移除NFC和蓝牙相关依赖、权限及功能代码,精简传输链路 2. 重构设备在线统计逻辑,使用后端7天活跃字段替代本地计算 3. 更新应用名称、权限说明和协议文档 4. 新增消息转发、缓存管理、医疗免责提示功能 5. 优化运势模块和字体管理文案,修复构建日志问题
710 lines
24 KiB
PHP
710 lines
24 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use app\common\controller\Api;
|
||
use think\Db;
|
||
use think\Exception;
|
||
|
||
/**
|
||
* OAuth2.0 社交登录
|
||
*
|
||
* 支持平台: apple, google, github
|
||
* 流程: 客户端获取授权码 → 服务端换取access_token → 获取用户信息 → 创建/关联账号
|
||
*
|
||
* 创建时间: 2026-06-05
|
||
* 更新时间: 2026-06-05
|
||
* 名称: Oauth
|
||
* 作用: OAuth社交登录接口,支持Apple/Google/GitHub三方登录、绑定、解绑
|
||
* 上次更新: 新增OAuth社交登录接口
|
||
*/
|
||
class Oauth extends Api
|
||
{
|
||
protected $noNeedLogin = ['*'];
|
||
protected $noNeedRight = ['*'];
|
||
|
||
/** OAuth平台远程地址配置 */
|
||
private $oauthConfig = [
|
||
'apple' => [
|
||
'verify_url' => 'https://appleid.apple.com/auth/token',
|
||
'revoke_url' => 'https://appleid.apple.com/auth/revoke',
|
||
],
|
||
'google' => [
|
||
'token_url' => 'https://oauth2.googleapis.com/token',
|
||
'userinfo_url' => 'https://www.googleapis.com/oauth2/v3/userinfo',
|
||
],
|
||
'github' => [
|
||
'token_url' => 'https://github.com/login/oauth/access_token',
|
||
'userinfo_url' => 'https://api.github.com/user',
|
||
],
|
||
];
|
||
|
||
/** 频率限制配置 */
|
||
private static $rateLimits = [
|
||
'login' => ['max' => 30, 'window' => 300],
|
||
'bind' => ['max' => 20, 'window' => 3600],
|
||
'unbind' => ['max' => 20, 'window' => 3600],
|
||
];
|
||
|
||
/** 支持的平台列表 */
|
||
private static $supportedPlatforms = ['apple', 'google', 'github'];
|
||
|
||
// ==================== 公开接口 ====================
|
||
|
||
/**
|
||
* 获取OAuth配置
|
||
* GET /api/oauth/config?platform=apple
|
||
*/
|
||
public function config()
|
||
{
|
||
$platform = $this->request->param('platform', '', 'trim');
|
||
if (!in_array($platform, self::$supportedPlatforms)) {
|
||
$this->error('不支持的平台');
|
||
}
|
||
|
||
$config = $this->getOAuthConfig($platform);
|
||
if (!$config) {
|
||
$this->error('平台未配置', null, ['platform' => $platform, 'configured' => false]);
|
||
}
|
||
|
||
$this->success('', [
|
||
'platform' => $platform,
|
||
'configured' => true,
|
||
'client_id' => $config['client_id'] ?? '',
|
||
'redirect_uri' => $config['redirect_uri'] ?? '',
|
||
'authorize_url' => $this->getAuthorizeUrl($platform, $config),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 社交登录
|
||
* POST /api/oauth/login
|
||
*
|
||
* 参数:
|
||
* - platform: apple/google/github
|
||
* - code: 授权码(google/github使用)
|
||
* - id_token: Apple ID Token(apple使用)
|
||
* - device_name: 设备名称(可选)
|
||
* - device_model: 设备型号(可选)
|
||
* - platform_type: 登录平台ios/android/web(可选)
|
||
* - device_id: 设备唯一标识(可选)
|
||
*/
|
||
public function login()
|
||
{
|
||
$platform = $this->request->post('platform', '', 'trim');
|
||
$code = $this->request->post('code', '', 'trim');
|
||
$idToken = $this->request->post('id_token', '', 'trim');
|
||
|
||
if (!in_array($platform, self::$supportedPlatforms)) {
|
||
$this->error('不支持的平台');
|
||
}
|
||
|
||
// 频率限制
|
||
$this->checkRateLimit('login');
|
||
|
||
// 获取第三方用户信息
|
||
try {
|
||
$oauthUser = $this->getOAuthUserInfo($platform, $code, $idToken);
|
||
} catch (Exception $e) {
|
||
$this->error('OAuth验证失败: ' . $e->getMessage());
|
||
}
|
||
|
||
if (!$oauthUser || empty($oauthUser['openid'])) {
|
||
$this->error('获取用户信息失败');
|
||
}
|
||
|
||
// 查找或创建用户
|
||
$user = $this->findOrCreateUser($platform, $oauthUser);
|
||
if (!$user) {
|
||
$this->error('登录失败');
|
||
}
|
||
|
||
// 生成Token
|
||
$token = $this->generateToken($user);
|
||
|
||
// 记录设备信息
|
||
$this->recordDeviceInfo($user);
|
||
|
||
$this->success('登录成功', [
|
||
'userinfo' => [
|
||
'id' => $user['id'],
|
||
'username' => $user['username'],
|
||
'nickname' => $user['nickname'],
|
||
'email' => $user['email'],
|
||
'avatar' => $user['avatar'],
|
||
],
|
||
'token' => $token,
|
||
'is_new_user' => $oauthUser['is_new'] ?? false,
|
||
'bind_platform' => $platform,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 绑定社交账号(已登录用户)
|
||
* POST /api/oauth/bind
|
||
*/
|
||
public function bind()
|
||
{
|
||
$this->checkLogin();
|
||
$platform = $this->request->post('platform', '', 'trim');
|
||
$code = $this->request->post('code', '', 'trim');
|
||
$idToken = $this->request->post('id_token', '', 'trim');
|
||
|
||
if (!in_array($platform, self::$supportedPlatforms)) {
|
||
$this->error('不支持的平台');
|
||
}
|
||
|
||
$this->checkRateLimit('bind');
|
||
|
||
try {
|
||
$oauthUser = $this->getOAuthUserInfo($platform, $code, $idToken);
|
||
} catch (Exception $e) {
|
||
$this->error('OAuth验证失败: ' . $e->getMessage());
|
||
}
|
||
|
||
// 检查是否已被其他用户绑定
|
||
$existing = Db::name('user_oauth')
|
||
->where('platform', $platform)
|
||
->where('openid', $oauthUser['openid'])
|
||
->find();
|
||
|
||
if ($existing) {
|
||
if ($existing['user_id'] == $this->auth->id) {
|
||
$this->error('已绑定该平台');
|
||
}
|
||
$this->error('该账号已被其他用户绑定');
|
||
}
|
||
|
||
// 绑定
|
||
Db::name('user_oauth')->insert([
|
||
'user_id' => $this->auth->id,
|
||
'platform' => $platform,
|
||
'openid' => $oauthUser['openid'],
|
||
'unionid' => $oauthUser['unionid'] ?? '',
|
||
'nickname' => $oauthUser['nickname'] ?? '',
|
||
'avatar' => $oauthUser['avatar'] ?? '',
|
||
'access_token' => $oauthUser['access_token'] ?? '',
|
||
'refresh_token' => $oauthUser['refresh_token'] ?? '',
|
||
'expires_at' => $oauthUser['expires_at'] ?? 0,
|
||
'createtime' => time(),
|
||
'updatetime' => time(),
|
||
]);
|
||
|
||
$this->success('绑定成功', [
|
||
'platform' => $platform,
|
||
'nickname' => $oauthUser['nickname'] ?? '',
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 解绑社交账号
|
||
* POST /api/oauth/unbind
|
||
*/
|
||
public function unbind()
|
||
{
|
||
$this->checkLogin();
|
||
$platform = $this->request->post('platform', '', 'trim');
|
||
|
||
if (!in_array($platform, self::$supportedPlatforms)) {
|
||
$this->error('不支持的平台');
|
||
}
|
||
|
||
$this->checkRateLimit('unbind');
|
||
|
||
$count = Db::name('user_oauth')
|
||
->where('user_id', $this->auth->id)
|
||
->where('platform', $platform)
|
||
->delete();
|
||
|
||
if ($count) {
|
||
$this->success('解绑成功');
|
||
}
|
||
$this->error('未绑定该平台');
|
||
}
|
||
|
||
/**
|
||
* 获取已绑定的社交账号列表
|
||
* GET /api/oauth/bound
|
||
*/
|
||
public function bound()
|
||
{
|
||
$this->checkLogin();
|
||
$list = Db::name('user_oauth')
|
||
->where('user_id', $this->auth->id)
|
||
->field('platform,openid,nickname,avatar,createtime')
|
||
->select();
|
||
|
||
$this->success('', ['bindings' => $list]);
|
||
}
|
||
|
||
/**
|
||
* 安装OAuth数据表
|
||
* GET /api/oauth/install
|
||
*/
|
||
public function install()
|
||
{
|
||
$sql = "CREATE TABLE IF NOT EXISTS `tool_user_oauth` (
|
||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||
`user_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
|
||
`platform` varchar(30) NOT NULL DEFAULT '' COMMENT '平台(apple/google/github)',
|
||
`openid` varchar(128) NOT NULL DEFAULT '' COMMENT '平台用户ID',
|
||
`unionid` varchar(128) NOT NULL DEFAULT '' COMMENT '联合ID',
|
||
`nickname` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',
|
||
`avatar` varchar(500) NOT NULL DEFAULT '' COMMENT '头像',
|
||
`access_token` text COMMENT '访问令牌',
|
||
`refresh_token` text COMMENT '刷新令牌',
|
||
`expires_at` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '令牌过期时间',
|
||
`createtime` int(11) unsigned NOT NULL DEFAULT 0,
|
||
`updatetime` int(11) unsigned NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_platform_openid` (`platform`, `openid`),
|
||
KEY `idx_user_id` (`user_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户OAuth绑定表';";
|
||
|
||
try {
|
||
Db::execute($sql);
|
||
$this->success('安装成功', [
|
||
'table' => 'tool_user_oauth',
|
||
'created' => true,
|
||
]);
|
||
} catch (Exception $e) {
|
||
$this->error('安装失败: ' . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
// ==================== 私有方法 ====================
|
||
|
||
/**
|
||
* 获取OAuth配置
|
||
* 优先从数据库读取,回退到配置文件
|
||
*/
|
||
private function getOAuthConfig($platform)
|
||
{
|
||
// 从数据库 tool_oauth_config 读取
|
||
try {
|
||
$config = Db::name('oauth_config')
|
||
->where('platform', $platform)
|
||
->where('status', 1)
|
||
->find();
|
||
|
||
if ($config) {
|
||
return json_decode($config['config'], true);
|
||
}
|
||
} catch (Exception $e) {
|
||
// 表不存在时忽略,回退到配置文件
|
||
}
|
||
|
||
// 回退到配置文件
|
||
$fileConfig = get_addon_config('third');
|
||
return $fileConfig[$platform] ?? null;
|
||
}
|
||
|
||
/**
|
||
* 获取授权URL
|
||
*/
|
||
private function getAuthorizeUrl($platform, $config)
|
||
{
|
||
$clientId = $config['client_id'] ?? '';
|
||
$redirectUri = $config['redirect_uri'] ?? '';
|
||
$state = md5(uniqid());
|
||
|
||
switch ($platform) {
|
||
case 'apple':
|
||
return "https://appleid.apple.com/auth/authorize?client_id={$clientId}&redirect_uri=" . urlencode($redirectUri) . "&response_type=code&scope=name%20email&state={$state}";
|
||
case 'google':
|
||
return "https://accounts.google.com/o/oauth2/v2/auth?client_id={$clientId}&redirect_uri=" . urlencode($redirectUri) . "&response_type=code&scope=openid%20email%20profile&state={$state}";
|
||
case 'github':
|
||
return "https://github.com/login/oauth/authorize?client_id={$clientId}&redirect_uri=" . urlencode($redirectUri) . "&scope=user:email&state={$state}";
|
||
default:
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取OAuth用户信息(分发到各平台验证方法)
|
||
*/
|
||
private function getOAuthUserInfo($platform, $code, $idToken)
|
||
{
|
||
switch ($platform) {
|
||
case 'apple':
|
||
return $this->verifyApple($idToken, $code);
|
||
case 'google':
|
||
return $this->verifyGoogle($code);
|
||
case 'github':
|
||
return $this->verifyGithub($code);
|
||
default:
|
||
throw new Exception('不支持的平台');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证Apple ID Token
|
||
* Apple返回的id_token是JWT格式,解析payload获取用户信息
|
||
*/
|
||
private function verifyApple($idToken, $code)
|
||
{
|
||
if (empty($idToken)) {
|
||
throw new Exception('id_token不能为空');
|
||
}
|
||
|
||
// 解码JWT(Apple ID Token是JWT格式)
|
||
$parts = explode('.', $idToken);
|
||
if (count($parts) !== 3) {
|
||
throw new Exception('无效的id_token格式');
|
||
}
|
||
|
||
$payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);
|
||
if (!$payload) {
|
||
throw new Exception('无法解析id_token');
|
||
}
|
||
|
||
// 验证issuer
|
||
if (($payload['iss'] ?? '') !== 'https://appleid.apple.com') {
|
||
throw new Exception('无效的issuer');
|
||
}
|
||
|
||
return [
|
||
'openid' => $payload['sub'] ?? '',
|
||
'email' => $payload['email'] ?? '',
|
||
'nickname' => '',
|
||
'avatar' => '',
|
||
'access_token' => $code,
|
||
'refresh_token' => '',
|
||
'expires_at' => $payload['exp'] ?? 0,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 验证Google授权码
|
||
* 用授权码换取access_token,再获取用户信息
|
||
*/
|
||
private function verifyGoogle($code)
|
||
{
|
||
if (empty($code)) {
|
||
throw new Exception('授权码不能为空');
|
||
}
|
||
|
||
$config = $this->getOAuthConfig('google');
|
||
if (!$config) {
|
||
throw new Exception('Google OAuth未配置');
|
||
}
|
||
|
||
// 用授权码换取access_token
|
||
$response = $this->httpPost($this->oauthConfig['google']['token_url'], [
|
||
'code' => $code,
|
||
'client_id' => $config['client_id'],
|
||
'client_secret' => $config['client_secret'],
|
||
'redirect_uri' => $config['redirect_uri'],
|
||
'grant_type' => 'authorization_code',
|
||
]);
|
||
|
||
$tokenData = json_decode($response, true);
|
||
if (empty($tokenData['access_token'])) {
|
||
throw new Exception('获取Google access_token失败');
|
||
}
|
||
|
||
// 获取用户信息
|
||
$userInfo = $this->httpGet($this->oauthConfig['google']['userinfo_url'], [
|
||
'Authorization: Bearer ' . $tokenData['access_token'],
|
||
]);
|
||
|
||
$userData = json_decode($userInfo, true);
|
||
if (empty($userData['sub'])) {
|
||
throw new Exception('获取Google用户信息失败');
|
||
}
|
||
|
||
return [
|
||
'openid' => 'google_' . $userData['sub'],
|
||
'unionid' => $userData['sub'] ?? '',
|
||
'email' => $userData['email'] ?? '',
|
||
'nickname' => $userData['name'] ?? '',
|
||
'avatar' => $userData['picture'] ?? '',
|
||
'access_token' => $tokenData['access_token'],
|
||
'refresh_token' => $tokenData['refresh_token'] ?? '',
|
||
'expires_at' => time() + ($tokenData['expires_in'] ?? 3600),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 验证GitHub授权码
|
||
* 用授权码换取access_token,再获取用户信息
|
||
*/
|
||
private function verifyGithub($code)
|
||
{
|
||
if (empty($code)) {
|
||
throw new Exception('授权码不能为空');
|
||
}
|
||
|
||
$config = $this->getOAuthConfig('oauth_github');
|
||
if (!$config) {
|
||
// 回退尝试 github 配置
|
||
$config = $this->getOAuthConfig('github');
|
||
}
|
||
if (!$config) {
|
||
throw new Exception('GitHub OAuth未配置');
|
||
}
|
||
|
||
// 用授权码换取access_token
|
||
$response = $this->httpPost($this->oauthConfig['github']['token_url'], [
|
||
'code' => $code,
|
||
'client_id' => $config['client_id'],
|
||
'client_secret' => $config['client_secret'],
|
||
'redirect_uri' => $config['redirect_uri'],
|
||
], ['Accept: application/json']);
|
||
|
||
$tokenData = json_decode($response, true);
|
||
if (empty($tokenData['access_token'])) {
|
||
throw new Exception('获取GitHub access_token失败');
|
||
}
|
||
|
||
// 获取用户信息
|
||
$userInfo = $this->httpGet($this->oauthConfig['github']['userinfo_url'], [
|
||
'Authorization: Bearer ' . $tokenData['access_token'],
|
||
]);
|
||
|
||
$userData = json_decode($userInfo, true);
|
||
if (empty($userData['id'])) {
|
||
throw new Exception('获取GitHub用户信息失败');
|
||
}
|
||
|
||
// 获取邮箱(GitHub可能不返回主邮箱)
|
||
$email = $userData['email'] ?? '';
|
||
if (empty($email)) {
|
||
$emails = $this->httpGet('https://api.github.com/user/emails', [
|
||
'Authorization: Bearer ' . $tokenData['access_token'],
|
||
]);
|
||
$emailList = json_decode($emails, true);
|
||
if (is_array($emailList)) {
|
||
foreach ($emailList as $e) {
|
||
if (($e['primary'] ?? false) && ($e['verified'] ?? false)) {
|
||
$email = $e['email'];
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return [
|
||
'openid' => 'github_' . $userData['id'],
|
||
'unionid' => (string)$userData['id'],
|
||
'email' => $email,
|
||
'nickname' => $userData['login'] ?? '',
|
||
'avatar' => $userData['avatar_url'] ?? '',
|
||
'access_token' => $tokenData['access_token'],
|
||
'refresh_token' => '',
|
||
'expires_at' => 0,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 查找或创建用户
|
||
* 1. 查找已有绑定 → 2. 通过邮箱查找 → 3. 创建新用户
|
||
*/
|
||
private function findOrCreateUser($platform, $oauthUser)
|
||
{
|
||
// 1. 查找已有绑定
|
||
$binding = Db::name('user_oauth')
|
||
->where('platform', $platform)
|
||
->where('openid', $oauthUser['openid'])
|
||
->find();
|
||
|
||
if ($binding) {
|
||
$user = Db::name('user')->where('id', $binding['user_id'])->find();
|
||
if ($user) {
|
||
// 更新token
|
||
Db::name('user_oauth')->where('id', $binding['id'])->update([
|
||
'access_token' => $oauthUser['access_token'] ?? '',
|
||
'refresh_token' => $oauthUser['refresh_token'] ?? '',
|
||
'expires_at' => $oauthUser['expires_at'] ?? 0,
|
||
'updatetime' => time(),
|
||
]);
|
||
$oauthUser['is_new'] = false;
|
||
return $user;
|
||
}
|
||
}
|
||
|
||
// 2. 通过邮箱查找已有用户
|
||
$user = null;
|
||
if (!empty($oauthUser['email'])) {
|
||
$user = Db::name('user')->where('email', $oauthUser['email'])->find();
|
||
}
|
||
|
||
// 3. 创建新用户
|
||
if (!$user) {
|
||
$username = $platform . '_' . substr($oauthUser['openid'], -8);
|
||
// 确保用户名唯一
|
||
$i = 1;
|
||
$baseUsername = $username;
|
||
while (Db::name('user')->where('username', $username)->find()) {
|
||
$username = $baseUsername . '_' . $i++;
|
||
}
|
||
|
||
$userId = Db::name('user')->insertGetId([
|
||
'username' => $username,
|
||
'nickname' => $oauthUser['nickname'] ?: $username,
|
||
'email' => $oauthUser['email'] ?? '',
|
||
'avatar' => $oauthUser['avatar'] ?? '',
|
||
'password' => md5(md5(uniqid())),
|
||
'salt' => '',
|
||
'status' => 'normal',
|
||
'verification' => json_encode(['email' => !empty($oauthUser['email']) ? 1 : 0]),
|
||
'createtime' => time(),
|
||
'updatetime' => time(),
|
||
]);
|
||
|
||
$user = Db::name('user')->where('id', $userId)->find();
|
||
$oauthUser['is_new'] = true;
|
||
} else {
|
||
$oauthUser['is_new'] = false;
|
||
}
|
||
|
||
// 4. 创建绑定
|
||
Db::name('user_oauth')->insert([
|
||
'user_id' => $user['id'],
|
||
'platform' => $platform,
|
||
'openid' => $oauthUser['openid'],
|
||
'unionid' => $oauthUser['unionid'] ?? '',
|
||
'nickname' => $oauthUser['nickname'] ?? '',
|
||
'avatar' => $oauthUser['avatar'] ?? '',
|
||
'access_token' => $oauthUser['access_token'] ?? '',
|
||
'refresh_token' => $oauthUser['refresh_token'] ?? '',
|
||
'expires_at' => $oauthUser['expires_at'] ?? 0,
|
||
'createtime' => time(),
|
||
'updatetime' => time(),
|
||
]);
|
||
|
||
return $user;
|
||
}
|
||
|
||
/**
|
||
* 生成Token
|
||
*/
|
||
private function generateToken($user)
|
||
{
|
||
$token = \fast\Random::uuid();
|
||
Db::name('user_token')->insert([
|
||
'user_id' => $user['id'],
|
||
'token' => $token,
|
||
'createtime' => time(),
|
||
'expiretime' => time() + 86400 * 30,
|
||
]);
|
||
return $token;
|
||
}
|
||
|
||
/**
|
||
* 记录设备信息
|
||
*/
|
||
private function recordDeviceInfo($user)
|
||
{
|
||
$deviceName = $this->request->post('device_name', '', 'trim');
|
||
$deviceModel = $this->request->post('device_model', '', 'trim');
|
||
$platformType = $this->request->post('platform_type', '', 'trim');
|
||
$deviceId = $this->request->post('device_id', '', 'trim');
|
||
$ipCity = $this->request->post('ip_city', '', 'trim');
|
||
$ipRange = $this->request->post('ip_range', '', 'trim');
|
||
|
||
if ($deviceId) {
|
||
$existing = Db::name('user_device')
|
||
->where('user_id', $user['id'])
|
||
->where('device_id', $deviceId)
|
||
->find();
|
||
|
||
$data = [
|
||
'device_name' => $deviceName ?: $deviceModel,
|
||
'device_model' => $deviceModel,
|
||
'platform' => $platformType,
|
||
'app_name' => '闲言工具箱',
|
||
'ip' => request()->ip(),
|
||
'ip_city' => $ipCity,
|
||
'ip_range' => $ipRange,
|
||
'last_active_time' => time(),
|
||
'is_online' => 1,
|
||
'updatetime' => time(),
|
||
];
|
||
|
||
if ($existing) {
|
||
Db::name('user_device')->where('id', $existing['id'])->update($data);
|
||
} else {
|
||
$data['user_id'] = $user['id'];
|
||
$data['device_id'] = $deviceId;
|
||
$data['createtime'] = time();
|
||
Db::name('user_device')->insert($data);
|
||
}
|
||
}
|
||
|
||
// 更新用户在线状态
|
||
Db::name('user')->where('id', $user['id'])->update([
|
||
'is_online' => 1,
|
||
'last_active_time' => time(),
|
||
'logintime' => time(),
|
||
'updatetime' => time(),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 频率限制检查
|
||
*/
|
||
private function checkRateLimit($action)
|
||
{
|
||
if (!isset(self::$rateLimits[$action])) {
|
||
return true;
|
||
}
|
||
$config = self::$rateLimits[$action];
|
||
$key = 'rate_oauth_' . $action . '_' . md5($this->request->ip());
|
||
$count = cache($key) ?: 0;
|
||
if ($count >= $config['max']) {
|
||
$this->error('请求过于频繁,请稍后再试', null, ['retry_after' => $config['window']]);
|
||
}
|
||
cache($key, $count + 1, $config['window']);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 检查用户是否已登录
|
||
*/
|
||
private function checkLogin()
|
||
{
|
||
if (!$this->auth->id) {
|
||
$this->error('请先登录', null, 401);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* HTTP GET 请求
|
||
*/
|
||
private function httpGet($url, $headers = [])
|
||
{
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $url,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 15,
|
||
CURLOPT_HTTPHEADER => array_merge(['User-Agent: XianYan/1.0'], $headers),
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
]);
|
||
$response = curl_exec($ch);
|
||
curl_close($ch);
|
||
return $response;
|
||
}
|
||
|
||
/**
|
||
* HTTP POST 请求
|
||
*/
|
||
private function httpPost($url, $data = [], $headers = [])
|
||
{
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $url,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => http_build_query($data),
|
||
CURLOPT_TIMEOUT => 15,
|
||
CURLOPT_HTTPHEADER => array_merge(['User-Agent: XianYan/1.0', 'Accept: application/json'], $headers),
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
]);
|
||
$response = curl_exec($ch);
|
||
curl_close($ch);
|
||
return $response;
|
||
}
|
||
}
|