From 93c65b56f68ce2202f9080849030680f195b1179 Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Wed, 15 Jul 2026 12:07:32 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 6 + app/Http/Controllers/IndexController.php | 74 +- app/Http/Controllers/UserController.php | 65 +- app/Models/UserModel.php | 4 + app/Services/Wechat/MiniProgramService.php | 72 ++ composer.json | 3 +- composer.lock | 1021 +++++++++++++++++++- config/wechat.php | 26 + routes/api.php | 11 +- 9 files changed, 1231 insertions(+), 51 deletions(-) create mode 100644 app/Services/Wechat/MiniProgramService.php create mode 100644 config/wechat.php diff --git a/.env.example b/.env.example index fdd5275..34ae2e6 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,12 @@ QUEUE_CONNECTION=database MEMCACHED_HOST=127.0.0.1 +# 微信小程序配置 +WECHAT_MINI_PROGRAM_APP_ID= +WECHAT_MINI_PROGRAM_SECRET= +WECHAT_MINI_PROGRAM_TOKEN= +WECHAT_MINI_PROGRAM_AES_KEY= + # 系统设置 SETTING_CACHE_KEY=settings diff --git a/app/Http/Controllers/IndexController.php b/app/Http/Controllers/IndexController.php index 825d653..f6b1959 100644 --- a/app/Http/Controllers/IndexController.php +++ b/app/Http/Controllers/IndexController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use App\Http\Requests\UserRegisterRequest; use App\Models\UserModel; +use App\Services\Wechat\MiniProgramService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -16,11 +17,9 @@ use Modules\Common\Trait\RequestJson; class IndexController { use RequestJson; - // 权限验证白名单 - protected array $noPermission = ['index', 'login', 'register', 'mail']; /** 获取首页信息 */ - #[GetRoute('/index')] + #[GetRoute('/index', false)] public function index(): JsonResponse { $web_setting = site_config('web'); @@ -29,7 +28,7 @@ class IndexController } /** 用户登录 */ - #[PostRoute('/login')] + #[PostRoute('/login', false)] public function login(Request $request): JsonResponse { $credentials = $request->validate([ @@ -46,7 +45,7 @@ class IndexController } /** 用户注册 */ - #[PostRoute('/register')] + #[PostRoute('/register', false)] public function register(UserRegisterRequest $request): JsonResponse { $data = $request->validated(); @@ -60,4 +59,69 @@ class IndexController return $this->error('创建用户失败'); } + + /** + * 小程序登录 + * + * 仅需 code 完成 openid 换取,首次登录使用默认昵称和头像, + * 用户信息后续通过编辑接口完善。 + */ + #[PostRoute('/wx/login', false)] + public function loginMiniProgram(Request $request, MiniProgramService $miniProgram): JsonResponse + { + $validated = $request->validate([ + 'code' => 'required|string', + 'phoneCode' => 'nullable|string', + ]); + + // 1. 通过 code 换取 openid、session_key、unionid + $session = $miniProgram->codeToSession($validated['code']); + + // 2. 构建用户数据,昵称/头像/性别使用默认值 + $userData = [ + 'openid' => $session['openid'], + 'unionid' => $session['unionid'] ?? '', + 'username' => 'wx_'.uniqid(), + 'nickname' => '微信用户', + 'avatar' => '', + 'gender' => 0, + 'password' => '', + ]; + + // 3. 解密手机号 + if (! empty($validated['phoneCode'])) { + try { + $phoneInfo = $miniProgram->getPhoneNumber($validated['phoneCode']); + $userData['mobile'] = $phoneInfo['phone_info']['purePhoneNumber'] ?? ''; + } catch (\Throwable) { + // 手机号解密失败不阻塞登录 + } + } + + // 4. 查找或创建用户 + $user = UserModel::firstOrCreate( + ['openid' => $userData['openid']], + $userData + ); + + // 5. 更新已有用户缺失的手机号和 unionid + $needUpdate = false; + foreach (['mobile', 'unionid'] as $field) { + if (! empty($userData[$field]) && empty($user->$field)) { + $user->$field = $userData[$field]; + $needUpdate = true; + } + } + if ($needUpdate) { + $user->save(); + } + + // 6. 创建 Sanctum token + $token = $user->createToken($user->username)->toArray(); + + return $this->success([ + 'token' => $token['plainTextToken'], + 'user' => $user->toArray(), + ], __('user.login_success')); + } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 9edba34..cc303a9 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -21,48 +21,43 @@ class UserController #[GetRoute] public function getUserInfo(): JsonResponse { - $info = auth()->user(); - return $this->success(compact('info')); - } + $user = UserModel::query()->find(auth()->id()); - #[PostRoute('/logout')] - public function logout(): JsonResponse - { - $user_id = auth('users')->id(); - $model = new UserModel; - if ($model->logout($user_id)) { - return $this->success('退出登录成功'); - } else { - return $this->error($model->getErrorMsg()); + if (! $user) { + return $this->error('用户不存在'); } + + $data = $user->toArray(); + + // 手机号脱敏:13812345678 → 138****5678 + if (! empty($data['mobile'])) { + $data['mobile'] = substr_replace($data['mobile'], '****', 3, 4); + } + + // 邮箱脱敏:test@example.com → t***@example.com + if (! empty($data['email'])) { + $parts = explode('@', $data['email']); + $parts[0] = substr($parts[0], 0, 1).'***'; + $data['email'] = implode('@', $parts); + } + + return $this->success($data); } - #[PutRoute] - public function setUserInfo(UserUpdateInfoRequest $request): JsonResponse - { - UserModel::where('user_id', auth('user')->id())->update($request->validated()); - - return $this->error('更新成功'); - } - - #[PostRoute('/setPwd')] - public function setPassword(Request $request): JsonResponse + /** 编辑用户信息(头像、昵称、性别) */ + #[PutRoute('/profile')] + public function updateProfile(Request $request): JsonResponse { $data = $request->validate([ - 'oldPassword' => 'required|string|max:20', - 'newPassword' => 'required|string|min:6|max:20', - 'rePassword' => 'required|same:newPassword', + 'avatar' => 'required|string|max:500', + 'nickname' => 'required|string|max:32', + 'gender' => 'required|integer|in:0,1,2', ]); - $user_id = auth('user')->id(); - $user = UserModel::query()->find($user_id); - if (! password_verify($data['oldPassword'], $user['password'])) { - return $this->error('旧密码不正确!'); - } - $user->password = password_hash($data['newPassword'], PASSWORD_DEFAULT); - if ($user->save()) { - return $this->success('更新成功'); - } - return $this->error('更新失败'); + $user = UserModel::query()->find(auth()->id()); + + $user->update($data); + + return $this->success($user->toArray(), '更新成功'); } } diff --git a/app/Models/UserModel.php b/app/Models/UserModel.php index c278ccd..1da175b 100644 --- a/app/Models/UserModel.php +++ b/app/Models/UserModel.php @@ -28,5 +28,9 @@ class UserModel extends Authenticatable 'email', 'password', 'nickname', + 'openid', + 'unionid', + 'avatar', + 'gender' ]; } diff --git a/app/Services/Wechat/MiniProgramService.php b/app/Services/Wechat/MiniProgramService.php new file mode 100644 index 0000000..1910e89 --- /dev/null +++ b/app/Services/Wechat/MiniProgramService.php @@ -0,0 +1,72 @@ +app) { + $this->app = new Application(config('wechat.mini_program')); + } + + return $this->app; + } + + /** + * 获取 Utils 实例(code2Session、解密等) + */ + public function utils(): Utils + { + return $this->app()->getUtils(); + } + + /** + * 通过 code 换取 session + * + * @return array{openid: string, session_key: string, unionid?: string} + * + * @throws HttpException + */ + public function codeToSession(string $code): array + { + return $this->utils()->codeToSession($code); + } + + /** + * 解密加密数据(如用户信息、手机号) + * + * @return array + */ + public function decryptData(string $sessionKey, string $iv, string $ciphertext): array + { + return $this->utils()->decryptSession($sessionKey, $iv, $ciphertext); + } + + /** + * 获取用户手机号 + * + * @return array + * + * @throws HttpException + */ + public function getPhoneNumber(string $code): array + { + return $this->utils()->getPhoneNumber($code); + } +} diff --git a/composer.json b/composer.json index ace2d4d..3a725f8 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "laravel/framework": "^13.0", "laravel/sanctum": "^4.0", "laravel/tinker": "^3.0", - "predis/predis": "2.0" + "predis/predis": "2.0", + "w7corp/easywechat": "^6.19" }, "require-dev": { "laravel/boost": "^2.0", diff --git a/composer.lock b/composer.lock index ea945e0..fdb46ee 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5be97691e2441df20e2d2f3fdf9f3c6f", + "content-hash": "8a71916b54c097686f87d23c0fec8630", "packages": [ { "name": "aws/aws-crt-php", @@ -2887,6 +2887,221 @@ ], "time": "2026-02-16T23:10:27+00:00" }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "nyholm/psr7-server", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-11-08T09:30:43+00:00" + }, + { + "name": "overtrue/socialite", + "version": "4.14.2", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "1249c4c1c3611c81d2e17fd651d7a3e58cf70592" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/1249c4c1c3611c81d2e17fd651d7a3e58cf70592", + "reference": "1249c4c1c3611c81d2e17fd651d7a3e58cf70592", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^7.0", + "php": ">=8.0.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.0", + "laravel/pint": "^1.2", + "mockery/mockery": "^1.3", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "autoload": { + "files": [ + "src/Contracts/FactoryInterface.php", + "src/Contracts/UserInterface.php", + "src/Contracts/ProviderInterface.php" + ], + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "A collection of OAuth 2 packages.", + "keywords": [ + "Feishu", + "login", + "oauth", + "qcloud", + "qq", + "social", + "wechat", + "weibo" + ], + "support": { + "issues": "https://github.com/overtrue/socialite/issues", + "source": "https://github.com/overtrue/socialite/tree/4.14.2" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2026-05-29T10:56:53+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.5", @@ -3033,6 +3248,55 @@ ], "time": "2022-06-08T13:14:56+00:00" }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -3722,6 +3986,190 @@ }, "time": "2025-12-14T04:43:48+00:00" }, + { + "name": "symfony/cache", + "version": "v7.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "9adfcb2a7fc3924473b09f5a0b058dcc2cc7be9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/9adfcb2a7fc3924473b09f5a0b058dcc2cc7be9a", + "reference": "9adfcb2a7fc3924473b09f5a0b058dcc2cc7be9a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "ext-redis": "<6.1", + "ext-relay": "<0.12.1", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-17T14:44:48+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, { "name": "symfony/clock", "version": "v7.4.8", @@ -4423,6 +4871,189 @@ ], "time": "2026-03-24T13:12:05+00:00" }, + { + "name": "symfony/http-client", + "version": "v7.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "f6bc6b5a54ff5afac4725cacec9bf2f52eb15920" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/f6bc6b5a54ff5afac4725cacec9bf2f52eb15920", + "reference": "f6bc6b5a54ff5afac4725cacec9bf2f52eb15920", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-16T11:50:14+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, { "name": "symfony/http-foundation", "version": "v7.4.13", @@ -5219,6 +5850,86 @@ ], "time": "2026-05-26T12:51:13+00:00" }, + { + "name": "symfony/polyfill-php81", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:45:58+00:00" + }, { "name": "symfony/polyfill-php83", "version": "v1.38.1", @@ -5687,6 +6398,94 @@ ], "time": "2026-05-23T16:05:06+00:00" }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", + "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-24T13:12:05+00:00" + }, { "name": "symfony/routing", "version": "v7.4.13", @@ -6297,6 +7096,138 @@ ], "time": "2026-03-30T13:44:50+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v7.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "0118811b1d59f323bf131250b3fb919febfece28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0118811b1d59f323bf131250b3fb919febfece28", + "reference": "0118811b1d59f323bf131250b3fb919febfece28", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-27T08:41:53+00:00" + }, + { + "name": "thenorthmemory/xml", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/TheNorthMemory/xml.git", + "reference": "6f50c63450a0b098772423f8bdc3c4ad2c4c30bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheNorthMemory/xml/zipball/6f50c63450a0b098772423f8bdc3c4ad2c4c30bb", + "reference": "6f50c63450a0b098772423f8bdc3c4ad2c4c30bb", + "shasum": "" + }, + "require": { + "ext-libxml": "*", + "ext-simplexml": "*", + "php": ">=7.1.2" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.89 || ^1.0", + "phpunit/phpunit": "^7.5 || ^8.5.16 || ^9.3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "TheNorthMemory\\Xml\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "James ZHANG", + "homepage": "https://github.com/TheNorthMemory" + } + ], + "description": "A wrapper of the XML parser and builder", + "homepage": "https://github.com/TheNorthMemory/xml", + "keywords": [ + "xml-builder", + "xml-parser" + ], + "support": { + "issues": "https://github.com/TheNorthMemory/xml/issues", + "source": "https://github.com/TheNorthMemory/xml/tree/1.1.1" + }, + "time": "2023-01-15T06:01:13+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", @@ -6509,6 +7440,88 @@ } ], "time": "2026-04-26T05:33:54+00:00" + }, + { + "name": "w7corp/easywechat", + "version": "6.19.1", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "d7c72e310c36f310a3c0bfce3fed980a6079976f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/d7c72e310c36f310a3c0bfce3fed980a6079976f", + "reference": "d7c72e310c36f310a3c0bfce3fed980a6079976f", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-fileinfo": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "nyholm/psr7": "^1.5", + "nyholm/psr7-server": "^1.0", + "overtrue/socialite": "^3.5.4|^4.0.1", + "php": ">=8.0.2", + "psr/http-client": "^1.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/cache": "^5.4|^6.0|^7.0|^8.0", + "symfony/http-client": "^5.4|^6.0|^7.0|^8.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0|^8.0", + "symfony/mime": "^5.4|^6.0|^7.0|^8.0", + "symfony/polyfill-php81": "^1.25", + "symfony/psr-http-message-bridge": "^2.1.2|^6.4.0|^7.1|^8.0", + "thenorthmemory/xml": "^1.0" + }, + "conflict": { + "overtrue/wechat": "*" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.0", + "laravel/pint": "^1.2", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.4.4", + "phpstan/phpstan": "^1.0 | ^2", + "phpunit/phpunit": "^9.5", + "symfony/var-dumper": "^5.2|^6|^7|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "EasyWeChat\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "微信SDK", + "keywords": [ + "easywechat", + "sdk", + "wechat", + "weixin", + "weixin-sdk" + ], + "support": { + "issues": "https://github.com/w7corp/easywechat/issues", + "source": "https://github.com/w7corp/easywechat/tree/6.19.1" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2026-04-03T13:00:40+00:00" } ], "packages-dev": [ @@ -8859,7 +9872,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -8869,6 +9882,6 @@ "ext-pdo": "*", "ext-redis": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/config/wechat.php b/config/wechat.php new file mode 100644 index 0000000..cbd41a4 --- /dev/null +++ b/config/wechat.php @@ -0,0 +1,26 @@ + [ + 'app_id' => env('WECHAT_MINI_PROGRAM_APP_ID', ''), + 'secret' => env('WECHAT_MINI_PROGRAM_SECRET', ''), + 'token' => env('WECHAT_MINI_PROGRAM_TOKEN', ''), + 'aes_key' => env('WECHAT_MINI_PROGRAM_AES_KEY', ''), + + /** + * HTTP 客户端配置 + * 参考:https://symfony.com/doc/current/http_client.html + */ + 'http' => [ + 'throw' => false, // 是否直接抛出异常 + 'timeout' => 10.0, + 'retry' => true, // 失败重试 + 'max_retries' => 2, + ], + ], +]; diff --git a/routes/api.php b/routes/api.php index 9379d3d..1ea15de 100644 --- a/routes/api.php +++ b/routes/api.php @@ -8,9 +8,10 @@ Route::get('/', function () { // IndexController Route::controller(App\Http\Controllers\IndexController::class)->prefix('api')->group(function () { - Route::get('/index', 'index')->middleware(['auth:sanctum', 'authGuard:users']); - Route::post('/login', 'login')->middleware(['auth:sanctum', 'authGuard:users']); - Route::post('/register', 'register')->middleware(['auth:sanctum', 'authGuard:users']); + Route::get('/index', 'index'); + Route::post('/login', 'login'); + Route::post('/register', 'register'); + Route::post('/wx/login', 'loginMiniProgram'); }); // PaymentTransactionController @@ -22,9 +23,7 @@ Route::controller(App\Http\Controllers\PaymentTransactionController::class)->pre // UserController Route::controller(App\Http\Controllers\UserController::class)->prefix('api/user')->group(function () { Route::get('/', 'getUserInfo')->middleware(['auth:sanctum', 'authGuard:users']); - Route::post('/logout', 'logout')->middleware(['auth:sanctum', 'authGuard:users']); - Route::put('/', 'setUserInfo')->middleware(['auth:sanctum', 'authGuard:users']); - Route::post('/setPwd', 'setPassword')->middleware(['auth:sanctum', 'authGuard:users']); + Route::put('/profile', 'updateProfile')->middleware(['auth:sanctum', 'authGuard:users']); }); // ForumCategoryController