Browse Source

feat:用户端-微信授权

刘学玺 4 months ago
parent
commit
3bebab148c

+ 114 - 0
app/Http/Controllers/Client/WechatController.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace App\Http\Controllers\Client;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\Client\Wechat\GetAuthUrlRequest;
+use App\Http\Requests\Client\Wechat\HandleCallbackRequest;
+use App\Services\Client\WechatService;
+
+/**
+ * @group 用户端
+ *
+ * 微信相关的API接口
+ */
+class WechatController extends Controller
+{
+    protected WechatService $wechatService;
+
+    public function __construct(WechatService $wechatService)
+    {
+        $this->wechatService = $wechatService;
+    }
+
+    /**
+     * [微信]获取微信授权URL
+     *
+     * @description 获取微信网页授权URL
+     *
+     * @queryParam redirect_url required 授权后的回调地址,必须是有效的URL Example: https://example.com/callback
+     * @queryParam scope 授权范围,可选值:snsapi_base,snsapi_userinfo Example: snsapi_userinfo
+     *
+     * @response 200 scenario="成功" {
+     *   "code": 200,
+     *   "data": {
+     *     "auth_url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx123456&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&response_type=code&scope=snsapi_userinfo&state=7d8f69a...",
+     *     "state": "7d8f69a..."
+     *   }
+     * }
+     * @response 422 scenario="参数验证失败" {
+     *   "code": 422,
+     *   "message": "回调地址不能为空|回调地址必须是一个有效的URL"
+     * }
+     * @response 400 scenario="参数错误" {
+     *   "code": 400,
+     *   "message": "生成授权链接失败,请稍后重试"
+     * }
+     *
+     * @throws BusinessException
+     */
+    public function getAuthUrl(GetAuthUrlRequest $request)
+    {
+        $validated = $request->validated();
+
+        $result = $this->wechatService->getAuthUrl(
+            $validated['redirect_url'],
+            $validated['scope'] ?? 'snsapi_userinfo'
+        );
+
+        return $this->success($result);
+    }
+
+    /**
+     * [微信]处理微信授权回调
+     *
+     * @description 处理微信授权回调,完成登录。用于接收微信授权后的回调,处理用户登录注册流程。
+     *
+     * @bodyParam code string required 微信授权码 Example: 071Jh6ll2tQfM54Kjtol2pWDCE1Jh6lH
+     * @bodyParam state string required 状态码,需要与获取授权URL时返回的state一致,长度必须为32位 Example: 7d8f69a...
+     *
+     * @response 200 scenario="成功" {
+     *   "code": 200,
+     *   "data": {
+     *     "token": "1|2uyxxxxxxxxxx",
+     *     "user": {
+     *       "id": 1,
+     *       "nickname": "张三",
+     *       "avatar": "https://thirdwx.qlogo.cn/mmopen/xxx/132",
+     *       "gender": "male",
+     *       "country": "中国",
+     *       "province": "广东",
+     *       "city": "深圳",
+     *       "created_at": "2024-01-01 00:00:00",
+     *       "updated_at": "2024-01-01 00:00:00"
+     *     }
+     *   }
+     * }
+     * @response 422 scenario="参数验证失败" {
+     *   "code": 422,
+     *   "message": "授权码不能为空|状态码长度必须为32位"
+     * }
+     * @response 400 scenario="无效的state" {
+     *   "code": 400,
+     *   "message": "无效的授权请求"
+     * }
+     * @response 400 scenario="授权失败" {
+     *   "code": 400,
+     *   "message": "微信授权失败,请稍后重试"
+     * }
+     *
+     * @throws BusinessException 当授权验证失败或处理出错时抛出
+     */
+    public function handleCallback(HandleCallbackRequest $request)
+    {
+        $validated = $request->validated();
+
+        $result = $this->wechatService->handleCallback(
+            $validated['code'],
+            $validated['state'],
+            $validated['invite_code'] ?? null
+        );
+
+        return $this->success($result);
+    }
+}

+ 42 - 0
app/Http/Requests/Client/Wechat/GetAuthUrlRequest.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Http\Requests\Client\Wechat;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class GetAuthUrlRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, array<int, string>>
+     */
+    public function rules(): array
+    {
+        return [
+            'redirect_url' => ['required', 'url'],
+            'scope' => ['nullable', 'string', 'in:snsapi_base,snsapi_userinfo'],
+        ];
+    }
+
+    /**
+     * Get custom attributes for validator errors.
+     *
+     * @return array<string, string>
+     */
+    public function attributes(): array
+    {
+        return [
+            'redirect_url' => '回调地址',
+            'scope' => '授权范围',
+        ];
+    }
+}

+ 43 - 0
app/Http/Requests/Client/Wechat/HandleCallbackRequest.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Http\Requests\Client\Wechat;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class HandleCallbackRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, array<int, string>>
+     */
+    public function rules(): array
+    {
+        return [
+            'code' => ['required', 'string'],
+            'state' => ['required', 'string', 'size:32'],
+            'invite_code' => ['nullable', 'string'],
+        ];
+    }
+
+    /**
+     * Get custom attributes for validator errors.
+     *
+     * @return array<string, string>
+     */
+    public function attributes(): array
+    {
+        return [
+            'code' => '授权码',
+            'state' => '状态码',
+        ];
+    }
+}

+ 1 - 0
app/Providers/WechatServiceProvider.php

@@ -0,0 +1 @@
+ 

+ 59 - 4
config/wechat.php

@@ -1,5 +1,47 @@
 <?php
+
 return [
+    /*
+     * 授权相关配置
+     */
+    'auth' => [
+        'cache_prefix' => 'wechat_auth_state:',
+        'cache_ttl' => 600, // 10分钟
+        'default_scope' => 'snsapi_userinfo',
+    ],
+
+    /*
+     * 默认配置,将会合并到各模块中
+     */
+    'defaults' => [
+        /*
+         * 指定 API 调用返回结果的类型:array(default)/object/raw/自定义类名
+         */
+        'response_type' => 'array',
+
+        /*
+         * 使用 Laravel 的缓存系统
+         */
+        'use_laravel_cache' => true,
+
+        /*
+         * 日志配置
+         */
+        'log' => [
+            'default' => 'single',
+            'channels' => [
+                'single' => [
+                    'driver' => 'single',
+                    'path' => storage_path('logs/wechat.log'),
+                    'level' => 'debug',
+                ],
+            ],
+        ],
+    ],
+
+    /*
+     * 公众号
+     */
     'official_account' => [
         'default' => [
             'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', ''),      // AppID
@@ -7,10 +49,23 @@ return [
             'token' => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', ''),       // Token
             'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_AES_KEY', ''),  // EncodingAESKey
 
-            'response_type' => 'array',
-            'log' => [
-                'level' => 'debug',
-                'file' => storage_path('logs/wechat.log'),
+            /**
+             * OAuth 配置
+             */
+            'oauth' => [
+                'scopes' => ['snsapi_userinfo'],
+                'callback' => env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_CALLBACK', '/oauth_callback'),
+            ],
+
+            /**
+             * 接口请求相关配置,超时时间等,具体可用参数请参考:
+             * https://github.com/symfony/symfony/blob/5.3/src/Symfony/Contracts/HttpClient/HttpClientInterface.php
+             */
+            'http' => [
+                'timeout' => 5.0,
+                'retry' => true,
+                'retry_delay' => 500,
+                'max_retries' => 1,
             ],
         ],
     ],

+ 7 - 0
routes/api.php

@@ -9,6 +9,7 @@ use App\Http\Controllers\Client\ProjectController;
 use App\Http\Controllers\Client\UserAddressController;
 use App\Http\Controllers\Client\UserController;
 use App\Http\Controllers\Client\WalletController;
+use App\Http\Controllers\Client\WechatController;
 use App\Http\Controllers\Coach\OrderController as CoachOrderController;
 use App\Http\Controllers\Coach\ProjectController as CoachProjectController;
 use App\Http\Controllers\Coach\WalletController as CoachWalletController;
@@ -133,6 +134,12 @@ Route::prefix('client')->group(function () {
             Route::get('list', [MarketDistTeamController::class, 'index'])->name('team.list');
         });
 
+        // 微信相关路由
+        Route::prefix('wechat')->group(function () {
+            Route::get('auth-url', [WechatController::class, 'getAuthUrl']);
+            Route::post('callback', [WechatController::class, 'handleCallback']);
+        });
+
     });
 
 });