From 4e17620cbe7b235a1d075e443ccf5ed6b3171dc9 Mon Sep 17 00:00:00 2001 From: lewis <604446095@qq.com> Date: Tue, 22 Jul 2025 17:50:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7=E7=99=BB?= =?UTF-8?q?=E5=BD=95=EF=BC=8C=E8=B4=AD=E7=89=A9=E8=BD=A6=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ExceptionHandler.php | 17 +- app/api/controller/AuthController.php | 22 + app/api/controller/CartController.php | 53 + app/api/controller/DishesController.php | 47 + app/api/controller/OrderController.php | 10 + app/api/logic/CartLogic.php | 81 ++ app/api/logic/UserLogic.php | 33 + app/api/logic/WechatUserLogic.php | 68 ++ app/api/middleware/AuthMiddleware.php | 37 + app/api/validate/CartValidate.php | 79 ++ .../exception/UnauthorizedException.php | 20 + app/common/model/Cart.php | 23 + app/common/model/CartProduct.php | 24 + app/common/model/Dishes.php | 5 + app/common/model/user/WechatUser.php | 14 + composer.json | 4 +- composer.lock | 1032 ++++++++++++++++- config/middleware.php | 5 +- config/plugin/tinywan/jwt/app.php | 83 ++ support/Request.php | 14 +- support/auth/Weixin.php | 63 + 21 files changed, 1719 insertions(+), 15 deletions(-) create mode 100644 app/api/controller/AuthController.php create mode 100644 app/api/controller/CartController.php create mode 100644 app/api/controller/DishesController.php create mode 100644 app/api/controller/OrderController.php create mode 100644 app/api/logic/CartLogic.php create mode 100644 app/api/logic/UserLogic.php create mode 100644 app/api/logic/WechatUserLogic.php create mode 100644 app/api/middleware/AuthMiddleware.php create mode 100644 app/api/validate/CartValidate.php create mode 100644 app/common/exception/UnauthorizedException.php create mode 100644 app/common/model/Cart.php create mode 100644 app/common/model/CartProduct.php create mode 100644 app/common/model/user/WechatUser.php create mode 100644 config/plugin/tinywan/jwt/app.php create mode 100644 support/auth/Weixin.php diff --git a/app/ExceptionHandler.php b/app/ExceptionHandler.php index 1ac0cd7..6412fa9 100644 --- a/app/ExceptionHandler.php +++ b/app/ExceptionHandler.php @@ -2,7 +2,7 @@ namespace app; -use hg\apidoc\exception\ErrorException; +use app\common\exception\UnauthorizedException; use Next\VarDumper\Dumper; use Next\VarDumper\DumperHandler; use support\exception\BusinessException; @@ -20,11 +20,11 @@ class ExceptionHandler extends Handler { if ($exception instanceof Dumper) { return \response(self::convertToHtml($exception)); - }elseif ($exception instanceof BusinessException) { - if ($request->expectsJson()) { - return json(['code' => 0, 'msg' => $exception->getMessage(),'show'=>1]); - } - return response($exception->getMessage()); + } elseif ($exception instanceof UnauthorizedException) { + $json = json_encode(['code' => 401, 'msg' => $exception->getMessage()], JSON_UNESCAPED_UNICODE); + return new Response(401, ['Content-Type' => 'application/json'], $json); + } elseif ($exception instanceof BusinessException) { + return json(['code' => 0, 'msg' => $exception->getMessage(), 'show' => 1]); } elseif ($exception instanceof \Exception) { $isDebug = config('app.debug'); $error = [ @@ -36,9 +36,10 @@ class ExceptionHandler extends Handler $error['file'] = $exception->getFile(); $error['line'] = $exception->getLine(); } - return response(json_encode($error, JSON_UNESCAPED_UNICODE)); + $json = json_encode($error, JSON_UNESCAPED_UNICODE); + return new Response(200, ['Content-Type' => 'application/json'], $json); } // 非json请求则返回一个页面 - return new Response(200, [], 'msg:'.$exception->getMessage().'。line:'.$exception->getLine().'。file:'.$exception->getFile()); + return new Response(200, [], 'msg:' . $exception->getMessage() . '。line:' . $exception->getLine() . '。file:' . $exception->getFile()); } } diff --git a/app/api/controller/AuthController.php b/app/api/controller/AuthController.php new file mode 100644 index 0000000..964156b --- /dev/null +++ b/app/api/controller/AuthController.php @@ -0,0 +1,22 @@ +post('type', 'wechat'); + $code = $request->post('code', '', 1); + $token = $userLogic->login($type, $code); + return $this->data($token); + } + +} diff --git a/app/api/controller/CartController.php b/app/api/controller/CartController.php new file mode 100644 index 0000000..369f02e --- /dev/null +++ b/app/api/controller/CartController.php @@ -0,0 +1,53 @@ +request->get('keyword'); + $params['uid'] = $this->request->user->id; + $params['page'] = $this->request->page(); + $params['limit'] = $this->request->limit(); + $data = $cartLogic->list($params); + return $this->data($data); + } + + public function add(CartValidate $cartValidate, CartLogic $cartLogic) + { + $params = $this->request->post(); + $params['uid'] = $this->request->user->id; + $params = $cartValidate->goCheck('add', $params); + $cartLogic->add($params); + return $this->success('添加成功', [], 1, 1); + } + + public function delete(CartValidate $cartValidate, CartLogic $cartLogic) + { + $params = $cartValidate->post()->goCheck('delete'); + $params['uid'] = $this->request->user->id; + if ($cartLogic->delete($params)) { + return $this->success('删除成功', [], 1, 1); + } + return $this->fail('删除失败'); + } + + public function count(CartLogic $cartLogic) + { + $params['uid'] = $this->request->user->id; + $data = $cartLogic->count($params); + return $this->data(['count' => $data]); + } + + public function change() + { + + } + +} diff --git a/app/api/controller/DishesController.php b/app/api/controller/DishesController.php new file mode 100644 index 0000000..456de45 --- /dev/null +++ b/app/api/controller/DishesController.php @@ -0,0 +1,47 @@ +select()->toArray(); + $category = linear_to_tree($category, 'children'); + return $this->data($category); + } + + public function dishes() + { + $categoryId = $this->request->get('category_id'); + $keyword = $this->request->get('keyword'); + $query = Dishes::field('id,dishes_category_id,name,image,intro')->where('status', 1); + if (!empty($categoryId)) { + $query->where('dishes_category_id', $categoryId); + } + if (!empty($keyword)) { + $query->whereLike('name', "%$keyword%"); + } + $data = $query->select()->toArray(); + return $this->data($data); + } + + public function detail() + { + $id = $this->request->get('id'); + $data = Dishes::with(['dishesProduct' => function ($query) { + $query->with(['product', 'unit']); + }])->where('id', $id)->where('status', 1)->find(); + if (empty($data)) { + return $this->fail('菜品不存在'); + } + $data = $data->toArray(); + return $this->data($data); + } + +} diff --git a/app/api/controller/OrderController.php b/app/api/controller/OrderController.php new file mode 100644 index 0000000..a2eb094 --- /dev/null +++ b/app/api/controller/OrderController.php @@ -0,0 +1,10 @@ + function ($query) { + $query->with(['product', 'unit']); + }])->where('uid', $params['uid'])->where('paid', 0)->where('buy_now', 0); + if (!empty($params['keyword'])) { + $dishesIds = Dishes::whereLike('name', "%{$params['keyword']}%")->column('id'); + $query->whereIn('dishes_id', $dishesIds); + } + $data = $query->page($params['page'], $params['limit'])->select()->toArray(); + return $data; + } + + public function count($params) + { + return Cart::where('uid', $params['uid'])->where('paid', 0)->where('buy_now', 0)->count(); + } + + public function delete($params) + { + Db::startTrans(); + try { + CartProduct::destroy(['cart_id' => $params['id']]); + Cart::destroy(['id' => $params['id'], 'uid' => $params['uid']]); + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + return false; + } + } + + public function add($params) + { + Db::startTrans(); + try { + $cart = new Cart(); + if (!$cart->save($params)) { + throw new \Exception('添加购物车失败'); + } + $products = DishesProduct::where('dishes_id', $params['dishes_id'])->select()->toArray(); + $cartProductData = []; + $datetime = date('Y-m-d H:i:s'); + foreach ($products as $product) { + $cartProductData[] = [ + 'uid' => $params['uid'], + 'dishes_id' => $params['dishes_id'], + 'product_id' => $product['product_id'], + 'cart_id' => $cart->id, + 'nums' => $product['nums'], + 'unit_id' => $product['unit_id'], + 'create_time' => $datetime, + 'update_time' => $datetime, + ]; + } + if (!CartProduct::insertAll($cartProductData)) { + throw new \Exception('添加购物车商品失败'); + } + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + throw $e; + } + } + +} diff --git a/app/api/logic/UserLogic.php b/app/api/logic/UserLogic.php new file mode 100644 index 0000000..4e96565 --- /dev/null +++ b/app/api/logic/UserLogic.php @@ -0,0 +1,33 @@ + $user->id, + 'nickname' => $user->nickname, + 'avatar' => $user->avatar, + 'code' => $user->code, + ]; + $token = JwtToken::generateToken(['id' => $user->id]); + $token['expires_in'] = $token['expires_in'] + time(); + $token['refresh_token_expire'] = time() + config('plugin.tinywan.jwt.app.jwt.refresh_exp'); + $data['token'] = $token; + } else { + $wechatUserLogic = new WechatUserLogic(); + $data = $wechatUserLogic->findOrCreate($code); + } + return $data; + } + +} diff --git a/app/api/logic/WechatUserLogic.php b/app/api/logic/WechatUserLogic.php new file mode 100644 index 0000000..088dc33 --- /dev/null +++ b/app/api/logic/WechatUserLogic.php @@ -0,0 +1,68 @@ +appId(); + $res = $handler->login($code); + Db::startTrans(); + try { + $wechatUser = WechatUser::withTrashed()->where('app_id', $appId)->where('routine_openid', $res['openid'])->find(); + if (empty($wechatUser)) { + $wechatUser = new WechatUser(); + $wechatUser->app_id = $appId; + $wechatUser->unionid = $res['union_id'] ?? $res['unionid'] ?? ''; + $wechatUser->routine_openid = $res['openid']; + $wechatUser->nickname = !empty($this->nickname) ? $this->nickname : $handler->nickname(); + $wechatUser->headimgurl = $handler->avatar(); + if (!$wechatUser->save()) { + throw new \Exception('三方登录信息保存出错', 500); + } + } + $user = User::withTrashed()->where('wechat_user_id', $wechatUser->id)->field('id,nickname,avatar')->find(); + if (!empty($user['delete_time'])) { + throw new BusinessException('账号已注销', 500); + } + if (empty($user)) { + $user = new User(); + $user->wechat_user_id = $wechatUser->id; + $user->real_name = ''; + $user->nickname = $wechatUser->nickname; + $user->avatar = $wechatUser->headimgurl; + } + $user->last_time = date('Y-m-d H:i:s'); + if (!$user->save()) { + throw new \Exception('用户信息保存出错', 500); + } + Db::commit(); + } catch (\Exception $exception) { + Db::rollback(); + throw new \Exception($exception->getMessage(), $exception->getCode()); + } + $tokenParam = [ + 'id' => $user->id, + 'nickname' => $user->nickname, + 'avatar' => $user->avatar, + 'code' => $user->code, + ]; + $token = JwtToken::generateToken(['id' => $user->id]); + $token['expires_in'] = $token['expires_in'] + time(); + $token['refresh_token_expire'] = time() + config('plugin.tinywan.jwt.app.jwt.refresh_exp'); + $tokenParam['token'] = $token; + return $tokenParam; + } + +} diff --git a/app/api/middleware/AuthMiddleware.php b/app/api/middleware/AuthMiddleware.php new file mode 100644 index 0000000..5715234 --- /dev/null +++ b/app/api/middleware/AuthMiddleware.php @@ -0,0 +1,37 @@ +user = User::withTrashed()->find($payload['extend']['id']); + if (empty($request->user)) { + throw new UnauthorizedException('用户不存在', 401); + } + } catch (\Throwable $e) { + $controller = new $request->controller; + if ((!isset($controller->optional) || !in_array($request->action, $controller->optional))) { + throw new UnauthorizedException('请登录', 401); + } + } + + /** @var Response $response */ + $response = $handler($request); + return $response; + } +} diff --git a/app/api/validate/CartValidate.php b/app/api/validate/CartValidate.php new file mode 100644 index 0000000..0708856 --- /dev/null +++ b/app/api/validate/CartValidate.php @@ -0,0 +1,79 @@ + 'require', + 'uid' => 'require', + 'dishes_id' => 'require', +// 'project_id' => 'require', +// 'cart_num' => 'require', +// 'paid' => 'require', + 'buy_now' => 'require', + ]; + + + /** + * 参数描述 + * @var string[] + */ + protected $field = [ + 'id' => 'id', + 'uid' => '用户id', + 'dishes_id' => '菜品id', + 'project_id' => '项目id', + 'cart_num' => '购买数量', + 'paid' => '支付状态', + 'buy_now' => '立即购买', + ]; + + + /** + * @notes 添加场景 + * @return CartValidate + */ + public function sceneAdd() + { + return $this->remove('id', true); + } + + + /** + * @notes 编辑场景 + * @return CartValidate + */ + public function sceneEdit() + { + return $this->only(['id']); + } + + + /** + * @notes 删除场景 + * @return CartValidate + */ + public function sceneDelete() + { + return $this->only(['id']); + } + + + /** + * @notes 详情场景 + * @return CartValidate + */ + public function sceneDetail() + { + return $this->only(['id']); + } + +} diff --git a/app/common/exception/UnauthorizedException.php b/app/common/exception/UnauthorizedException.php new file mode 100644 index 0000000..3fdc70d --- /dev/null +++ b/app/common/exception/UnauthorizedException.php @@ -0,0 +1,20 @@ +response = json(json_decode($message,true)); + parent::__construct($message, $code, $previous); + } + + public function getResponse(){ + return $this->response; + } +} diff --git a/app/common/model/Cart.php b/app/common/model/Cart.php new file mode 100644 index 0000000..b8c3664 --- /dev/null +++ b/app/common/model/Cart.php @@ -0,0 +1,23 @@ +hasOne(Dishes::class, 'id', 'dishes_id')->bind(['dishes_name' => 'name', 'image']); + } + + public function cartProduct() + { + return $this->hasMany(CartProduct::class, 'cart_id', 'id'); + } + +} diff --git a/app/common/model/CartProduct.php b/app/common/model/CartProduct.php new file mode 100644 index 0000000..f17a0ef --- /dev/null +++ b/app/common/model/CartProduct.php @@ -0,0 +1,24 @@ +hasOne(Product::class, 'id', 'product_id')->bind(['product_name' => 'name', 'image', 'product_type']); + } + + public function unit() + { + return $this->hasOne(ProductUnit::class, 'id', 'unit_id')->bind(['unit_name' => 'name']); + } + +} diff --git a/app/common/model/Dishes.php b/app/common/model/Dishes.php index ed6e278..3217ecb 100644 --- a/app/common/model/Dishes.php +++ b/app/common/model/Dishes.php @@ -23,4 +23,9 @@ class Dishes extends BaseModel return $this->hasOne(DishesCategory::class, 'id', 'dishes_category_id'); } + public function dishesProduct() + { + return $this->hasMany(DishesProduct::class, 'dishes_id', 'id'); + } + } diff --git a/app/common/model/user/WechatUser.php b/app/common/model/user/WechatUser.php new file mode 100644 index 0000000..53a1277 --- /dev/null +++ b/app/common/model/user/WechatUser.php @@ -0,0 +1,14 @@ +=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.11.2", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "83dd537a88b30cd9204ee2c46a5b2e181bc1fa66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/83dd537a88b30cd9204ee2c46a5b2e181bc1fa66", + "reference": "83dd537a88b30cd9204ee2c46a5b2e181bc1fa66", + "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": "^1.7", + "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.11.2" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2024-10-08T16:23:14+00:00" + }, { "name": "php-di/invoker", "version": "2.3.4", @@ -2531,6 +2809,332 @@ "homepage": "https://symfony.com", "time": "2023-05-23T14:45:45+00:00" }, + { + "name": "symfony/http-client", + "version": "v6.2.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "297374a399ce6852d5905d92a1351df00bb9dd10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/297374a399ce6852d5905d92a1351df00bb9dd10", + "reference": "297374a399ce6852d5905d92a1351df00bb9dd10", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "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/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.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/v6.2.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-03T12:13:45+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "75d7043853a42837e68111812f4d964b01e5101c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-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.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-29T11:18:49+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.1.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "bd1bcfb0eba14de22c4d086c5023e608f37366ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bd1bcfb0eba14de22c4d086c5023e608f37366ed", + "reference": "bd1bcfb0eba14de22c4d086c5023e608f37366ed", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^5.4|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.1.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-30T15:43:30+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "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": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-19T08:51:26+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.28.0", @@ -2648,6 +3252,89 @@ ], "time": "2023-01-26T09:26:14+00:00" }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-10T14:38:51+00:00" + }, { "name": "symfony/polyfill-intl-normalizer", "version": "v1.28.0", @@ -2885,6 +3572,165 @@ ], "time": "2023-01-26T09:26:14+00:00" }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/c9cf83326a1074f83a738fc5320945abf7fb7fec", + "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.2" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.2|^7.0", + "symfony/http-kernel": "^6.2|^7.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/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, { "name": "symfony/service-contracts", "version": "v2.5.2", @@ -3283,6 +4129,104 @@ "description": "The webman Validate Package", "time": "2022-08-27T08:29:08+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": "tinywan/jwt", + "version": "v1.11.3", + "source": { + "type": "git", + "url": "https://github.com/Tinywan/webman-jwt.git", + "reference": "1b067c998d970c252b8ad113a460922f8108b9ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Tinywan/webman-jwt/zipball/1b067c998d970c252b8ad113a460922f8108b9ac", + "reference": "1b067c998d970c252b8ad113a460922f8108b9ac", + "shasum": "" + }, + "require": { + "ext-json": "*", + "firebase/php-jwt": "^6.8", + "php": "^7.1||^8.0", + "workerman/webman-framework": "^1.2.1||^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.6", + "illuminate/database": "^8.83", + "mockery/mockery": "^1.5", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.0", + "topthink/think-orm": "^2.0", + "vimeo/psalm": "^4.22", + "workerman/webman": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tinywan\\Jwt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "JSON Web Token (JWT) for webman plugin", + "support": { + "issues": "https://github.com/Tinywan/webman-jwt/issues", + "source": "https://github.com/Tinywan/webman-jwt/tree/v1.11.3" + }, + "time": "2025-04-10T12:05:48+00:00" + }, { "name": "tinywan/storage", "version": "v0.3.4", @@ -3540,6 +4484,88 @@ ], "time": "2022-03-08T17:03:00+00:00" }, + { + "name": "w7corp/easywechat", + "version": "6.17.5", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "ea6460149b20a0a31d614d18c5c8cbe77414c4ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/ea6460149b20a0a31d614d18c5c8cbe77414c4ca", + "reference": "ea6460149b20a0a31d614d18c5c8cbe77414c4ca", + "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", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/polyfill-php81": "^1.25", + "symfony/psr-http-message-bridge": "^2.1.2|^6.4.0|^7.1", + "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" + }, + "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.17.5" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2025-03-19T00:52:30+00:00" + }, { "name": "webman/console", "version": "v1.2.38", @@ -3766,13 +4792,13 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=7.2", "ext-json": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/middleware.php b/config/middleware.php index 78cbaee..364a9f6 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -22,5 +22,6 @@ return [ app\admin\middleware\LoginMiddleware::class, // 权限认证 app\admin\middleware\AuthMiddleware::class, - ] -]; \ No newline at end of file + ], + 'api' => [\app\api\middleware\AuthMiddleware::class], +]; diff --git a/config/plugin/tinywan/jwt/app.php b/config/plugin/tinywan/jwt/app.php new file mode 100644 index 0000000..735b24d --- /dev/null +++ b/config/plugin/tinywan/jwt/app.php @@ -0,0 +1,83 @@ + true, + 'jwt' => [ + /** 算法类型 HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384、PS512 */ + 'algorithms' => 'HS256', + + /** access令牌秘钥 */ + 'access_secret_key' => '2022d3d3LmJq', + + /** access令牌过期时间,单位:秒。默认 2 小时 */ + 'access_exp' => 86400 * 3, + + /** refresh令牌秘钥 */ + 'refresh_secret_key' => '2022KTxigxc9o50c', + + /** refresh令牌过期时间,单位:秒。默认 7 天 */ + 'refresh_exp' => 604800, + + /** refresh 令牌是否禁用,默认不禁用 false */ + 'refresh_disable' => false, + + /** 令牌签发者 */ + 'iss' => 'webman.tinywan.cn', + + /** 某个时间点后才能访问,单位秒。(如:30 表示当前时间30秒后才能使用) */ + 'nbf' => 0, + + /** 时钟偏差冗余时间,单位秒。建议这个余地应该不大于几分钟 */ + 'leeway' => 60, + + /** 是否允许单设备登录,默认不允许 false */ + 'is_single_device' => false, + + /** 缓存令牌时间,单位:秒。默认 7 天 */ + 'cache_token_ttl' => 604800, + + /** 缓存令牌前缀,默认 JWT:TOKEN: */ + 'cache_token_pre' => 'JWT:TOKEN:', + + /** 缓存刷新令牌前缀,默认 JWT:REFRESH_TOKEN: */ + 'cache_refresh_token_pre' => 'JWT:REFRESH_TOKEN:', + + /** 用户信息模型 */ + 'user_model' => function ($uid) { + return []; + }, + + /** 是否支持 get 请求获取令牌 */ + 'is_support_get_token' => false, + /** GET 请求获取令牌请求key */ + 'is_support_get_token_key' => 'authorization', + + /** access令牌私钥 */ + 'access_private_key' => << << << <<get('page', 1); + return intval($page); + } + + public function limit() + { + $limit = $this->get('limit', 10); + return intval(min($limit, 50)); + } + +} diff --git a/support/auth/Weixin.php b/support/auth/Weixin.php new file mode 100644 index 0000000..0de54d6 --- /dev/null +++ b/support/auth/Weixin.php @@ -0,0 +1,63 @@ +config = [ + 'app_id' => '', + 'secret' => '', + 'response_type' => 'array', + 'log' => [ + 'level' => 'debug', + 'file' => __DIR__ . '/wechat.log', + ], + ]; + $this->app = new Application($this->config); + } + + public function appId() + { + return $this->config['app_id']; + } + + public function nickname() + { + return '微信用户' . time() . rand(1, 100); + } + + public function avatar() + { + return '/image/avatar.jpg'; + } + + public function login($code) + { + $result = $this->app->getUtils()->codeToSession($code); + if (isset($result['openid'])) { + return $result; + } + throw new BusinessException('登录失败', 500); + } + + public function getPhoneNumber($code, $iv, $encryptedData) + { + $utils = $this->app->getUtils(); + $response = $utils->codeToSession($code); + $session = $utils->decryptSession($response['session_key'], $iv, $encryptedData); + if (empty($session['phoneNumber'])) { + throw new BusinessException('获取手机号失败', 500); + } + return $session['phoneNumber']; + } + +}