From 8cd02a001c7a8bc77369d7acf658c1eb11bf6c83 Mon Sep 17 00:00:00 2001 From: php_Team <> Date: Sun, 25 Jun 2023 08:51:24 +0800 Subject: [PATCH] Initial commit --- .gitignore | 9 + .travis.yml | 42 + LICENSE.txt | 32 + README.md | 56 + app/.htaccess | 1 + app/AppService.php | 22 + app/BaseController.php | 138 + app/BaseModel.php | 16 + app/BasePivot.php | 16 + app/ExceptionHandle.php | 133 + app/Request.php | 80 + app/admin/controller/ChatGpt/ChatGpt.php | 56 + app/admin/controller/Common.php | 82 + .../controller/Dictionary/Dictionary.php | 234 ++ app/admin/controller/Flow/Flow.php | 308 ++ app/admin/controller/Gen/GenApi/GenApi.php | 517 +++ .../Gen/GenApi/GenController/Admin.php | 122 + .../Gen/GenApi/GenController/Api.php | 49 + .../controller/Gen/GenApi/GenModel/Model.php | 209 ++ app/admin/controller/Gen/GenVue/GenDto.php | 223 ++ .../controller/Gen/GenVue/GenIndex/Index.php | 212 ++ app/admin/controller/Login.php | 145 + app/admin/controller/Menu/Menu.php | 368 ++ app/admin/controller/Role/Role.php | 198 ++ app/admin/controller/Tdk/Tdk.php | 150 + app/admin/controller/User/User.php | 371 ++ app/admin/controller/User/UserRole.php | 153 + app/admin/middleware/Auth.php | 84 + app/admin/route.php | 12 + app/api/controller/CommonApi/CommonApi.php | 32 + app/api/controller/Crawler/Crawler.php | 112 + app/api/controller/Crawler/CrawlerHoude.php | 129 + app/api/controller/Flow/Flow.php | 33 + app/api/controller/Login.php | 144 + app/api/controller/Tdk/Tdk.php | 107 + app/api/middleware/Auth.php | 85 + app/api/route.php | 12 + app/common.php | 178 + app/common/arw/adjfut/composer.json | 39 + app/common/arw/adjfut/src/ArrayFilter.php | 170 + app/common/arw/adjfut/src/Base64.php | 174 + app/common/arw/adjfut/src/ChunkUpload.php | 258 ++ .../arw/adjfut/src/Config/chunkUpload.php | 9 + app/common/arw/adjfut/src/Config/wechat.php | 39 + app/common/arw/adjfut/src/Curl.php | 790 +++++ app/common/arw/adjfut/src/Excel.php | 519 +++ .../arw/adjfut/src/Exception/ErrorMsg.php | 12 + app/common/arw/adjfut/src/Model/Pivot.php | 258 ++ app/common/arw/adjfut/src/PartitionTable.php | 261 ++ app/common/arw/adjfut/src/ReTry.php | 250 ++ .../arw/adjfut/src/Service/Validate.php | 68 + app/common/arw/adjfut/src/Tool.php | 196 ++ .../arw/adjfut/src/Traits/Dictionary.php | 223 ++ app/common/arw/adjfut/src/Traits/Event.php | 62 + app/common/arw/adjfut/src/Traverse.php | 240 ++ app/common/arw/adjfut/src/Unit/File.php | 86 + app/common/arw/adjfut/src/UploadFile.php | 289 ++ app/common/arw/adjfut/src/Validate.php | 254 ++ app/common/arw/adjfut/src/WeChat/Config.php | 123 + app/common/arw/adjfut/src/WeChat/Gzh.php | 348 ++ .../arw/adjfut/src/WeChat/GzhCommon.php | 459 +++ app/common/arw/adjfut/src/WeChat/Xcx.php | 232 ++ .../arw/adjfut/src/WeChat/XcxCommon.php | 279 ++ app/common/exception/Base64.php | 174 + app/common/exception/LoginTimeOut.php | 12 + app/common/exception/Map.php | 37 + app/common/exception/NotAuthApi.php | 12 + app/common/exception/RegularVerification.php | 49 + app/common/exception/Sort.php | 211 ++ app/common/exception/Tool.php | 539 +++ app/common/listener/DelayToken.php | 20 + app/common/logic/Login.php | 170 + app/common/model/Dictionary/Dictionary.php | 290 ++ app/common/model/Flow/Flow.php | 279 ++ app/common/model/Menu/Menu.php | 128 + app/common/model/Menu/MenuApi.php | 142 + app/common/model/Role/Role.php | 146 + app/common/model/Role/RoleMenu.php | 162 + app/common/model/Tdk/Tdk.php | 184 + app/common/model/Test/Test.php | 168 + app/common/model/Token.php | 337 ++ app/common/model/User/User.php | 548 +++ app/common/model/User/UserRole.php | 157 + app/common/traits/Auth.php | 110 + app/common/traits/Model.php | 254 ++ app/event.php | 20 + app/middleware.php | 10 + app/provider.php | 9 + app/resources/view/admin/controller.tpl | 83 + app/resources/view/admin/model.tpl | 70 + app/resources/view/api/controller.tpl | 57 + .../business/sort/sortAdminController.tpl | 113 + .../view/business/sort/sortModel.tpl | 74 + app/resources/view/business/webApi.tpl | 23 + .../view/business/webApiController.tpl | 32 + app/resources/view/business/webController.tpl | 44 + app/resources/view/business/webIndex.tpl | 90 + app/resources/view/jsVue/add.tpl | 105 + app/resources/view/jsVue/api.tpl | 56 + app/resources/view/jsVue/detail.tpl | 62 + app/resources/view/jsVue/edit.tpl | 104 + app/resources/view/jsVue/index.tpl | 158 + app/service.php | 9 + composer.json | 61 + composer.lock | 3102 +++++++++++++++++ config/app.php | 33 + config/cache.php | 29 + config/captcha.php | 46 + config/chunkUpload.php | 9 + config/console.php | 10 + config/cookie.php | 20 + config/database.php | 63 + config/filesystem.php | 37 + config/lang.php | 27 + config/log.php | 124 + config/middleware.php | 8 + config/route.php | 50 + config/session.php | 19 + config/wechat.php | 39 + package-lock.json | 6 + public/.htaccess | 8 + public/admin.php | 36 + public/api.php | 36 + .../excel/tdk/网站tdk导入模板 - 副本 (2).xlsx | Bin 0 -> 9952 bytes public/excel/tdk/网站tdk导入模板 - 副本.xlsx | Bin 0 -> 10209 bytes public/excel/tdk/网站tdk导入模板.xlsx | Bin 0 -> 13522 bytes public/favicon.ico | Bin 0 -> 1150 bytes public/robots.txt | 2 + public/router.php | 19 + public/sql/php_mb_v1.1.sql | 0 public/static/.gitignore | 0 think | 10 + 132 files changed, 19550 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 app/.htaccess create mode 100644 app/AppService.php create mode 100644 app/BaseController.php create mode 100644 app/BaseModel.php create mode 100644 app/BasePivot.php create mode 100644 app/ExceptionHandle.php create mode 100644 app/Request.php create mode 100644 app/admin/controller/ChatGpt/ChatGpt.php create mode 100644 app/admin/controller/Common.php create mode 100644 app/admin/controller/Dictionary/Dictionary.php create mode 100644 app/admin/controller/Flow/Flow.php create mode 100644 app/admin/controller/Gen/GenApi/GenApi.php create mode 100644 app/admin/controller/Gen/GenApi/GenController/Admin.php create mode 100644 app/admin/controller/Gen/GenApi/GenController/Api.php create mode 100644 app/admin/controller/Gen/GenApi/GenModel/Model.php create mode 100644 app/admin/controller/Gen/GenVue/GenDto.php create mode 100644 app/admin/controller/Gen/GenVue/GenIndex/Index.php create mode 100644 app/admin/controller/Login.php create mode 100644 app/admin/controller/Menu/Menu.php create mode 100644 app/admin/controller/Role/Role.php create mode 100644 app/admin/controller/Tdk/Tdk.php create mode 100644 app/admin/controller/User/User.php create mode 100644 app/admin/controller/User/UserRole.php create mode 100644 app/admin/middleware/Auth.php create mode 100644 app/admin/route.php create mode 100644 app/api/controller/CommonApi/CommonApi.php create mode 100644 app/api/controller/Crawler/Crawler.php create mode 100644 app/api/controller/Crawler/CrawlerHoude.php create mode 100644 app/api/controller/Flow/Flow.php create mode 100644 app/api/controller/Login.php create mode 100644 app/api/controller/Tdk/Tdk.php create mode 100644 app/api/middleware/Auth.php create mode 100644 app/api/route.php create mode 100644 app/common.php create mode 100644 app/common/arw/adjfut/composer.json create mode 100644 app/common/arw/adjfut/src/ArrayFilter.php create mode 100644 app/common/arw/adjfut/src/Base64.php create mode 100644 app/common/arw/adjfut/src/ChunkUpload.php create mode 100644 app/common/arw/adjfut/src/Config/chunkUpload.php create mode 100644 app/common/arw/adjfut/src/Config/wechat.php create mode 100644 app/common/arw/adjfut/src/Curl.php create mode 100644 app/common/arw/adjfut/src/Excel.php create mode 100644 app/common/arw/adjfut/src/Exception/ErrorMsg.php create mode 100644 app/common/arw/adjfut/src/Model/Pivot.php create mode 100644 app/common/arw/adjfut/src/PartitionTable.php create mode 100644 app/common/arw/adjfut/src/ReTry.php create mode 100644 app/common/arw/adjfut/src/Service/Validate.php create mode 100644 app/common/arw/adjfut/src/Tool.php create mode 100644 app/common/arw/adjfut/src/Traits/Dictionary.php create mode 100644 app/common/arw/adjfut/src/Traits/Event.php create mode 100644 app/common/arw/adjfut/src/Traverse.php create mode 100644 app/common/arw/adjfut/src/Unit/File.php create mode 100644 app/common/arw/adjfut/src/UploadFile.php create mode 100644 app/common/arw/adjfut/src/Validate.php create mode 100644 app/common/arw/adjfut/src/WeChat/Config.php create mode 100644 app/common/arw/adjfut/src/WeChat/Gzh.php create mode 100644 app/common/arw/adjfut/src/WeChat/GzhCommon.php create mode 100644 app/common/arw/adjfut/src/WeChat/Xcx.php create mode 100644 app/common/arw/adjfut/src/WeChat/XcxCommon.php create mode 100644 app/common/exception/Base64.php create mode 100644 app/common/exception/LoginTimeOut.php create mode 100644 app/common/exception/Map.php create mode 100644 app/common/exception/NotAuthApi.php create mode 100644 app/common/exception/RegularVerification.php create mode 100644 app/common/exception/Sort.php create mode 100644 app/common/exception/Tool.php create mode 100644 app/common/listener/DelayToken.php create mode 100644 app/common/logic/Login.php create mode 100644 app/common/model/Dictionary/Dictionary.php create mode 100644 app/common/model/Flow/Flow.php create mode 100644 app/common/model/Menu/Menu.php create mode 100644 app/common/model/Menu/MenuApi.php create mode 100644 app/common/model/Role/Role.php create mode 100644 app/common/model/Role/RoleMenu.php create mode 100644 app/common/model/Tdk/Tdk.php create mode 100644 app/common/model/Test/Test.php create mode 100644 app/common/model/Token.php create mode 100644 app/common/model/User/User.php create mode 100644 app/common/model/User/UserRole.php create mode 100644 app/common/traits/Auth.php create mode 100644 app/common/traits/Model.php create mode 100644 app/event.php create mode 100644 app/middleware.php create mode 100644 app/provider.php create mode 100644 app/resources/view/admin/controller.tpl create mode 100644 app/resources/view/admin/model.tpl create mode 100644 app/resources/view/api/controller.tpl create mode 100644 app/resources/view/business/sort/sortAdminController.tpl create mode 100644 app/resources/view/business/sort/sortModel.tpl create mode 100644 app/resources/view/business/webApi.tpl create mode 100644 app/resources/view/business/webApiController.tpl create mode 100644 app/resources/view/business/webController.tpl create mode 100644 app/resources/view/business/webIndex.tpl create mode 100644 app/resources/view/jsVue/add.tpl create mode 100644 app/resources/view/jsVue/api.tpl create mode 100644 app/resources/view/jsVue/detail.tpl create mode 100644 app/resources/view/jsVue/edit.tpl create mode 100644 app/resources/view/jsVue/index.tpl create mode 100644 app/service.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config/app.php create mode 100644 config/cache.php create mode 100644 config/captcha.php create mode 100644 config/chunkUpload.php create mode 100644 config/console.php create mode 100644 config/cookie.php create mode 100644 config/database.php create mode 100644 config/filesystem.php create mode 100644 config/lang.php create mode 100644 config/log.php create mode 100644 config/middleware.php create mode 100644 config/route.php create mode 100644 config/session.php create mode 100644 config/wechat.php create mode 100644 package-lock.json create mode 100644 public/.htaccess create mode 100644 public/admin.php create mode 100644 public/api.php create mode 100644 public/excel/tdk/网站tdk导入模板 - 副本 (2).xlsx create mode 100644 public/excel/tdk/网站tdk导入模板 - 副本.xlsx create mode 100644 public/excel/tdk/网站tdk导入模板.xlsx create mode 100644 public/favicon.ico create mode 100644 public/robots.txt create mode 100644 public/router.php create mode 100644 public/sql/php_mb_v1.1.sql create mode 100644 public/static/.gitignore create mode 100644 think diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3dd1dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.idea +/.vscode +/vendor +/public/uploads/** +*.log +.env +*.http +runtime/* +app/admin/controller/Gen/Gen.php diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36f7b6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..574a39c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2929dad --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +ThinkPHP 6.0 +=============== + +> 运行环境要求PHP7.1+,兼容PHP8.0。 + +[官方应用服务市场](https://market.topthink.com) | [`ThinkAPI`——官方统一API服务](https://docs.topthink.com/think-api) + +ThinkPHPV6.0版本由[亿速云](https://www.yisu.com/)独家赞助发布。 + +## 主要新特性 + +* 采用`PHP7`强类型(严格模式) +* 支持更多的`PSR`规范 +* 原生多应用支持 +* 更强大和易用的查询 +* 全新的事件系统 +* 模型事件和数据库事件统一纳入事件系统 +* 模板引擎分离出核心 +* 内部功能中间件化 +* SESSION/Cookie机制改进 +* 对Swoole以及协程支持改进 +* 对IDE更加友好 +* 统一和精简大量用法 + +## 安装 + +~~~ +composer create-project topthink/think tp 6.0.* +~~~ + +如果需要更新框架使用 +~~~ +composer update topthink/framework +~~~ + +## 文档 + +[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content) + +## 参与开发 + +请参阅 [ThinkPHP 核心框架包](https://github.com/top-think/framework)。 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2020 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/app/AppService.php b/app/AppService.php new file mode 100644 index 0000000..96556e8 --- /dev/null +++ b/app/AppService.php @@ -0,0 +1,22 @@ +app = $app; + $this->request = $this->app->request; + + // 控制器初始化 + $this->initialize(); + } + + // 初始化 + protected function initialize() + { + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array|string|true + * @throws ValidateException + */ + protected function validate(array $data, $validate, array $message = [], bool $batch = true) + { + if (is_array($validate)) { + $v = new Validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + $v = new $class(); + if (!empty($scene)) { + $v->scene($scene); + } + } + + $v->message($message); + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + return $v->failException(true)->check($data); + } + + /** + * 开启事务 + * + * @param callable $cb + * @param array $args + * @return mixed + * @date 2022-07-22 + * @example + * @author admin + * @since 1.0.0 + */ + protected static function transaction(callable $cb, array $args = []) + { + Db::startTrans(); + try { + $result = invoke($cb, $args); + Db::commit(); + return $result; + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + /** + * 分页包装器 + * + * @param $query + * @date 2022-03-03 + * @example + * @author admin + * @since 1.0.0 + */ + protected static function pageWrapper($query) + { + return (clone $query)->page( + (int) Request::param('page', 1), + (int) Request::param('limit', 10) + ); + } +} diff --git a/app/BaseModel.php b/app/BaseModel.php new file mode 100644 index 0000000..ce65c75 --- /dev/null +++ b/app/BaseModel.php @@ -0,0 +1,16 @@ +respone([ + 'code' => 40050, + 'msg' => $e->getMessage(), + ]); + } + // 登录超时 + if ($e instanceof LoginTimeOut) { + return $this->respone([ + 'code' => 40026, + 'msg' => $request->isDev() ? $e->getMessage() : '登录超时', + ]); + } + // 参数验证不通过 + if ($e instanceof ValidateException) { + return $this->respone([ + 'code' => 40030, + 'msg' => $e->getMessage() + ]); + } + // 业务异常 + if ($e instanceof ErrorMsg || $e instanceof ExceptionErrorMsg) { + $code = $e->getCode(); + return $this->respone([ + 'code' => $code == 0 ? 40052 : $code, + 'msg' => $e->getMessage(), + ], $e); + } + // 服务器异常 + // if ($e instanceof Exception) { + // return $this->respone([ + // 'code' => 40051, + // 'msg' => '服务器异常', + // ], $e); + // } + // 其他错误交给系统处理 + return parent::render($request, $e); + } + + /** + * 返回 + * + * @param array $data + * @param Throwable $e + * @return Response + * @date 2022-04-15 + * @example + * @author admin + * @since 1.0.0 + */ + private function respone(array $data, $e = null): Response + { + $request = $this->app->request; + if ($e && Env::get('app_show_error')) { + $data['__error'] = [ + 'File' => $e->getFile(), + 'Line' => $e->getLine(), + 'Message' => $e->getMessage(), + 'Trace' => $e->getTrace() + ]; + $data['__param'] = $request->param(); + } + if ($request->isAjax()) { + return Response::create($data, 'json'); + } else { + return Response::create(json_encode($data), 'html'); + } + } +} diff --git a/app/Request.php b/app/Request.php new file mode 100644 index 0000000..dc5103c --- /dev/null +++ b/app/Request.php @@ -0,0 +1,80 @@ + 'https://api.openai.com/v1/completions', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $api_key + ], + CURLOPT_POSTFIELDS => json_encode([ + 'prompt' => $message, //你的内容 + 'model' => 'text-davinci-003', //机器人3代 + 'max_tokens' => 4000, //最大字符串 + 'temperature' => 0.5 //回答精准度 + ]) + ]); + + // 请求结束 + $domain = curl_exec($curl); + curl_close($curl); + //处理请求的数据 + $domain_array = json_decode($domain, true); + $data = $domain_array['choices'][0]['text']; //该数组输出最后回答的内容F + + return [ + 'code' => 0, + 'data' => $data + ]; + } +} diff --git a/app/admin/controller/Common.php b/app/admin/controller/Common.php new file mode 100644 index 0000000..82feb19 --- /dev/null +++ b/app/admin/controller/Common.php @@ -0,0 +1,82 @@ +param('dirName'); + + $upload = new UploadFile('uploads', 'file'); + $path = $upload->putFile($dirName . 'Img'); + $url = "/uploads/" . $path; + + //图片大小>500k,压缩图片质量 + $absolute_path = public_path() . $url; + if (ceil(filesize($absolute_path) / 1000) > 500) { + $image = \think\Image::open($absolute_path); + $image->save($absolute_path, null, 80); + } + + return [ + 'code' => 0, + 'data' => [ + "name" => $path, + "url" => $url, + ], + 'msg' => '上传成功!' + ]; + } + + /** + * 上传文件 + */ + public function uploadFile(Request $request) + { + $dirName = $request->param('dirName'); + + $upload = new UploadFile('uploads', 'file'); + $path = $upload->putFile($dirName . 'File'); + return [ + 'code' => 0, + 'data' => [ + "name" => $path, + "url" => "/uploads/" . $path, + ], + 'msg' => '上传成功!' + ]; + } + + /** + * 上传视频 + */ + public function uploadVideo(Request $request) + { + $dirName = $request->param('dirName'); + $upload = new UploadFile('uploads', 'file'); + $path = $upload->putFile($dirName . 'File'); + return [ + 'code' => 0, + 'data' => [ + "name" => $path, + "url" => "/uploads/" . $path, + ], + 'msg' => '上传成功!' + ]; + } +} diff --git a/app/admin/controller/Dictionary/Dictionary.php b/app/admin/controller/Dictionary/Dictionary.php new file mode 100644 index 0000000..3a6533c --- /dev/null +++ b/app/admin/controller/Dictionary/Dictionary.php @@ -0,0 +1,234 @@ +order([ + 'dictionary_index', + 'dictionary_order' => 'desc', + ])->select(); + return [ + 'code' => 0, + 'msg' => 'ok', + 'data' => $select + ]; + } + + /** + * 获取菜单树 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function getDictionaryTree(Request $request) + { + $dictionary = ModelDictionary::withoutGlobalScope(['status'])->order([ + 'dictionary_index', + 'dictionary_order' => 'desc', + ])->field([ + 'dictionary_parent_guid', + 'dictionary_name', + 'dictionary_index', + 'dictionary_value', + 'dictionary_order', + 'dictionary_status', + 'dictionary_allow_update', + 'dictionary_list_class', + 'dictionary_guid', + ])->select()->toArray(); + + array_unshift($dictionary, [ + 'dictionary_guid' => '0', + 'dictionary_parent_guid' => '-1', + 'dictionary_name' => '系统字典', + 'dictionary_index' => 0, + 'dictionary_value' => '', + 'dictionary_order' => 0, + 'dictionary_status' => 0, + 'dictionary_allow_update' => 2, + 'dictionary_list_class' => '', + ]); + + $Traverse = new Traverse('dictionary_guid', 'dictionary_parent_guid'); + $dictionaryTree = $Traverse->tree($dictionary, '-1', function ($v) { + return [ + 'label' => $v['dictionary_name'], + 'id' => $v['dictionary_guid'], + 'parent_id' => $v['dictionary_parent_guid'], + 'index' => $v['dictionary_index'], + 'order' => $v['dictionary_order'], + 'status' => $v['dictionary_status'], + 'allow' => $v['dictionary_allow_update'], + 'value' => $v['dictionary_value'], + 'list_class' => $v['dictionary_list_class'] + ]; + }); + + return [ + 'code' => 0, + 'msg' => 'ok', + 'data' => $dictionaryTree + ]; + } + + /** + * 添加字典 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function addDictionary(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'dictionary_name' => 'require', + 'dictionary_index' => 'require', + 'dictionary_order' => 'require', + 'dictionary_parent_guid' => 'require', + ]); + if (isset($params['dictionary_guid'])) { + unset($params['dictionary_guid']); + } + ModelDictionary::create($params); + return [ + 'code' => 0, + 'msg' => '添加成功' + ]; + } + + /** + * 更新部门 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function updateDictionary(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'dictionary_guid' => 'require' + ]); + ModelDictionary::withoutGlobalScope(['status'])->update($params, [ + 'dictionary_guid' => $params['dictionary_guid'] + ]); + return [ + 'code' => 0, + 'msg' => '更新成功' + ]; + } + + /** + * 删除菜单 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function deleteDictionary(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'dictionary_guid' => 'require' + ]); + ModelDictionary::find($params['dictionary_guid'])->delete(); + return [ + 'code' => 0, + 'msg' => '删除成功' + ]; + } + + // 获取字典 + public function getDictionary(Request $request) + { + $dictionary_value = $request->param('dictionary_value'); + + $dictionary_guid = ModelDictionary::where('dictionary_value', $dictionary_value)->find()['dictionary_guid']; + $res = ModelDictionary::field([ + 'dictionary_guid', + 'dictionary_parent_guid', + 'dictionary_name', + 'dictionary_value', + 'dictionary_list_class', + ])->where('dictionary_parent_guid', $dictionary_guid) + ->where('dictionary_status', 1)->select(); + if (!$res) throwErrorMsg("字典不存在"); + else return $res; + } + + + /** + * 下载导入模板 + */ + public function downloadTemplate(Request $request) + { + $params = $request->param(); + $data = [ + [ + '上级字典', + '名称', + '值', + '层级', + '排序', + '回显', + ] + ]; + + $data[] = ['','状态','status','1','0','',]; + $data[] = ['状态','启用','1','2','1','primary']; + $data[] = ['状态','禁用','2','2','2','danger',]; + + $excel = (new Excel())->exporTsheet($data); + $excel->save('字典导入模板.xlsx'); + } + + /** + * 导入excel + */ + public function importExcel(Request $request) + { + $file = new UploadFile('uploads', 'fileExt:xlsx'); + $file->putFile('Dictionary'); + + $msg = ModelDictionary::importExcel($file); + + return [ + 'code' => 0, + 'msg' => $msg + ]; + } +} diff --git a/app/admin/controller/Flow/Flow.php b/app/admin/controller/Flow/Flow.php new file mode 100644 index 0000000..6ad861d --- /dev/null +++ b/app/admin/controller/Flow/Flow.php @@ -0,0 +1,308 @@ +track(); + + /** + * 查询流量访问记录 + * @throws \think\db\exception\DbException + */ + public function getFlowRecord(Request $request): array + { + $query = ModelFlow::where([]); + $select = self::pageWrapper($query) + ->field([ + 'flow_id', + 'flow_record_no', + 'flow_visitor_ip', + 'flow_location', + 'flow_create_time', + 'flow_source', + 'flow_target', + 'flow_os', + 'flow_browser' + ]) + ->order('flow_id', 'desc') + ->select(); + + $count = $query->count(); + return [ + 'msg' => '查询成功', + 'code' => 0, + 'data' => $select, + 'count' => $count + ]; + } + + /** + * 查询流量来源统计 + */ + public function getFlowSourceCount(): array + { + $query = ModelFlow::where([]); + $count = $query->count(); + $select = $query->field([ + 'flow_id', + 'flow_source', + 'flow_target', + 'flow_os', + ])->select(); + $GDP = $query->field([ + + 'flow_source', + ])->group('flow_source')->select(); + foreach ($GDP as $k => &$i) { + $i['number'] = 0; + $i['percentage'] = 0; + } + foreach ($select as $key => &$item) { + foreach ($GDP as $k => &$i) { + if ($item['flow_source'] === $i['flow_source']) { + $i['number'] += 1; + } + } + } + foreach ($GDP as $k => $i) { + $i['percentage'] = round($i['number'] / $count * 100); + } + return [ + 'msg' => '查询完成', + 'code' => 0, + 'count' => $count, + 'data' => [ + 'GDP' => $GDP, + 'select' => $select + ] + ]; + } + + /** + * 查询流量浏览器统计 + */ + public function getFlowBrowserCount(): array + { + $query = ModelFlow::where([]); + $count = $query->count(); + $select = $query->field([ + 'flow_id', + 'flow_browser', + ])->select(); + $GDP = $query->field([ + + 'flow_browser', + ])->group('flow_browser')->select(); + foreach ($GDP as $k => &$i) { + $i['number'] = 0; + $i['percentage'] = 0; + } + foreach ($select as $key => &$item) { + foreach ($GDP as $k => &$i) { + if ($item['flow_browser'] === $i['flow_browser']) { + $i['number'] += 1; + } + } + } + foreach ($GDP as $k => $i) { + $i['percentage'] = round($i['number'] / $count * 100); + } + return [ + 'msg' => '查询完成', + 'code' => 0, + 'count' => $count, + 'data' => [ + 'select' => $select, + 'GDP' => $GDP + ] + ]; + } + + /** + * 查询流量月统计 + */ + public function getFlowMonthCount(Request $request): array + { + $current_year = input('post.current_year') ?? 0; + $query = ModelFlow::where([]); + $current_year = date('Y', strtotime("$current_year year")); + $count = $query->whereYear('flow_create_time', $current_year) + ->field([ + 'flow_create_time', + ]) + ->select()->count(); + $select = $query->whereYear('flow_create_time', $current_year) + ->field([ + 'flow_id', + 'flow_record_no', + 'flow_visitor_ip', + 'flow_location', + 'flow_create_time', + 'flow_source', + 'flow_target', + 'flow_os', + 'flow_browser' + ]) + ->order('flow_id', 'desc') + ->select()->toArray(); + $res_select = []; + for ($i = 1; $i <= 12; $i++) { + array_push($res_select, [ + 'flow_Date' => $current_year . + '-' . + date('m', strtotime($current_year . '-' . $i)), + 'number' => 0, + 'percentage' => 0 + ]); + } + foreach ($select as $key => &$item) { + $date_format = explode('-', explode(' ', $item['flow_create_time'])[0]); + + $item['flow_Date'] = $date_format[0] . '-' . $date_format[1]; + foreach ($res_select as $k => &$it) { + if ($item['flow_Date'] == $it['flow_Date']) { + $it['number'] += 1; + } + } + foreach ($res_select as $k => &$it) { + $it['percentage'] = round($it['number'] / $count * 100); + } + }; + foreach ($res_select as $k => &$it) { + foreach ($select as $key => &$item) { + if ($it['flow_Date'] === $item['flow_Date']) { + $it = array_merge($it, $item); + } + } + } + return [ + 'code' => 0, + 'msg' => '查询成功', + 'count' => $count, + 'data' => [ + 'current_date' => ['y' => $current_year], + 'select' => $res_select + ] + ]; + } + + /** + * 查询流量日统计 + */ + public function getFlowDayCount(Request $request) + { + $current_month = input('post.current_month') ?? 0; + // return ['a'=>$current_month]; + $query = ModelFlow::where([]); + $current_month_date = date('Y-m', strtotime(date('Y-m-01') . "$current_month month")); + // return ['a'=>$current_month_date]; + $count = $query->whereMonth('flow_create_time', $current_month_date) + ->field([ + 'flow_create_time', + ]) + ->select()->count(); + // $select = $query->whereMonth('flow_create_time', $current_month_date) + // ->field([ + // 'flow_create_time', + // ]) + // ->order('flow_id', 'desc') + // ->select()->toArray(); + $select = $query->whereMonth('flow_create_time', $current_month_date) + ->field([ + 'flow_create_time', + ]) + ->buildSql(); + return $select; + $GDP = []; + $res_select = []; + for ($i = 1; $i <= date('t', strtotime(date('Y-m-01') . "$current_month month")); $i++) { + + array_push($res_select, [ + 'week' => $this->getWeek(date('Y-m', strtotime(date('Y-m-01') . "$current_month month")) + . '-' + . date('d', strtotime(date('Y-m', strtotime(date('Y-m-01') . "$current_month month")) . "-$i"))), + 'flow_Date' => date('Y-m', strtotime(date('Y-m-01') . "$current_month month")) + . '-' + . date('d', strtotime(date('Y-m', strtotime(date('Y-m-01') . "$current_month month")) . "-$i")), + 'number' => 0, 'percentage' => 0 + ]); + } + foreach ($select as $key => &$item) { + + $item['flow_Date'] = explode(' ', $item['flow_create_time'])[0]; + foreach ($res_select as $k => &$it) { + if ($item['flow_Date'] == $it['flow_Date']) { + $it['number'] += 1; + } + } + foreach ($res_select as $k => &$it) { + $it['percentage'] = round($it['number'] / $count * 100); + } + }; + foreach ($res_select as $k => &$it) { + foreach ($select as $key => &$item) { + if ($it['flow_Date'] === $item['flow_Date']) { + $it = array_merge($it, $item); + } + } + } + return [ + 'code' => 0, + 'msg' => '查询成功', + 'count' => $count, + 'data' => [ + 'current_date' => ['y' => date('Y', strtotime(date('Y-m-01') . "$current_month month")), 'm' => date('m', strtotime(date('Y-m-01') . "$current_month month"))], + 'select' => $res_select + ] + ]; + } + + /** + * @param $date + * @return string + * @descript 获取指定日期星期 + */ + private function getWeek($date): string + { + //强制转换日期格式 + $date_str = date('Y-m-d', strtotime($date)); + + //封装成数组 + $arr = explode("-", $date_str); + + //参数赋值 + //年 + $year = $arr[0]; + + //月,输出2位整型,不够2位右对齐 + $month = sprintf('%02d', $arr[1]); + + //日,输出2位整型,不够2位右对齐 + $day = sprintf('%02d', $arr[2]); + + //时分秒默认赋值为0; + $hour = $minute = $second = 0; + + //转换成时间戳 + $strap = mktime($hour, $minute, $second, $month, $day, $year); + + //获取数字型星期几 + $number_wk = date("w", $strap); + + //自定义星期数组 + $weekArr = array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"); + + //获取数字对应的星期 + return $weekArr[$number_wk]; + } +} diff --git a/app/admin/controller/Gen/GenApi/GenApi.php b/app/admin/controller/Gen/GenApi/GenApi.php new file mode 100644 index 0000000..5bb82c6 --- /dev/null +++ b/app/admin/controller/Gen/GenApi/GenApi.php @@ -0,0 +1,517 @@ + [ //单表(增删改查) + 'api' => 'resources/view/api/controller.tpl', + 'admin' => 'resources/view/admin/controller.tpl', + 'model' => 'resources/view/admin/model.tpl', + ], + 'crud_sort' => [ //单表(增删改查+排序处理) + 'api' => 'resources/view/api/controller.tpl', + 'admin' => 'resources/view/business/sort/sortAdminController.tpl', + 'model' => 'resources/view/business/sort/sortModel.tpl', + ], + 'web_static' => [ //门户静态 + 'api' => 'resources/view/business/webApiController.tpl', + 'admin' => 'resources/view/business/webController.tpl', + 'model' => 'resources/view/admin/model.tpl', + ], + ]; + + /** + * 模型层模板读取路径 + * @var string + */ + protected $model_temp_path; + + /** + * 模型层模块生成路径 + * @var string + */ + protected $model_module_path; + + /** + * 模型层生成路径 + * @var string + */ + protected $model_path; + + /** + * 后台控制器模板读取路径 + * @var string + */ + protected $admin_con_temp_path; + + /** + * 后台控制器模块生成路径 + * @var string + */ + protected $admin_con_module_path; + + /** + * 后台控制器生成路径 + * @var string + */ + protected $admin_con_path; + + /** + * 前台控制器模板读取路径 + * @var string + */ + protected $api_con_temp_path; + + /** + * 前台控制器生成路径 + * @var string + */ + protected $api_con_path; + + /** + * 前台控制器模块生成路径 + * @var string + */ + protected $api_con_module_path; + + /** + * 新增功能必要字段 + * @var array + */ + protected $add_required_fields = ['guid', 'create_user_guid', 'update_user_guid']; + + /** + * 新增允许字段模板字符串 + * @var string 例:['user_name','user_id',...] + */ + protected $add_fields_temp; + + /** + * 新增必填验证模板字符串 + * @var string 例: ['user_name|用户名称' => 'require',...] + */ + protected $add_required_verify_temp; + + /** + * 修改功能必要字段 + * @var array + */ + protected $edit_required_fields = ['update_user_guid']; + + /** + * 编辑允许字段模板字符串 + * @var string 例:['user_name','user_id',...] + */ + protected $edit_fields_temp; + + /** + * 编辑必填验证模板字符串 + * @var string 例: ['user_name|用户名称' => 'require',...] + */ + protected $edit_required_verify_temp; + + /** + * 业务字段 + * @var array 例:[ 'user_name' => '用户名称',...] + */ + protected $business_fields = []; + + /** + * 是否导入 + * @var bool + */ + protected $is_import = false; + + /** + * 是否导出 + * @var bool + */ + protected $is_export = false; + + /** + * 是否排序处理 + * @var bool + */ + protected $is_sort = false; + + /** + * 图片字段数组 + * @var array + */ + protected $img_fields = []; + + /** + * 生成后端模板类型 + * @var string crud:: 单表(增删改查) | web_static: 门户静态模块(查询修改) + */ + protected $gen_api_type; + + /** + * 查询列表显示字段 + * @var array 例:['user_name','user_id',...] + */ + protected $query_list_fields = []; + + /** + * 排序字段 + * @var string + */ + protected $sort_field; + + /** + * 排序类型 + * @var string + */ + protected $sort_type; + + /** + * 查询列表条件模板字符串 + * @var string 例子:Tool::getOptionalQuery(['user_name', 'LIKE']) + */ + protected $query_list_where_temp; + + /** + * 构造器 + * + * @param array $fields 字段信息 + * @param array $table 生成信息 + */ + public function __construct(array $fields, array $table) + { + //基本参数初始化 + $this->root = base_path(); + $this->fields = $fields; + $this->table = $table; + $this->class_name = $this->table['className']; + $this->module_name = $this->table['moduleName']; + $this->business_name = $this->table['businessName']; + $this->function_name = $this->table['functionName']; + //后端生成通用数据初始化 + $this->initBusinessFields(); + $this->initRequiredFields('add_required_fields'); + $this->initRequiredFields('edit_required_fields'); + $this->initImportExportStatus(); + $this->initImgFields(); + $this->buildAddFieldsTemp(); + $this->buildEditFieldsTemp(); + $this->initGenApiType(); + $this->initQueryListDisplayFields(); + $this->initSortInfo(); + $this->initRequiredVerifyFieldsTemp(); + $this->buildQueryWhereContentTemp(); + $this->initIsSortStatus(); + //后端生成所需路径初始化 + $this->initModelGenPath(); + $this->initAdminControllerGenPath(); + $this->initApiControllerGenPath(); + } + + /** + * 构建查询列表条件模板字符串 + */ + public function buildQueryWhereContentTemp(): void + { + $where_content_arr = []; + foreach ($this->fields as $val) { + if ($val['isQuery']) { + $where_content_arr[] = [$val['columnName'], $val['queryType']]; + } + }; + $this->query_list_where_temp = self::toFormTempStr($where_content_arr, 3); + } + + /** + * 初始化排序信息 + */ + private function initSortInfo(): void + { + $this->sort_field = $this->table['options']->SortField ?? "{$this->business_name}_update_time"; + $this->sort_type = $this->table['options']->SortType ?? 'desc'; + } + + /** + * 初始化查询列表可显字段 + */ + private function initQueryListDisplayFields(): void + { + foreach ($this->fields as $val) { + if ($val['isList']) { + $this->query_list_fields[] = $val['columnName']; + } + }; + } + + /** + * 初始化后端模板类型 + */ + private function initGenApiType(): void + { + $this->gen_api_type = $this->table['tplCategory']; + } + + /** + * 初始化模型层生成所需路径 + */ + private function initModelGenPath(): void + { + $this->model_temp_path = self::initFilePath($this->getTempPath('model')); + $this->model_module_path = self::initFilePath("{$this->root}common/model/{$this->module_name}"); + $this->model_path = self::initFilePath("{$this->model_module_path}/{$this->class_name}.php"); + } + + /** + * 初始化后台控制器生成所需路径 + */ + private function initAdminControllerGenPath(): void + { + $this->admin_con_temp_path = self::initFilePath($this->getTempPath('admin')); + $this->admin_con_module_path = self::initFilePath("{$this->root}admin/controller/{$this->module_name}"); + $this->admin_con_path = self::initFilePath("{$this->admin_con_module_path}/{$this->class_name}.php"); + } + + /** + * 初始化前台控制器生成所需路径 + */ + private function initApiControllerGenPath(): void + { + $this->api_con_temp_path = self::initFilePath($this->getTempPath('api')); + $this->api_con_module_path = self::initFilePath("{$this->root}api/controller/{$this->module_name}"); + $this->api_con_path = self::initFilePath("{$this->api_con_module_path}/{$this->class_name}.php"); + } + + /** + * 获取生成模板文件路径 + * + * @param string $type 生成模板文件类型 admin:后台控制器 | api:前台控制器 | model:模型层 + */ + private function getTempPath(string $type): string + { + //模板类型 + $temp_type = $this->gen_api_type; + //若排序处理开启,并且模板类型为crud,则使用crud_sort单表(增删改查+排序处理) + if ($this->is_sort) { + $temp_type = ($temp_type == 'crud') ? 'crud_sort' : $temp_type; + } + return $this->root . self::TEMP_PATH_CONFIG[$temp_type][$type]; + } + + /** + * 初始化图片字段变量 + */ + private function initImgFields(): void + { + foreach ($this->fields as $val) { + if ($val['htmlType'] == 'imageUpload') { + $this->img_fields[] = $val['columnName']; + } + } + } + + /** + * 初始化导入导出状态 + */ + private function initImportExportStatus(): void + { + //其他选项 4:导出 6:导入 + $checked_btn_arr = $this->table['options']->CheckedBtn; + $this->is_import = in_array('6', $checked_btn_arr); + $this->is_export = in_array('4', $checked_btn_arr); + } + + /** + * 初始化是否排序处理状态 + */ + private function initIsSortStatus(): void + { + $this->is_sort = ($this->table['isSort'] == 1); + } + + /** + * 构建新增允许字段模板字符 + */ + private function buildAddFieldsTemp(): void + { + $field_arr = array_merge(array_keys($this->business_fields), $this->add_required_fields); + $this->add_fields_temp = self::toFormTempStr($field_arr); + } + + /** + * 构建编辑允许字段模板字符 + */ + private function buildEditFieldsTemp(): void + { + $field_arr = array_merge(array_keys($this->business_fields), $this->edit_required_fields); + $this->edit_fields_temp = self::toFormTempStr($field_arr); + } + + /** + * 初始化功能必填验证模板字符 + */ + private function initRequiredVerifyFieldsTemp(): void + { + $edit_require_fields = []; + $add_require_fields = []; + foreach ($this->fields as $val) { + $column_name = $val['columnName']; + if ($val['isEdit']) { + if ($val['isRequired']) { + $edit_require_fields[] = $column_name; + } + } + if ($val['isInsert']) { + if ($val['isRequired']) { + $add_require_fields[] = $column_name; + } + } + }; + $this->edit_required_verify_temp = self::toFormTempStr($edit_require_fields, 2); + $this->add_required_verify_temp = self::toFormTempStr($add_require_fields, 2); + } + + /** + * 构建业务字段名 + */ + private function initBusinessFields() + { + foreach ($this->fields as $val) { + if (!$val['isInit']) { + $this->business_fields[$val['columnName']] = $val['columnComment']; + } + } + } + + /** + * 生成模块文件夹 + * + * @param string $path 模块文件夹路径 + */ + protected function createModuleMkdir(string $module_path): void + { + self::mkdir($module_path); + } + + /** + * 初始化文件路径 + * + * @param string $path 文件路径 + * @return string 初始化后的文件路径 + */ + protected static function initFilePath(string $path): string + { + //系统分隔符替换 + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } + + /** + * 获取模板文件字符串 + * + * @param string $temp_path 模板文件路径 + */ + protected static function getTempStr(string $temp_path): string + { + //打开该文件资源(r只读方式打开),读取模板文件内容 + return fread(fopen($temp_path, "r"), filesize($temp_path)); + } + + /** + * 写入文件 + * + * @param string $path 文件路径 + * @param string $content 写入内容 + */ + protected static function writeFile(string $path, string $content): void + { + //打开文件资源(w写入方式打开),进行写入 + fwrite(fopen($path, 'w'), $content); + } + + /** + * 初始化必要字段 + * + * @param string 必要字段变量名 + * + */ + protected function initRequiredFields(string $op_name): void + { + $init_data = []; + foreach ($this->$op_name as $val) { + $init_data[] = "{$this->business_name}_{$val}"; + } + $this->$op_name = $init_data; + } + + /** + * 创建文件夹 + * + * @param string $path 文件夹创建所属路径 + */ + protected static function mkdir(string $path): void + { + if (!(new Gen(new \think\App()))->mkdir($path)) { + throwErrorMsg(__METHOD__ . "创建文件夹失败!"); + } + } + + /** + * 数组转换为模板字符串 + * @param array $arr 数组 + * @param int $type 模板字符类型 1:一维数组格式 2:验证器格式 3:列表条件查询 4:关联数组格式 + */ + protected function toFormTempStr(array $arr, int $type = 1): string + { + return (new Gen(new \think\App()))->toFormTempStr($arr, $this->business_fields, $type); + } +} diff --git a/app/admin/controller/Gen/GenApi/GenController/Admin.php b/app/admin/controller/Gen/GenApi/GenController/Admin.php new file mode 100644 index 0000000..8dff2fd --- /dev/null +++ b/app/admin/controller/Gen/GenApi/GenController/Admin.php @@ -0,0 +1,122 @@ +admin_con_module_path); + //模板文件字符串替换 + $temp_str = Tool::strReplacePlus( + self::getTempStr($this->admin_con_temp_path), + [ + //实体类名 + '{$className}' => $this->class_name, + //模块名 + '{$moduleName}' => $this->module_name, + //业务名 + '{$businessName}' => $this->business_name, + //方法名 + '{$functionName}' => $this->function_name, + //查询列表显示字段 + '{$queryFields}' => self::toFormTempStr($this->query_list_fields), + //排序字段 + '{$orderField}' => $this->sort_field, + //排序类型 + '{$orderMode}' => $this->sort_type, + //新增允许字段 + '{$addAllowFields}' => $this->add_fields_temp, + //编辑允许字段 + '{$editAllowFields}' => $this->edit_fields_temp, + //列表查询条件 + '{$whereContent}' => $this->query_list_where_temp, + //导出接口 + '{$exportExcelContent}' => $this->buildExportExcelApiTemp(), + //导入接口 + '{$importExcelContent}' => $this->buildImportExcelApiTemp(), + //下载模板接口 + '{$downloadTempContent}' => $this->buildDownloadTemplateApiTemp(), + //编辑必填验证 + '{$editRequireFields}' => $this->edit_required_verify_temp, + //新增必填验证 + '{$addRequireFields}' => $this->add_required_verify_temp, + ] + ); + //文件写入 + self::writeFile($this->admin_con_path, $temp_str); + } + + /** + * 构建Excel导出接口模板字符串 + */ + private function buildExportExcelApiTemp(): string + { + if (!$this->is_export) return ''; + + //模板字符串构建并返回 + return "/**\n* 导出Excel\n*/ + public function exportExcel(Request \$request):void + { + Model{$this->class_name}::exportExcel(self::get{$this->class_name}List(\$request, true)); + }"; + } + + /** + * 构建Excel导入接口模板字符串 + */ + private function buildImportExcelApiTemp(): string + { + if (!$this->is_import) return ''; + + //模板字符串构建并返回 + return "/**\n* 导入excel\n*/ + public function importExcel(Request \$request):array + { + \$file = new UploadFile('uploads', 'fileExt:xlsx'); + \$file->putFile('{$this->business_name}'); + \$msg = Model{$this->class_name}::importExcel(\$file); + return [ + 'code' => 0, + 'msg' => \$msg + ]; + }"; + } + + /** + * 构建Excel下载模板接口模板字符串 + */ + private function buildDownloadTemplateApiTemp(): string + { + if (!$this->is_import) return ''; + + $initial_value_temp = ''; + for($i =0 ;$ibusiness_fields);$i++){ + $index =$i+1; + $initial_value_temp .= "'默认值{$index}',"; + } + + //模板字符串构建并返回 + return "/**\n* 下载导入模板\n*/ + public function downloadTemplate(Request \$request):void + { + \$params = \$request->param(); + \$data = [ + array_values(Model{$this->class_name}::EXCELFIELD), + [{$initial_value_temp}] + ]; + \$excel = (new Excel())->exporTsheet(\$data); + \$excel->save('{$this->function_name}导入模板.xlsx'); + }"; + } +} diff --git a/app/admin/controller/Gen/GenApi/GenController/Api.php b/app/admin/controller/Gen/GenApi/GenController/Api.php new file mode 100644 index 0000000..f3fb6f7 --- /dev/null +++ b/app/admin/controller/Gen/GenApi/GenController/Api.php @@ -0,0 +1,49 @@ +api_con_module_path); + //模板文件字符串替换 + $temp_str = Tool::strReplacePlus( + self::getTempStr($this->api_con_temp_path), + [ + //实体类名 + '{$className}' => $this->class_name, + //模块名 + '{$moduleName}' => $this->module_name, + //业务名 + '{$businessName}' => $this->business_name, + //方法名 + '{$functionName}' => $this->function_name, + //查询列表显示字段 + '{$queryFields}' => self::toFormTempStr($this->query_list_fields), + //排序字段 + '{$orderField}' => $this->sort_field, + //排序类型 + '{$orderMode}' => $this->sort_type, + //新增允许字段 + '{$addAllowFields}' => $this->add_fields_temp, + //编辑允许字段 + '{$editAllowFields}' => $this->edit_fields_temp, + //列表查询条件 + '{$whereContent}' => $this->query_list_where_temp, + ] + ); + //文件写入 + self::writeFile($this->api_con_path, $temp_str); + } +} diff --git a/app/admin/controller/Gen/GenApi/GenModel/Model.php b/app/admin/controller/Gen/GenApi/GenModel/Model.php new file mode 100644 index 0000000..df81e80 --- /dev/null +++ b/app/admin/controller/Gen/GenApi/GenModel/Model.php @@ -0,0 +1,209 @@ +model_module_path); + //模板文件字符串替换 + $temp_str = Tool::strReplacePlus( + self::getTempStr($this->model_temp_path), + [ + //实体类名 + '{$className}' => $this->class_name, + //模块名 + '{$moduleName}' => $this->module_name, + //业务名 + '{$businessName}' => $this->business_name, + //模型层字段信息 + '{$fields}' => $this->buildFieldsInfoTemp(), + //模型层导出Excel方法 + '{$exportExcelContent}' => $this->buildExportFun(), + //模型层导入/下载模板Excel表头 + '{$importExcelField}' => $this->buildImportDownloadFieldsTemp(), + //模型层导入Excel方法 + '{$importExcelContent}' => $this->buildImportFunTemp(), + //模型层导入Excel初始化方法 + '{$importExcelInitContent}' => $this->buildImportInitFunTemp(), + //排序字段 + '{$orderField}' => $this->sort_field, + ] + ); + //文件写入 + self::writeFile($this->model_path, $temp_str); + } + + /** + * 构建模型层字段信息模板字符串 + */ + public function buildFieldsInfoTemp(): string + { + $str = ""; + foreach ($this->fields as $value) { + $str .= " '{$value['columnName']}' => '{$value['columnType']}' , \n"; + } + return $str; + } + + /** + * 构建模型层导出方法模板字符串 + */ + private function buildExportFun(): string + { + if (!$this->is_export) return ''; + + //excel表头字符串构建 + $excel_header = []; + foreach ($this->business_fields as $name) { + $excel_header[] = $name; + }; + $excel_header_temp = $this->toFormTempStr($excel_header); + + //导出数据分配 + $data_str = ''; + foreach ($this->business_fields as $field => $name) { + //图片字段处理 + if (in_array($field, $this->img_fields)) { + $data_str .= "Excel::ExportImgFiled(\$val['$field']),\n"; + } else { + $data_str .= "\$val['{$field}'],\n"; + } + } + $data_str = "[\n{$data_str}]"; + + //模板字符串构建并返回 + return " + /** + * 导出Excel + * + * @param array \$select 导出的数据 + */ + public static function exportExcel(array \$select): void + { + \$data = [{$excel_header_temp}]; + foreach (\$select as \$key => \$val) { + \$data[] = {$data_str}; + } + \$excel = (new Excel())->exporTsheet(\$data); + \$excel->save('{$this->function_name}.xlsx'); + }"; + } + + /** + * 构建导入/下载模板表头模板字符串 + */ + private function buildImportDownloadFieldsTemp(): string + { + if (!$this->is_import) return ''; + + $init_fields_str = $this->toFormTempStr($this->business_fields, 4); + $init_fields_str = substr($init_fields_str, 0, strlen($init_fields_str) - 1); //末尾的逗号去除 + return " // excel导入/下载模板表头 + public const EXCELFIELD = {$init_fields_str};"; + } + + /** + * 构建模型层导入方法模板字符串 + */ + private function buildImportFunTemp(): string + { + if (!$this->is_import) return ''; + + //模板字符串构建并返回 + return " + /** + * 导入excel + * + * @param \app\common\arw\adjfut\src\UploadFile \$file excel + */ + public static function importExcel(\app\common\arw\adjfut\src\UploadFile \$file): string + { + \$msg = []; + + Db::startTrans(); + try { + \$excel = new Excel(\$file); + \$data = \$excel->parseExcel( + Tool::getExcelRule(self::EXCELFIELD), + ['titleLine' => [1]]); + if (!\$data) throwErrorMsg('excel无数据', 1); + \$msg = []; + foreach (\$data as \$line => \$value) { + try { + \$model = self::importExcelInit(\$value); + \$msg[] = \"{\$line} 新增成功!
\"; + } catch (\Throwable \$th) { + \$msg[] = \"{\$line} {\$th->getMessage()}
\"; + } + } + Db::commit(); + return implode(', ', \$msg); + } catch (\Throwable \$th) { + Db::rollback(); + throw \$th; + } + }"; + } + + /** + * 构建模型层导入初始化方法模板字符串 + */ + private function buildImportInitFunTemp(): string + { + if (!$this->is_import) return ''; + + /** + * (匿名函数)获取excel每行导入数据变量分配-模板字符串 + */ + $getImportAllocationTemp = function () { + $str = ""; + foreach ($this->business_fields as $field => $name) { + $str .= "\${$field} = \$value['{$field}'];"; + }; + return $str; + }; + $import_allocation_temp = $getImportAllocationTemp(); + + /** + * (匿名函数)获取新增的字段值们-模板字符串 + */ + $getAddFieldsTemp = function () { + $str = ""; + foreach ($this->business_fields as $field => $name) { + $str .= "'{$field}' => \${$field},\n"; + }; + return $str; + }; + $add_allocation_temp = $getAddFieldsTemp(); + + //模板字符串构建并返回 + return " + /** + * 导入excel初始化 + * + * @param array \$value excel每行数据 + */ + public static function importExcelInit(array \$value):void + { + {$import_allocation_temp} + + self::create( + [{$add_allocation_temp}], + {$this->add_fields_temp} + ); + } + "; + } +} diff --git a/app/admin/controller/Gen/GenVue/GenDto.php b/app/admin/controller/Gen/GenVue/GenDto.php new file mode 100644 index 0000000..a4b0bca --- /dev/null +++ b/app/admin/controller/Gen/GenVue/GenDto.php @@ -0,0 +1,223 @@ + '用户名称',...] + */ + protected $business_fields = []; + + /** + * 新增允许字段模板字符 + * @var string + */ + protected $add_fields_temp = ""; + + /** + * 是否导入 + * @var bool + */ + protected $is_import = false; + + /** + * 是否导出 + * @var bool + */ + protected $is_export = false; + + /** + * 图片字段数组 + * @var array + */ + protected $img_fields = []; + + /** + * 构造器 + * + * @param array $fields 字段信息 + * @param array $table 生成信息 + */ + public function __construct(array $fields, array $table) + { + $this->root = base_path(); + $this->fields = $fields; + $this->table = $table; + + $this->class_name = $this->table['className']; + $this->module_name = $this->table['moduleName']; + $this->business_name = $this->table['businessName']; + $this->function_name = $this->table['functionName']; + $this->genPath = $this->table['genPath']; + $this->vue_index_temp_path = self::initFilePath("{$this->root}resources/view/admin/model.tpl"); + $this->model_module_path = self::initFilePath("{$this->root}common/model/{$this->module_name}"); + $this->model_path = self::initFilePath("{$this->model_module_path}/{$this->class_name}.php"); + $this->mkdirModelModule(); + $this->buildBusinessFields(); + $this->buildAddFieldsTemp(); + $this->initImportExportStatus(); + $this->initImgFields(); + } + + /** + * 初始化图片字段变量 + */ + private function initImgFields(): void + { + foreach ($this->fields as $val) { + if ($val['htmlType'] == 'imageUpload') { + $this->img_fields[] = $val['columnName']; + } + }; + } + + /** + * 初始化导入导出状态 + */ + private function initImportExportStatus(): void + { + //其他选项 4:导出 6:导入 + $checked_btn_arr = $this->table['options']->CheckedBtn; + $this->is_import = in_array('6', $checked_btn_arr); + $this->is_export = in_array('4', $checked_btn_arr); + } + + /** + * 构建新增允许字段模板字符 + */ + private function buildAddFieldsTemp(): void + { + $this->add_fields_temp .= '['; + foreach ($this->business_fields as $key => $val) { + $this->add_fields_temp .= "'{$key}',"; + }; + $this->add_fields_temp .= ']'; + } + + /** + * 构建业务字段名 + */ + private function buildBusinessFields() + { + foreach ($this->fields as $val) { + if (!$val['isInit']) { + $this->business_fields[$val['columnName']] = $val['columnComment']; + } + }; + } + + /** + * 生成模型层模块文件夹 + */ + private function mkdirModelModule() + { + self::mkdir($this->model_module_path); + } + + /** + * 初始化文件路径 + * + * @param string $path 文件路径 + * @return string 初始化后的文件路径 + */ + protected static function initFilePath(string $path): string + { + //系统分隔符替换 + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } + + /** + * 创建文件夹 + * + * @param string $path 文件夹创建所属路径 + */ + protected static function mkdir(string $path): void + { + if (!(new Gen(new \think\App()))->mkdir($path)) { + throwErrorMsg(__METHOD__ . "创建文件夹失败!"); + }; + } + + /** + * 数组转换为模板字符串 + * @param array $arr 数组 + * @param int $type 模板字符类型 1:一维数组格式 2:验证器格式 3:列表条件查询 4:关联数组格式 + */ + protected function toFormTempStr(array $arr, int $type = 1): string + { + return (new Gen(new \think\App()))->toFormTempStr($arr, $this->business_fields, $type); + } +} diff --git a/app/admin/controller/Gen/GenVue/GenIndex/Index.php b/app/admin/controller/Gen/GenVue/GenIndex/Index.php new file mode 100644 index 0000000..7470a05 --- /dev/null +++ b/app/admin/controller/Gen/GenVue/GenIndex/Index.php @@ -0,0 +1,212 @@ +vue_index_temp_path, "r"); + //读取Index.vue模板文件,拿到模板文件的全部字符串 + $temp_str = fread($tem_f, filesize($this->vue_index_temp_path)); + //模板文件字符串替换 + $temp_str = Tool::strReplacePlus($temp_str, [ + //实体类名 + '{$className}' => $this->class_name, + //模块名 + '{$moduleName}' => $this->module_name, + //业务名 + '{$businessName}' => $this->business_name, + //Index.vue字段信息 + '{$fields}' => $this->buildFieldsInfoTemp(), + //Index.vue导出Excel方法 + '{$exportExcelContent}' => $this->buildExportFun(), + //Index.vue导入/下载模板Excel表头 + '{$importExcelField}' => $this->buildImportDownloadFieldsTemp(), + //Index.vue导入Excel方法 + '{$importExcelContent}' => $this->buildImportFunTemp(), + //Index.vue导入Excel初始化方法 + '{$importExcelInitContent}' => $this->buildImportInitFunTemp(), + ]); + + //打开Index.vue文件,拿到该文件资源(w写入方式打开) + $gen_model = fopen($this->model_path, 'w'); + //写入该Index.vue文件 + fwrite($gen_model, $temp_str); + } + + /** + * 构建Index.vue字段信息模板字符 + */ + public function buildFieldsInfoTemp(): string + { + $str = ""; + foreach ($this->fields as $value) { + $str .= " '{$value['columnName']}' => '{$value['columnType']}' , \n"; + } + return $str; + } + + /** + * 构建Index.vue导出方法模板字符 + */ + private function buildExportFun(): string + { + if (!$this->is_export) return ''; + + //excel表头字符构建 + $excel_header = []; + foreach ($this->business_fields as $name) { + $excel_header[] = $name; + }; + $excel_header_temp = $this->toFormTempStr($excel_header); + + //导出数据分配 + $data_str = ''; + foreach ($this->business_fields as $field => $name) { + if (in_array($name, $this->img_fields)) { + $data_str .= "Excel::ExportImgFiled(\$val['$field']),\n"; + } else { + $data_str .= "\$val['{$field}'],\n"; + } + } + $data_str = "[\n{$data_str}]"; + + //模板字符构建并返回 + return " + /** + * 导出Excel + * + * @param array \$select 导出的数据 + */ + public static function exportExcel(array \$select): void + { + \$data = [{$excel_header_temp}]; + foreach (\$select as \$key => \$val) { + \$data[] = {$data_str}; + } + \$excel = (new Excel())->exporTsheet(\$data); + \$excel->save('{$this->function_name}.xlsx'); + }"; + } + + /** + * 构建导入/下载模板表头模板字符 + */ + private function buildImportDownloadFieldsTemp(): string + { + if (!$this->is_import) return ''; + + $init_fields_str = $this->toFormTempStr($this->business_fields, 4); + $init_fields_str = substr($init_fields_str, 0, strlen($init_fields_str) - 1); //末尾的逗号去除 + return " + // excel导入/下载模板表头 + public const EXCELFIELD = {$init_fields_str}; + "; + } + + /** + * 构建Index.vue导入方法模板字符 + */ + private function buildImportFunTemp(): string + { + if (!$this->is_import) return ''; + + //模板字符构建并返回 + return " + /** + * 导入excel + * + * @param \app\common\arw\adjfut\src\UploadFile \$file excel + */ + public static function importExcel(\app\common\arw\adjfut\src\UploadFile \$file): string + { + \$msg = []; + + Db::startTrans(); + try { + \$excel = new Excel(\$file); + \$data = \$excel->parseExcel( + Tool::getExcelRule(self::EXCELFIELD), + [ + 'titleLine' => [1] + ]); + if (!\$data) throwErrorMsg('excel无数据', 1); + \$msg = []; + foreach (\$data as \$line => \$value) { + try { + \$model = self::importExcelInit(\$value); + \$msg[] = \"{\$line} 新增成功!
\"; + } catch (\Throwable \$th) { + \$msg[] = \"{\$line} {\$th->getMessage()}
\"; + } + } + Db::commit(); + return implode(', ', \$msg); + } catch (\Throwable \$th) { + Db::rollback(); + throw \$th; + } + }"; + } + + /** + * 构建Index.vue导入初始化方法模板字符 + */ + private function buildImportInitFunTemp(): string + { + if (!$this->is_import) return ''; + + /** + * (匿名函数)获取excel每行导入数据变量分配-模板字符 + */ + $getImportAllocationTemp = function () { + $str = ""; + foreach ($this->business_fields as $field => $name) { + $str .= "\${$field} = \$value['{$field}'];"; + }; + return $str; + }; + $import_allocation_temp = $getImportAllocationTemp(); + + /** + * (匿名函数)获取新增的字段值们-模板字符 + */ + $getAddFieldsTemp = function () { + $str = ""; + foreach ($this->business_fields as $field => $name) { + $str .= "'{$field}' => \${$field},\n"; + }; + return $str; + }; + $add_allocation_temp = $getAddFieldsTemp(); + + //模板字符构建并返回 + return " + /** + * 导入excel初始化 + * + * @param array \$value excel每行数据 + */ + public static function importExcelInit(array \$value):void + { + {$import_allocation_temp} + + self::create( + [{$add_allocation_temp}], + {$this->add_fields_temp} + ); + } + "; + } +} diff --git a/app/admin/controller/Login.php b/app/admin/controller/Login.php new file mode 100644 index 0000000..63d99bd --- /dev/null +++ b/app/admin/controller/Login.php @@ -0,0 +1,145 @@ +middleware[SessionInit::class] = [ + 'only' => ['accountLogin', 'getCaptcha'] + ]; + } + + /** + * 验证token + * + * @param Request $request + * @return void + */ + public function validateToken(Request $request) + { + $token = $request->getCurrentToken(); + return [ + 'code' => 0, + 'data' => [ + 'exp_time' => $token->token_exp_time, + ], + 'msg' => 'ok' + ]; + } + + /** + * 西北政法大学单点登陆 + * + * @param Request $request + * @return Response + * @date 2023-01-04 + * @example + * @author admin + * @since 1.0.0 + */ + public function casOauthLogin(Request $request): Response + { + $url = $request->param('url'); + return LogicLogin::casOauthLogin( + LogicLogin::casOauthLoginHandle($url) + ); + } + + /** + * 西北政法大学单点登出 + * + * @param Request $request + * @return Response + * @date 2023-01-04 + * @example + * @author admin + * @since 1.0.0 + */ + public function casOauthLogout(Request $request): Response + { + $url = $request->param('url'); + return LogicLogin::casOauthLogout($url); + } + + /** + * 用户账号登录 + * + * @param Request $request + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function accountLogin(Request $request): array + { + $param = Validate::param([ + 'account|账号' => 'require', + 'password|密码' => 'require', + // 'captcha|验证码' => $request->isProd() ? 'require|captcha' : false + ]); + $token = LogicLogin::accountLogin( + $param['account'], + $param['password'] + ); + + return [ + 'code' => 0, + 'data' => [ + 'token' => $token->token_content, + 'exp_time' => $token->token_exp_time, + ], + 'msg' => 'ok' + ]; + } + + /** + * 生成验证码 + * + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function getCaptcha() + { + return Captcha::create(); + } + + /** + * 用户登出 + * + * @param Request $request + * @date 2022-03-09 + * @example + * @author admin + * @since 1.0.0 + */ + public function userLogout(Request $request): array + { + $token = $request->getCurrentToken(); + $token->logout(); + return [ + 'code' => 0, + 'msg' => '登出成功' + ]; + } +} diff --git a/app/admin/controller/Menu/Menu.php b/app/admin/controller/Menu/Menu.php new file mode 100644 index 0000000..00ead1e --- /dev/null +++ b/app/admin/controller/Menu/Menu.php @@ -0,0 +1,368 @@ + 'desc', + ])->field([ + 'menu_guid', + 'menu_parent_guid', + 'menu_name', + 'menu_url', + ])->select()->toArray(); + + $menuApi = []; + foreach (MenuApi::select() as $value) { + if (!isset($menuApi[$value->menu_guid])) { + $menuApi[$value->menu_guid] = []; + } + $menuApi[$value->menu_guid][] = $value->menu_api_url; + } + + $Traverse = new Traverse('menu_guid', 'menu_parent_guid'); + $menuTree = $Traverse->tree($menu, '0', function ($v) use ($menuApi) { + return [ + 'menu_api_url' => isset($menuApi[$v['menu_guid']]) ? $menuApi[$v['menu_guid']] : [], + 'menu_name' => $v['menu_name'], + 'menu_guid' => $v['menu_guid'], + 'menu_parent_guid' => $v['menu_parent_guid'], + 'menu_url' => $v['menu_url'], + ]; + }, function ($v) { + $v['hasChildren'] = isset($value['children']) && count($value['children']); + return $v; + }); + + return [ + 'code' => 0, + 'msg' => 'ok', + 'data' => $menuTree, + ]; + } + + /** + * 添加菜单接口 + * + * @param Request $request + * @return array + * @date 2022-03-08 + * @example + * @author admin + * @since 1.0.0 + */ + public function addMenuApi(Request $request) + { + Db::startTrans(); + try { + $params = $request->param(); + $this->validate($params, [ + 'menu_guid' => 'require', + 'menu_api_url' => 'require' + ]); + foreach (explode(',', $params['menu_api_url']) as $menu_api_url) { + MenuApi::create([ + 'menu_guid' => $params['menu_guid'], + 'menu_api_url' => $menu_api_url, + ]); + } + Db::commit(); + return [ + 'code' => 0, + 'msg' => '添加成功', + ]; + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + /** + * 删除菜单接口 + * + * @param Request $request + * @return array + * @date 2022-03-08 + * @example + * @author admin + * @since 1.0.0 + */ + public function deleteMenuApi(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'menu_api_guid' => 'require' + ]); + MenuApi::where([ + 'menu_api_guid' => explode(',', $params['menu_api_guid']) + ])->select()->delete(); + return [ + 'code' => 0, + 'msg' => "删除成功" + ]; + } + + /** + * 获取菜单接口列表 + * + * @param Request $request + * @return array + * @date 2022-03-07 + * @example + * @author admin + * @since 1.0.0 + */ + public function getMenuApiList(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'menu_guid' => 'require' + ]); + $query = ModelMenu::find($params['menu_guid'])->apis()->append([ + 'menu_api_status_text' + ]); + $data = self::pageWrapper($query)->select(); + $count = $query->count(); + return [ + 'code' => 0, + 'msg' => 'ok', + 'data' => $data, + 'count' => $count + ]; + } + + /** + * 获取菜单树 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function getMenuTree(Request $request) + { + $menu = ModelMenu::order([ + 'menu_index', + 'menu_order' => 'desc', + ])->field([ + 'menu_parent_guid', + 'menu_name', + 'menu_url', + 'menu_index', + 'menu_order', + 'menu_status', + 'menu_show', + 'menu_guid', + ])->select()->toArray(); + + array_unshift($menu, [ + 'menu_guid' => '0', + 'menu_parent_guid' => '-1', + 'menu_name' => '系统菜单', + 'menu_index' => 0, + 'menu_order' => 0, + 'menu_status' => 0, + 'menu_show' => 0, + 'menu_url' => '', + ]); + + $Traverse = new Traverse('menu_guid', 'menu_parent_guid'); + $menuTree = $Traverse->tree($menu, '-1', function ($v) { + return [ + 'label' => $v['menu_name'], + 'id' => $v['menu_guid'], + 'parent_id' => $v['menu_parent_guid'], + 'index' => $v['menu_index'], + 'order' => $v['menu_order'], + 'status' => $v['menu_status'], + 'show' => $v['menu_show'], + 'url' => $v['menu_url'], + ]; + }); + + return [ + 'code' => 0, + 'msg' => 'ok', + 'data' => $menuTree + ]; + } + + /** + * 获取角色菜单 + * + * @param Request $request + * @return array + * @date 2022-03-09 + * @example + * @author admin + * @since 1.0.0 + */ + public function getRoleMenu(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'role_guid' => 'require' + ]); + $menus = Role::getByRoleGuid($params['role_guid'])->menus; + $menu_guids = []; + $menu_parent_guids = []; + foreach ($menus as $menu) { + if (!in_array($menu->menu_guid, $menu_guids)) { + $menu_guids[] = $menu->menu_guid; + } + if (!in_array($menu->menu_parent_guid, $menu_parent_guids)) { + $menu_parent_guids[] = $menu->menu_parent_guid; + } + } + // 去除父id 存在父id会全选子节点 + $temp = []; + foreach ($menu_guids as $menu_guid) { + if (!in_array($menu_guid, $menu_parent_guids)) { + $temp[] = $menu_guid; + } + } + return [ + 'code' => 0, + 'data' => $temp, + 'msg' => 'ok' + ]; + } + + /** + * 绑定角色菜单 + * + * @param Request $request + * @return array + * @date 2022-03-09 + * @example + * @author admin + * @since 1.0.0 + */ + public function bindRoleMenu(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'role_guid' => 'require', + 'menu_guid' => 'require' + ]); + $menus = array_map(function ($v) { + return ModelMenu::find($v); + }, explode(',', $params['menu_guid'])); + RoleMenu::rebindRoleMenu( + [ + Role::getByRoleGuid($params['role_guid']), + ], + array_filter($menus) + ); + return [ + 'code' => 0, + 'msg' => '绑定成功' + ]; + } + + /** + * 添加菜单 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function addMenu(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'menu_name' => 'require', + 'menu_index' => 'require', + 'menu_order' => 'require', + 'menu_show' => 'require', + 'menu_parent_guid' => 'require', + ]); + if (isset($params['menu_guid'])) { + unset($params['menu_guid']); + } + ModelMenu::create($params); + return [ + 'code' => 0, + 'msg' => '添加成功' + ]; + } + + /** + * 更新菜单 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function updateMenu(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'menu_guid' => 'require' + ]); + ModelMenu::withoutGlobalScope(['status'])->update($params, [ + 'menu_guid' => $params['menu_guid'] + ]); + return [ + 'code' => 0, + 'msg' => '更新成功' + ]; + } + + /** + * 删除菜单 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function deleteMenu(Request $request) + { + $params = $request->param(); + $this->validate($params, [ + 'menu_guid' => 'require' + ]); + ModelMenu::where([ + 'menu_guid' => explode(',', $params['menu_guid']) + ])->select()->delete(); + return [ + 'code' => 0, + 'msg' => '删除成功' + ]; + } +} diff --git a/app/admin/controller/Role/Role.php b/app/admin/controller/Role/Role.php new file mode 100644 index 0000000..9299621 --- /dev/null +++ b/app/admin/controller/Role/Role.php @@ -0,0 +1,198 @@ +param('role_status'); + $role = $request->param('role'); + $scope = explode(',', $request->param('scope', '')); + $con = [ + // 'role_status' => 1 + ]; + if ($role) { + $con['role_name'] = $role; + } + if ($role_status) { + $con['role_status'] = $role_status; + } + $query = ModelRole::scope($scope)->withSearch(['role_name'], $con)->where($con); + $select = self::pageWrapper($query)->field([ + 'role_name', + 'role_status', + 'role_guid', + ])->append([ + 'role_status_text' + ])->order([ + 'role_id' => 'desc' + ])->select(); + $count = $query->count(); + return [ + 'code' => 0, + 'data' => $select, + 'count' => $count, + 'msg' => 'ok' + ]; + } + + /** + * 获取角色菜单 + * + * @param Request $request + * @date 2022-03-08 + * @example + * @author admin + * @since 1.0.0 + */ + public function getRoleMenu(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'role_guid' => 'require' + ]); + dump(ModelRole::getByRoleGuid($params['role_guid'])->menus); + return [ + 'code' => 0, + 'msg' => 'ok', + // 'data' => $data + ]; + } + + /** + * 编辑角色 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function editRole(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'role_guid' => 'require', + 'role_name' => 'require', + ]); + $role = ModelRole::where([ + 'role_guid' => $params['role_guid'] + ])->find(); + if (!$role) { + throwErrorMsg("角色不存在", 1); + } + $role->save([ + 'role_name' => $params['role_name'], + ]); + return [ + 'code' => 0, + 'msg' => '编辑成功' + ]; + } + + /** + * 添加角色 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function addRole(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'role_name' => 'require', + ]); + ModelRole::create([ + 'role_name' => $params['role_name'], + ]); + return [ + 'code' => 0, + 'msg' => '添加成功' + ]; + } + + /** + * 删除角色 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function deleteRole(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'role_guid' => 'require', + ]); + $roles = ModelRole::where([ + 'role_guid' => ['in', $params['role_guid']] + ])->select(); + $roles->delete(); + return [ + 'code' => 0, + 'msg' => "删除成功" + ]; + } + + /** + * 更新角色状态 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function updateRoleStatus(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'role_guid' => 'require', + 'role_status' => 'require|in:1,2', + ]); + $role_status = $params['role_status']; + $roles = ModelRole::where([ + 'role_guid' => explode(',', $params['role_guid']) + ])->select(); + $roles->update([ + 'role_status' => $role_status + ]); + return [ + 'code' => 0, + 'msg' => "更新成功" + ]; + } +} diff --git a/app/admin/controller/Tdk/Tdk.php b/app/admin/controller/Tdk/Tdk.php new file mode 100644 index 0000000..b3b5545 --- /dev/null +++ b/app/admin/controller/Tdk/Tdk.php @@ -0,0 +1,150 @@ +param(); + $con = []; + + $con = Tool::getOptionalQuery(['tdk_type', '='], ['tdk_title', 'LIKE'],); + + $query = ModelTdk::where($con) + ->field([ + 'tdk_id', + 'tdk_guid', + 'tdk_type', + 'tdk_title', + 'tdk_description', + 'tdk_keyword' + ]) + ->order('tdk_update_time', 'desc'); + + return msg("获取网站tdk列表成功!", $query); + } + + /** + * 编辑网站tdk + */ + public function editTdk(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'tdk_type|tdk所属模块' => 'require', + 'tdk_title|网页标题' => 'require', + 'tdk_description|网页简介' => 'require', + 'tdk_keyword|网页关键词' => 'require' + ]); + $model = ModelTdk::where('tdk_guid', $params['tdk_guid'])->find(); + if (!$model) throwErrorMsg("该网站tdk不存在", 1); + $model->allowField([ + 'tdk_update_user_guid', + 'tdk_type', + 'tdk_title', + 'tdk_description', + 'tdk_keyword' + ])->save($params); + return msg('编辑成功!'); + } + + /** + * 添加网站tdk + */ + public function addTdk(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'tdk_type|tdk所属模块' => 'require', + 'tdk_title|网页标题' => 'require', + 'tdk_description|网页简介' => 'require', + 'tdk_keyword|网页关键词' => 'require' + ]); + $model = ModelTdk::create($params, [ + 'tdk_guid', + 'tdk_create_user_guid', + 'tdk_update_user_guid', + 'tdk_type', + 'tdk_title', + 'tdk_description', + 'tdk_keyword' + ]); + return msg('添加成功!'); + } + + /** + * 删除网站tdk + */ + public function deleteTdk(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'tdk_guid' => 'require', + ]); + $tdk = ModelTdk::where([ + 'tdk_guid' => explode(',', $params['tdk_guid']) + ])->select(); + $tdk->delete(); + return msg('删除成功!'); + } + + /** + * 导出Excel + */ + public function exportExcel(Request $request) + { + $params = $request->param(); + $select = ModelTdk::field([ + 'tdk_type', + 'tdk_title', + 'tdk_description', + 'tdk_keyword' + ]) + ->order('tdk_update_time', 'desc') + ->select(); + return ModelTdk::exportExcel($select); + } + + /** + * 下载导入模板 + */ + public function downloadTemplate(Request $request) + { + $params = $request->param(); + $data = array_values(ModelTdk::EXCELFIELD); + $excel = (new Excel())->exporTsheet($data); + $excel->save('网站tdk导入模板.xlsx'); + } + + /** + * 导入excel + */ + public function importExcel(Request $request) + { + $file = new UploadFile('uploads', 'fileExt:xlsx'); + $file->putFile('tdk'); + + $msg = ModelTdk::importExcel($file); + return [ + 'code' => 0, + 'msg' => $msg + ]; + } +} diff --git a/app/admin/controller/User/User.php b/app/admin/controller/User/User.php new file mode 100644 index 0000000..0f80762 --- /dev/null +++ b/app/admin/controller/User/User.php @@ -0,0 +1,371 @@ +getCurrentUser(); + return [ + 'code' => 0, + 'data' => [ + 'user_name' => $user['user_name'] + ], + 'msg' => 'ok' + ]; + } + + /** + * 获取用户菜单 + * + * @param Request $request + * @return array + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function getUserMenu(Request $request) + { + // $user = $request->getCurrentUser(); + // $menus = []; + // foreach ($user->roles as $role) { + // $menus = array_merge($menus, $role->menus->toArray()); + // } + + // array_multisort(array_column($menus, 'menu_order'), SORT_DESC, $menus); + + // var_dump($menus); + + $token = Token::getCurrent(); + $menus = $token->token_menu; + + $Traverse = new Traverse('menu_guid', 'menu_parent_guid'); + + $tree = $Traverse->tree($menus, '0', function ($v) { + return [ + 'key' => $v['menu_guid'], + 'name' => $v['menu_name'], + 'url' => $v['menu_url'], + 'show' => $v['menu_show'], + 'icon' => $v['menu_icon'], + ]; + }); + + return [ + 'code' => 0, + 'msg' => 'ok', + 'data' => $tree + ]; + } + + /** + * 获取用户列表 + * + * @param Request $request + * @date 2022-02-25 + * @example + * @author admin + * @since 1.0.0 + */ + public function getUserList(Request $request): array + { + $user = $request->param('user'); + $con = [ + // 'user_status' => 1 + ]; + $search = []; + if ($user) { + $search['user_name'] = $user; + } + + $query = ModelUser::scope(['admin']) + ->withSearch(array_keys($search), $search)->where($con) + ->leftjoin('user_role', 'user_role.user_guid = user.user_guid') + ->join('role', join(' AND ', [ + 'role.role_guid = user_role.role_guid', + 'role.role_status = 1', + 'role.role_delete_time IS NULL', + ]), 'left')->where($con); + $select = self::pageWrapper($query)->field([ + 'user.user_guid', + 'user.user_name', + 'user.user_phone', + 'user.user_position', + 'user.user_department', + 'user.user_img', + 'user.user_status', + 'role.role_name', + ]) + ->append([ + 'roles', + ]) + ->group('user.user_guid')->order([ + 'user.user_update_time' => 'desc' + ])->select(); + $count = $query->count(); + return [ + 'code' => 0, + 'data' => $select, + 'count' => $count, + 'msg' => 'ok' + ]; + } + + /** + * 编辑用户 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function editUser(Request $request): array + { + $params = Validate::param([ + 'user_guid' => 'require', + 'user_name' => 'require', + 'roles|角色' => 'require', + ]); + $model = ModelUser::where([ + 'user_guid' => $params['user_guid'] + ])->find(); + if (!$model) { + throwErrorMsg("用户不存在", 1); + } + $model->save($params); + ModelUser::editUserRole($params); + return [ + 'code' => 0, + 'msg' => '编辑成功' + ]; + } + + /** + * 添加用户 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function addUser(Request $request): array + { + $params = Validate::param([ + 'user_name' => 'require', + 'user_password' => 'require', + 'roles|角色' => 'require', + ]); + $params['user_status'] = 1; + + $model = ModelUser::create($params); + + $user_guid = $model->user_guid; + + ModelUser::addUserRole($user_guid, $params); + + return [ + 'code' => 0, + 'msg' => '添加成功' + ]; + } + + /** + * 删除用户 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function deleteUser(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'user_guid' => 'require', + ]); + $users = ModelUser::where([ + 'user_guid' => explode(',', $params['user_guid']) + ])->select(); + $users->delete(); + return [ + 'code' => 0, + 'msg' => "删除成功" + ]; + } + + /** + * 更新用户状态 + * + * @param Request $request + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function updateUserStatus(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'user_guid' => 'require', + 'user_status' => 'require|in:1,2', + ]); + $user_status = $params['user_status']; + $users = ModelUser::where([ + 'user_guid' => explode(',', $params['user_guid']) + ])->select(); + $users->update([ + 'user_status' => $user_status + ]); + return [ + 'code' => 0, + 'msg' => "更新成功" + ]; + } + + /** + * 重置用户密码 + * + * @param Request $request + * @date 2022-02-25 + * @example + * @author admin + * @since 1.0.0 + */ + public function resetUserPassword(Request $request): array + { + $params = Validate::param([ + 'user_guid' => 'require', + 'password' => 'require', + ]); + $password = $params['password']; + $users = ModelUser::select(explode(',', $params['user_guid'])); + // $users = ModelUser::where([ + // 'user_guid' => ['in', $params['user_guid']] + // ])->select(); + $users->update([ + 'user_password' => $password + ]); + return [ + 'code' => 0, + 'msg' => "密码已重置为:$password ,请及时修改密码" + ]; + } + + + /** + * 导出Excel + */ + public function exportExcel(Request $request) + { + $user = $request->param('user'); + $con = [ + // 'user_status' => 1 + ]; + $search = []; + if ($user) { + $search['user_name'] = $user; + } + + $select = ModelUser::scope(['admin']) + ->where($con)->field([ + 'user.user_guid', + 'user.user_name', + 'user.user_phone', + 'user.user_position', + 'user.user_department', + 'user.user_img', + 'role.role_name', + ]) + ->append([ + 'roles', + ]) + ->withSearch(array_keys($search), $search)->where($con) + ->leftjoin('user_role', 'user_role.user_guid = user.user_guid') + ->join('role', join(' AND ', [ + 'role.role_guid = user_role.role_guid', + 'role.role_status = 1', + 'role.role_delete_time IS NULL', + ]), 'left') + ->group('user.user_guid')->order([ + 'user.user_update_time' => 'desc' + ])->select(); + + return ModelUser::exportExcel($select); + } + + /** + * 下载导入模板 + */ + public function downloadTemplate(Request $request) + { + $params = $request->param(); + $data = [ + [ + '用户名', + '头像', + '角色', + '手机号', + '密码', + ] + ]; + + $data[] = [ + '负责人', + 'https://img13.360buyimg.com/n5/jfs/t1/195344/2/24691/95759/6295e145E6fae20b9/1172b44c351eeaab.jpg.avif', + '管理员,部门负责人', + '10086', + '123456@aerwen', + ]; + + $excel = (new Excel())->exporTsheet($data); + $excel->save('用户导入模板.xlsx'); + } + + /** + * 导入excel + */ + public function importExcel(Request $request) + { + $file = new UploadFile('uploads', 'fileExt:xlsx'); + $file->putFile('User'); + + $msg = ModelUser::importExcel($file); + + return [ + 'code' => 0, + 'msg' => $msg + ]; + } + +} diff --git a/app/admin/controller/User/UserRole.php b/app/admin/controller/User/UserRole.php new file mode 100644 index 0000000..971e593 --- /dev/null +++ b/app/admin/controller/User/UserRole.php @@ -0,0 +1,153 @@ +param('role_guid'); + $user = $request->param('user'); + $this->validate($request->param(), [ + 'role_guid' => 'require' + ]); + $user_guids = []; + $con = []; + $users = ModelRole::getByRoleGuid($role_guid)->users; + if ($users) { + $user_guids = $users->column('user_guid'); + } + if ($user) { + $con['user_name'] = $user; + } + $query = ModelUser::withSearch(['user_name'], $con)->where($con)->where('user_guid', 'not in', $user_guids); + $select = self::pageWrapper($query)->field([ + 'user_name', + 'user_guid', + ])->order([ + 'user_id' => 'desc' + ])->select(); + $count = $query->count(); + return [ + 'code' => 0, + 'data' => $select, + 'count' => $count, + 'msg' => 'ok' + ]; + } + /** + * 获取绑定角色用户列表 + * + * @param Request $request + * @return void + * @date 2022-03-02 + * @example + * @author admin + * @since 1.0.0 + */ + public function getBindUserList(Request $request): array + { + $role_guid = $request->param('role_guid'); + $user = $request->param('user'); + $this->validate($request->param(), [ + 'role_guid' => 'require' + ]); + $user_ids = []; + $con = []; + $users = ModelRole::getByRoleGuid($role_guid)->users; + if ($users) { + $user_ids = $users->column('user_id'); + } + if ($user) { + $con['user_name'] = $user; + } + $query = ModelUser::withSearch(['user_name'], $con)->where($con)->where('user_id', 'in', $user_ids); + $select = self::pageWrapper($query)->field([ + 'user_name', + 'user_guid', + ])->order([ + 'user_id' => 'desc' + ])->select(); + $count = $query->count(); + return [ + 'code' => 0, + 'data' => $select, + 'count' => $count, + 'msg' => 'ok' + ]; + } + + /** + * 绑定用户角色 + * + * @param Request $request + * @date 2022-03-02 + * @example + * @author admin + * @since 1.0.0 + */ + public function bindUserRole(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'user_guid' => 'require', + 'role_guid' => 'require', + ]); + $users = array_map(function ($v) { + return ModelUser::getByUserGuid($v); + }, explode(',', $params['user_guid'])); + $roles = array_map(function ($v) { + return ModelRole::getByRoleGuid($v); + }, explode(',', $params['role_guid'])); + ModelUserRole::bindUserRole($users, $roles); + return [ + 'code' => 0, + 'msg' => '绑定成功' + ]; + } + + /** + * 绑定用户角色 + * + * @param Request $request + * @date 2022-03-02 + * @example + * @author admin + * @since 1.0.0 + */ + public function unBindUserRole(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + 'user_guid' => 'require', + 'role_guid' => 'require', + ]); + $users = array_map(function ($v) { + return ModelUser::getByUserGuid($v); + }, explode(',', $params['user_guid'])); + $roles = array_map(function ($v) { + return ModelRole::getByRoleGuid($v); + }, explode(',', $params['role_guid'])); + ModelUserRole::unbindUserRole($users, $roles); + return [ + 'code' => 0, + 'msg' => '解绑成功' + ]; + } +} diff --git a/app/admin/middleware/Auth.php b/app/admin/middleware/Auth.php new file mode 100644 index 0000000..c67b42f --- /dev/null +++ b/app/admin/middleware/Auth.php @@ -0,0 +1,84 @@ + ['*'], + 'admin' => ['*'], + ]; + + /** + * 处理请求 + * + * @param \app\Request $request + * @param \Closure $next + * @return Response + */ + public function handle(Request $request, \Closure $next): Response + { + $this->request = $request; + + if ($this->isIgnoreLogin()) { + return $next($request); + } + + if ($request->isLogin()) { + $user = $request->getCurrentUser(); + if ($user->user_admin) { + return $next($request); + } + } + + $api = join('/', [ + $request->controller(), + $request->action() + ]); + + $token = Token::getCurrent(); + + /** + * 缓存接口权限验证 + */ + $validate = $this->validateApi($token->token_api); + /** + * 缓存权限验证不通过 + */ + if ($validate === false) { + /** + * 自定义验证权限 + * 如果验证通过刷新缓存接口 + */ + $validate = $this->validateUser(); + if ($validate) { + Token::getCurrentUser()->login(); + } + } + /** + * 权限验证通过 + */ + if ($validate) { + return $next($request); + } + throw new NotAuthApi("未授权接口 [ $api ]", 1); + } +} diff --git a/app/admin/route.php b/app/admin/route.php new file mode 100644 index 0000000..90d0835 --- /dev/null +++ b/app/admin/route.php @@ -0,0 +1,12 @@ + [ + // 接口鉴权 + \app\admin\middleware\Auth::class + ], +]; diff --git a/app/api/controller/CommonApi/CommonApi.php b/app/api/controller/CommonApi/CommonApi.php new file mode 100644 index 0000000..e5d2c70 --- /dev/null +++ b/app/api/controller/CommonApi/CommonApi.php @@ -0,0 +1,32 @@ +request('GET', $url); + + // 选择要解析的HTML元素 + $titles = $crawler->filter('.productsBody .productItem .products'); + $imgs = $crawler->filter('.productsBody .productItem .products .productImg'); + // return $titles; + // dump($titles); + // die; + + $title111 = $titles->filter('.productType')->text(); + $img111 = $titles->filter('.productImg')->eq(2)->attr('lazyload'); + // return $img111; + // return $title111; + + + // 在全部元素中筛选 + // $products = $crawler->filter('.productsBody .productItem .products'); + + // foreach ($products as $key => $value) { + // // dump($value->filter('.productType')->eq($key)->text()); + // // die; + // foreach ($titles->filter('.productType')->eq($key) as $key => $value) { + // $title = $value->textContent; + // } + + // // return $title; + // } + + + $titleArr = []; + // 遍历元素并输出结果 + foreach ($titles as $title) { + $titleArr[] = $title->textContent; + } + + return $titleArr; + + // $imgArr = []; + foreach ($imgs as $img) { + // var_dump($img->getAttribute('lazyload')); + // die; + // return $img->getAttribute('lazyload'); + $src = $img->getAttribute('lazyload'); + + if (strpos($src, 'http') === 0) { + $img_url = $src; + } else { + $img_url = $base_url . $src; + } + // return $img_url; + + // 文件夹名称 + $dirName = "product" . "Img"; + // 文件保存位置 + $fileSaveLocation = public_path('uploads') . $dirName . "\\"; + // return $fileSaveLocation; + if (true !== $res = Tool::mkdir($fileSaveLocation)) { + return $res; + } + + // 获取图片二进制数据 + $imageContent = file_get_contents($img_url); + // var_dump($imageContent); + // die; + // return $imageContent; + + // 保存图片到本地文件系统 + file_put_contents($fileSaveLocation . basename($img_url), $imageContent); + + $res_img_url = "/uoloads" . "/" . $dirName . "/" . basename($img_url); + return $res_img_url; + } + + + // return $imgArr; + return $titleArr; + } +} diff --git a/app/api/controller/Crawler/CrawlerHoude.php b/app/api/controller/Crawler/CrawlerHoude.php new file mode 100644 index 0000000..1d2eb94 --- /dev/null +++ b/app/api/controller/Crawler/CrawlerHoude.php @@ -0,0 +1,129 @@ +request('GET', $url); + + // 选择要解析的HTML元素 + $imgs = $crawler->filter('.grid img'); + // return $titles; + // dump($titles); + // die; + + // 在全部元素中筛选 + // $products = $crawler->filter('.productsBody .productItem .products'); + + // foreach ($products as $key => $value) { + // // dump($value->filter('.productType')->eq($key)->text()); + // // die; + // foreach ($titles->filter('.productType')->eq($key) as $key => $value) { + // $title = $value->textContent; + // } + + // // return $title; + // } + + + $titleArr = []; + // 遍历元素并输出结果 + // foreach ($titles as $title) { + // $titleArr[] = $title->textContent; + // } + + // return $titleArr; + + // $imgArr = []; + foreach ($imgs as $img) { + // var_dump($img->getAttribute('lazyload')); + // die; + // return $img->getAttribute('lazyload'); + $src = $img->getAttribute('src'); + + if (strpos($src, 'http') === 0) { + $img_url = $src; + } else { + $img_url = $base_url . $src; + } + // return $img_url; + + // 文件夹名称 + $dirName = "HomeWorks" . "Img"; + // 文件保存位置 + $fileSaveLocation = public_path('uploads') . $dirName . "\\"; + // return $fileSaveLocation; + if (true !== $res = Tool::mkdir($fileSaveLocation)) { + return $res; + } + + $saveFileSaveLocation = $fileSaveLocation . date('Ymd') . "\\"; + if (true !== $res = Tool::mkdir($saveFileSaveLocation)) { + return $res; + } + + // 获取图片二进制数据 + $imageContent = file_get_contents($img_url); + // var_dump($imageContent); + // die; + // return $imageContent; + + // return $saveFileSaveLocation; + + // 保存图片到本地文件系统 + file_put_contents($saveFileSaveLocation . basename($img_url), $imageContent); + + $res_img_url = "/uploads" . "/" . $dirName . "/" . date('Ymd') . "/" . basename($img_url); + // return $res_img_url; + + // $model = ModelHomeEnv::create([ + // 'home_env_img' => $res_img_url, + // 'home_env_sort' => 1 + // ]); + + $model = ModelHomeWorks::create([ + 'home_works_img' => $res_img_url, + 'home_works_sort' => 1 + ]); + + // return $res_img_url; + } + + + // return $imgArr; + return $titleArr; + } +} diff --git a/app/api/controller/Flow/Flow.php b/app/api/controller/Flow/Flow.php new file mode 100644 index 0000000..2a7bbfc --- /dev/null +++ b/app/api/controller/Flow/Flow.php @@ -0,0 +1,33 @@ +param(); + $this->validate($params, ['flow_target' => 'require']); + $flow_target = $params['flow_target']; + + try { + return (new ModelFlow)->track($flow_target); + return json(msg("添加流量访问记录成功")); + } catch (\Throwable $th) { + throwErrorMsg("错误信息:" . $th); + } + } + +} diff --git a/app/api/controller/Login.php b/app/api/controller/Login.php new file mode 100644 index 0000000..f6704b5 --- /dev/null +++ b/app/api/controller/Login.php @@ -0,0 +1,144 @@ +middleware[SessionInit::class] = [ + 'only' => ['accountLogin', 'getCaptcha'] + ]; + } + + /** + * 验证token + * + * @param Request $request + * @return void + */ + public function validateToken(Request $request) + { + $token = $request->getCurrentToken(); + return [ + 'code' => 0, + 'data' => [ + 'exp_time' => $token->token_exp_time, + ], + 'msg' => 'ok' + ]; + } + + /** + * 西北政法大学单点登陆 + * + * @param Request $request + * @return Response + * @date 2023-01-04 + * @example + * @author admin + * @since 1.0.0 + */ + public function casOauthLogin(Request $request): Response + { + $url = $request->param('url'); + return LogicLogin::casOauthLogin( + LogicLogin::casOauthLoginHandle($url) + ); + } + + /** + * 西北政法大学单点登出 + * + * @param Request $request + * @return Response + * @date 2023-01-04 + * @example + * @author admin + * @since 1.0.0 + */ + public function casOauthLogout(Request $request): Response + { + $url = $request->param('url'); + return LogicLogin::casOauthLogout($url); + } + + /** + * 用户账号登录 + * + * @param Request $request + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function accountLogin(Request $request): array + { + $param = Validate::param([ + 'account|账号' => 'require', + 'password|密码' => 'require', + 'captcha|验证码' => $request->isProd() ? 'require|captcha' : false + ]); + $token = LogicLogin::accountLogin( + $param['account'], + $param['password'] + ); + + return [ + 'code' => 0, + 'data' => [ + 'token' => $token->token_content, + 'exp_time' => $token->token_exp_time, + ], + 'msg' => 'ok' + ]; + } + + /** + * 生成验证码 + * + * @date 2022-03-05 + * @example + * @author admin + * @since 1.0.0 + */ + public function getCaptcha() + { + return Captcha::create(); + } + + /** + * 用户登出 + * + * @param Request $request + * @date 2022-03-09 + * @example + * @author admin + * @since 1.0.0 + */ + public function userLogout(Request $request): array + { + $token = $request->getCurrentToken(); + $token->logout(); + return [ + 'code' => 0, + 'msg' => '登出成功' + ]; + } +} diff --git a/app/api/controller/Tdk/Tdk.php b/app/api/controller/Tdk/Tdk.php new file mode 100644 index 0000000..55f5fd8 --- /dev/null +++ b/app/api/controller/Tdk/Tdk.php @@ -0,0 +1,107 @@ + $it) { + $item = $it['Tables_in_shop_uniapp']; + $currentMatch = ''; + foreach ($match as $matchKey => $matchItem) { + if (stripos($item, $matchItem) === false) { + $currentMatch = ''; + } else { + $currentMatch = $item; + + break; + } + } + if ($item != $currentMatch) { + $arr[$item] = [ + 'comment' => $table_info = Db::query('SHOW TABLE STATUS LIKE ' . "'" . $item . "'")[0]['Comment'], + 'children' => Db::query('SHOW FULL COLUMNS FROM `' . $item . '`') + ]; + } + } + function createXmdStr(&$res_str, $arr) + { + foreach ($arr as $key => &$item) { + if ($item['comment'] != '') { + $res_str .= "\n{$key}\n\t" . $item['comment']; + if ($item['children']) { + foreach ($item['children'] as $childKey => &$childItem) { + if ($childItem['Comment'] == '') { + $res_str .= "\n\t\t{$childItem['Field']}"; + } else { + $childItem['Comment'] = str_replace(",", ',', $childItem['Comment']); + $childItem['Comment'] = str_replace("]", ')', $childItem['Comment']); + $childItem['Comment'] = str_replace("[", '(', $childItem['Comment']); + $res_str .= "\n\t\t{$childItem['Field']}\n\t\t\t{$childItem['Comment']}"; + } + } + } + } else { + $res_str .= "\n{$key}"; + if ($item['children']) { + foreach ($item['children'] as $childKey => &$childItem) { + if ($childItem['Comment'] == '') { + $res_str .= "\n\t{$childItem['Field']}"; + } else { + $childItem['Comment'] = str_replace(",", ',', $childItem['Comment']); + $childItem['Comment'] = str_replace("]", ')', $childItem['Comment']); + $childItem['Comment'] = str_replace("[", '(', $childItem['Comment']); + $res_str .= "\n\t{$childItem['Field']}\n\t\t{$childItem['Comment']}"; + } + } + } + } + } + } + createXmdStr($res_str, $arr); + + + echo $res_str; + return ''; + } + /** + * 获取网站tdk详情 + */ + public function getTdkInfo(Request $request): array + { + $params = $request->param(); + + $this->validate($params, ['tdk_type' => 'require']); + + $find = ModelTdk::field([ + 'tdk_id', + 'tdk_type', + 'tdk_title', + 'tdk_description', + 'tdk_keyword' + ]) + ->where('tdk_type', $params['tdk_type']) + ->find(); + + return msg(0, '获取网站tdk详情成功!', ['data' => $find]); + } +} diff --git a/app/api/middleware/Auth.php b/app/api/middleware/Auth.php new file mode 100644 index 0000000..4c16d83 --- /dev/null +++ b/app/api/middleware/Auth.php @@ -0,0 +1,85 @@ + ['*'], + 'admin' => ['*'], + 'Consult' => ['*'], + ]; + + /** + * 处理请求 + * + * @param \app\Request $request + * @param \Closure $next + * @return Response + */ + public function handle(Request $request, \Closure $next): Response + { + $this->request = $request; + + if ($this->isIgnoreLogin()) { + return $next($request); + } + + if ($request->isLogin()) { + $user = $request->getCurrentUser(); + if ($user->user_admin) { + return $next($request); + } + } + + $api = join('/', [ + $request->controller(), + $request->action() + ]); + + $token = Token::getCurrent(); + + /** + * 缓存接口权限验证 + */ + $validate = $this->validateApi($token->token_api); + /** + * 缓存权限验证不通过 + */ + if ($validate === false) { + /** + * 自定义验证权限 + * 如果验证通过刷新缓存接口 + */ + $validate = $this->validateUser(); + if ($validate) { + Token::getCurrentUser()->login(); + } + } + /** + * 权限验证通过 + */ + if ($validate) { + return $next($request); + } + throw new NotAuthApi("未授权接口 [ $api ]", 1); + } +} diff --git a/app/api/route.php b/app/api/route.php new file mode 100644 index 0000000..4198024 --- /dev/null +++ b/app/api/route.php @@ -0,0 +1,12 @@ + [ + // 接口鉴权 + // \app\api\middleware\Auth::class + ], +]; diff --git a/app/common.php b/app/common.php new file mode 100644 index 0000000..14fbb3b --- /dev/null +++ b/app/common.php @@ -0,0 +1,178 @@ +\n(\s+)/m', '] => ', ob_get_clean()); + + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, $flags); + } + + $output = '
' . $label . $output . '
'; + + if ($echo) { + echo ($output); + return; + } + + return $output; +} + +/** + * 抛出业务异常 + * + * @param string $msg + * @param integer $code + * @return void + * @throws ErrorMsg + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ +function throwErrorMsg(string $msg, int $code = 1): void +{ + throw new ErrorMsg($msg, $code); +} + +/** + * 计算年龄 + * + * @param string $birthday Y-m-d + * @return int + * @date 2022-03-11 + * @example + * @author admin + * @since 1.0.0 + */ +function sumAge($birthday): int +{ + $age = 0; + try { + if (!is_string($birthday)) { + throw new ErrorMsg("非法日期", 1); + } + $birthday = explode("-", date('Y-m-d', strtotime($birthday))); + $date = explode("-", date('Y-m-d')); + if (count($birthday) === 3) { + list($y, $m, $d) = $birthday; + } else { + list($y, $m, $d) = $date; + } + // list($y, $m, $d) = $birthday; + list($dy, $dm, $dd) = $date; + + $age = $dy - $y; + if ($dm >= $m) { + if ($dm == $m) { + if ($dd >= $d) { + } else { + $age--; + } + } + } else { + $age--; + } + return $age; + } catch (\Throwable $th) { + } + return $age; +} + +Validate::maker(function (ThinkValidate $validate) { + $validate->extend( + 'string', + function ($value) { + return is_string($value); + }, + ':attribute 数据类型非法 不是字符串' + ); +}); + +/** + * 接口返回封装 + */ +function msg(...$arr) +{ + $msg_data = ['code' => 0]; + $default_code_msg = [0 => '操作成功!', 1 => '操作失败!']; + + //data数据构建 + function constructData(&$msg_data, $code, $msg_str, &$arr2) + { + $msg_data['code'] = $code; + $msg_data['msg'] = $msg_str; + if (is_array($arr2)) { //数组 + $msg_data['data'] = $arr2; + } + if (is_object($arr2)) { //查询对象 + $obj = $arr2; + $soft_delete_field = '_delete_time'; //软删除默认后缀 + //模型层 || Db层 + if ($obj instanceof think\db\Query || $obj instanceof think\Collection) { + //join软删除字段过滤补全(暂时只适应软删除为 【前缀名_delete_time】存储为datetime 的设计) + if (isset($obj->getOptions()['join']) && $join_data = $obj->getOptions('join')) { + $join_soft_delete = []; + foreach ($join_data as $key => $join) { + $join_table_name = is_array($join[0]) ? array_keys($join[0])[0] : $join[0]; //联表表名 + $join_soft_delete[] = [$join_table_name . $soft_delete_field, 'NULL', null]; + } + $obj = $obj->where($join_soft_delete); + } + //select()、count()补全 + $msg_data['data'] = $obj->page((int) Request::param('page', 1), (int) Request::param('limit', 10))->select(); + $msg_data['count'] = $obj->count(); + } else { + return ['code' => 444, 'msg' => '对象只允许传递来自think\db\Query类和instanceof think\Collection类的实例!']; + } + } + }; + + switch (count($arr)) { + case 1: //单参数 + constructData($msg_data, 0, '查询成功!', $arr[0],); + if (is_string($arr[0])) $msg_data['msg'] = $arr[0]; + if (is_int($arr[0])) { + $msg_data['code'] = $arr[0]; + $msg_data['msg'] = $default_code_msg[$arr[0]]; + } + break; + case 2: //双参数 + if (is_int($arr[0]) && is_string($arr[1])) { + $msg_data['code'] = $arr[0]; + $msg_data['msg'] = $arr[1]; + } else { + constructData($msg_data, 0, $arr[0], $arr[1]); + } + break; + case 3: //三参数 + $msg_data['code'] = $arr[0]; + $msg_data['msg'] = $arr[1]; + if (is_object($arr[2])) { + $msg_data['data'] = $arr[2]; + } else { + foreach ($arr[2] as $key => $val) + $msg_data[$key] = $val; + } + break; + default: + return ['code' => 444, 'msg' => 'msg()最多参数只允许3个!']; + } + + return $msg_data; +} diff --git a/app/common/arw/adjfut/composer.json b/app/common/arw/adjfut/composer.json new file mode 100644 index 0000000..7991c90 --- /dev/null +++ b/app/common/arw/adjfut/composer.json @@ -0,0 +1,39 @@ +{ + "name": "arw/adjfut", + "type": "library", + "autoload": { + "psr-4": { + "arw\\adjfut\\": "src/" + } + }, + "authors": [{ + "name": "arw", + "email": "2679599887@qq.com" + }], + "require": { + "php": ">=7.1.0", + "phpoffice/phpspreadsheet": "^1.12", + "topthink/framework": "^6.0.0", + "topthink/think-filesystem": "^2.0" + }, + "config": { + "sort-packages": true + }, + "repositories": { + "packagist": { + "type": "composer", + "url": "https://mirrors.aliyun.com/composer/" + } + }, + "extra": { + "think": { + "services": [ + "arw\\adjfut\\Service\\Validate" + ], + "config": { + "wechat": "src/Config/wechat.php", + "chunkUpload": "src/Config/chunkUpload.php" + } + } + } +} diff --git a/app/common/arw/adjfut/src/ArrayFilter.php b/app/common/arw/adjfut/src/ArrayFilter.php new file mode 100644 index 0000000..dcd0ede --- /dev/null +++ b/app/common/arw/adjfut/src/ArrayFilter.php @@ -0,0 +1,170 @@ +data = $data; + } + + /** + * 二维数组排除字段 + * + * @param array $array + * @param array $except + * @return array + * @date 2023-01-13 + * @example + * @author arw + * @since 1.0.0 + */ + public static function exceptFields(array $array, array $except): array + { + foreach ($array as &$value) { + $self = new self($value); + $value = $self->except($except)->toArray(); + unset($value, $self); + } + return $array; + } + + /** + * 二维数组指定字段 + * + * @param array $array + * @param array $only + * @return array + * @date 2023-01-13 + * @example + * @author arw + * @since 1.0.0 + */ + public static function onlyFields(array $array, array $only): array + { + foreach ($array as &$value) { + $self = new self($value); + $value = $self->only($only)->toArray(); + unset($value, $self); + } + return $array; + } + + /** + * 排除某些变量 + * + * @param array $fields + * @return self + * @date 2023-01-13 + * @example + * @author arw + * @since 1.0.0 + */ + public function except(array $fields): self + { + $data = $this->data; + // $fields = [id,name] + foreach ($fields as $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + return new self($data); + } + + /** + * 获取部分变量 + * + * @param array $fields + * @return self + * @date 2023-01-13 + * @example + * @author arw + * @since 1.0.0 + */ + public function only(array $fields): self + { + $data = $this->data; + $result = []; + // $fields = [id,name] + // $fields = [id=>0,name=>asdasd] + foreach ($fields as $key => $value) { + if (is_int($key)) { + if (isset($data[$value])) { + $result[$value] = $data[$value]; + } + } else { + $result[$key] = isset($data[$key]) ? $data[$key] : $value; + } + } + return new self($result); + } + + /** + * 获取数据 + * + * @return array + * @date 2023-01-13 + * @example + * @author arw + * @since 1.0.0 + */ + public function toArray(): array + { + return $this->data; + } + + + public function __get($name) + { + return $this->offsetGet($name); + } + + public function __set($name, $value) + { + return $this->offsetSet($name, $value); + } + + public function offsetExists($offset): bool + { + return isset($this->data[$offset]); + } + + public function offsetGet($offset) + { + return $this->data[$offset]; + } + + public function offsetSet($offset, $value): void + { + $this->data[$offset] = $value; + } + + public function offsetUnset($offset): void + { + unset($this->data[$offset]); + } +} diff --git a/app/common/arw/adjfut/src/Base64.php b/app/common/arw/adjfut/src/Base64.php new file mode 100644 index 0000000..4bb6653 --- /dev/null +++ b/app/common/arw/adjfut/src/Base64.php @@ -0,0 +1,174 @@ + 'html', + 'text/css' => 'css', + 'text/javascript' => 'js', + 'image/gif' => 'gif', + 'image/png' => 'png', + 'image/jpeg' => 'jpg', + 'image/x-icon' => 'ico', + ]; + /** + * 实例化 + * + * @param string $base64 + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function __construct(string $base64) + { + $parse = self::parse($base64); + $this->base64 = $base64; + $this->type = $parse['type']; + $this->body = $parse['body']; + } + + /** + * 获取文件后缀 + * + * @return string + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function getFileExt(): string + { + return Arr::get(self::FILE_EXT_MAP, $this->type, ''); + } + + /** + * 判断是否是base64图片字符串 + * + * @return boolean + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function isImage(): bool + { + return in_array($this->type, [ + 'image/gif', + 'image/png', + 'image/jpeg', + 'image/x-icon', + ]); + } + + /** + * 保存base64图片 + * + * @param string $path + * @return void + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function saveImage(string $path): void + { + file_put_contents($path, base64_decode($this->body)); + } + + /** + * 判断是否是base64图片字符串 + * + * @param string $base64 + * @return boolean + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public static function isBase64Image(string $base64): bool + { + $ins = new self($base64); + return $ins->isImage(); + } + + /** + * 保存base64图片 + * + * @param string $base64 + * @param string $path + * @return void + */ + public static function saveBase64Image(string $base64, string $path): void + { + $ins = new self($base64); + $ins->saveImage($path); + } + + /** + * 解析base64 + * + * @param string $base64 + * @return array + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + private static function parse(string $base64): array + { + $prefix = 'data:'; + $validate = substr($base64, 0, strlen($prefix)) === $prefix; + if (!$validate) { + throw new ErrorMsg("非法base64 开头data:", 1); + } + $explode = explode(';base64,', $base64); + if (count($explode) != 2) { + throw new ErrorMsg("非法base64 不存在;base64,", 1); + } + list($type, $body) = $explode; + $type = str_replace('data:', '', $type); + return [ + 'type' => $type, + 'body' => $body + ]; + } +} diff --git a/app/common/arw/adjfut/src/ChunkUpload.php b/app/common/arw/adjfut/src/ChunkUpload.php new file mode 100644 index 0000000..1990fc2 --- /dev/null +++ b/app/common/arw/adjfut/src/ChunkUpload.php @@ -0,0 +1,258 @@ +id = $id; + if (is_string($file)) { + $file = Request::file($file); + } + if (is_array($file)) { + $file = new UploadedFile(...$file); + } + if (!($file instanceof UploadedFile)) { + throw new ErrorMsg("非法文件", 1); + } + $this->file = $file; + } + + /** + * 获取存储配置 + * + * @return Driver + * @date 2022-08-21 + * @example + * @author arw + * @since 1.0.0 + */ + public function getDisk(): Driver + { + return Filesystem::disk(self::$disk); + } + + /** + * 分片是否上传完成 + * + * @return boolean + * @date 2022-08-22 + * @example + * @author arw + * @since 1.0.0 + */ + public function isDone(): bool + { + return $this->isDone; + } + + /** + * 合并文件 + * + * @return self + * @date 2022-08-21 + * @example + * @author arw + * @since 1.0.0 + */ + public function merge(string $name): self + { + if (!$this->isDone()) { + throw new ErrorMsg("文件未上传完成 禁止合并", 1); + } + + $id = $this->id; + + $root = self::getDiskConfig('root'); + + $path = join('', [ + $root, + $id, + '.' . pathinfo($name, PATHINFO_EXTENSION) + ]); + + for ($i = 0; $i <= $this->total; $i++) { + $_path = $root . $id . DIRECTORY_SEPARATOR . $i; + file_put_contents( + $path, + file_get_contents($_path), + FILE_APPEND + ); + unlink($_path); + } + rmdir($root . $id); + $this->mergeFile = new UploadedFile($path, $name); + return $this; + } + + /** + * 获取合并文件 + * + * @return UploadedFile + * @date 2022-08-22 + * @example + * @author arw + * @since 1.0.0 + */ + public function getMergeFile(): UploadedFile + { + if (!$this->mergeFile) { + throw new ErrorMsg("未合并文件", 1); + } + return $this->mergeFile; + } + + /** + * 获取上传文件 + * + * @param string $diskName + * @param string $validate + * @return UploadFile + * @date 2022-08-22 + * @example + * @author arw + * @since 1.0.0 + */ + public function getUploadFile(string $diskName, string $validate = ''): UploadFile + { + return new UploadFile( + $diskName, + $validate, + $this->getMergeFile() + ); + } + + /** + * 保存文件 + * + * @param integer $index + * @param integer $total + * @return self + * @date 2022-08-21 + * @example + * @author arw + * @since 1.0.0 + */ + public function put(int $index, int $total): self + { + $this->total = $total; + $this->getDisk()->putFileAs( + $this->id, + $this->file, + $index + ); + $this->isDone = $index === $total; + return $this; + } + + /** + * 删除文件 + * + * @return void + * @date 2022-08-22 + * @example + * @author arw + * @since 1.0.0 + */ + public function delete(): void + { + $this->getDisk()->delete($this->getMergeFile()->getFilename()); + } + + /** + * 初始化配置 + * + * @param array $config + * @return void + * @date 2022-08-21 + * @example + * @author arw + * @since 1.0.0 + */ + public static function init(array $config): void + { + self::$disk = Arr::get($config, 'disk', ''); + $type = self::getDiskConfig('type'); + if ($type != 'local') { + throw new ErrorMsg("分片上传仅支持 local类型磁盘", 1); + } + } + + /** + * 获取磁盘配置 + * + * @param string $name + * @param mixed $default + * @return mixed + * @date 2022-08-22 + * @example + * @author arw + * @since 1.0.0 + */ + public static function getDiskConfig($name = null, $default = null) + { + return Filesystem::getDiskConfig(self::$disk, $name, $default); + } + + /** + * 生成上传唯一id + * + * @return string + * @date 2022-08-21 + * @example + * @author arw + * @since 1.0.0 + */ + public static function generateUploadId(): string + { + return md5(join('-', [ + Request::ip(), + time() + ])); + } +} diff --git a/app/common/arw/adjfut/src/Config/chunkUpload.php b/app/common/arw/adjfut/src/Config/chunkUpload.php new file mode 100644 index 0000000..6a888bb --- /dev/null +++ b/app/common/arw/adjfut/src/Config/chunkUpload.php @@ -0,0 +1,9 @@ + '' +]; diff --git a/app/common/arw/adjfut/src/Config/wechat.php b/app/common/arw/adjfut/src/Config/wechat.php new file mode 100644 index 0000000..7c798be --- /dev/null +++ b/app/common/arw/adjfut/src/Config/wechat.php @@ -0,0 +1,39 @@ + '', + // 中控层地址 + 'server_base' => 'http://clique.dszjjt.com:7001/', + // 公众号配置 + 'gzh' => [ + // 缓存key + 'cache_key' => 'wechat.gzh', + // 日志通道 + 'log_channel' => 'wechat/gzh', + // 公众号模板推送 + 'template' => [ + // {{first.DATA}} + // 申请人:{{keyword1.DATA}} + // 申请时间:{{keyword2.DATA}} + // 调课时间:{{keyword3.DATA}} + // {{remark.DATA}} + '申请审核通知' => 'iLuqzUrQM8-v_7ilxageUZoi9v-SQWipkTYVZXB1GeI' + ], + 'appid' => 'wx99caf571c9fb1cc5', + 'secret' => '61100d9c4fff5acf97517f0c8799e2e2', + ], + // 小程序配置 + 'xcx' => [ + // 缓存key + 'cache_key' => 'wechat.xcx', + // 日志通道 + 'log_channel' => 'wechat/xcx', + + 'appid' => 'wx2495492e15854b02', + 'secret' => '5b4fcb27324398083fba4a3c39e8a557', + ], +]; diff --git a/app/common/arw/adjfut/src/Curl.php b/app/common/arw/adjfut/src/Curl.php new file mode 100644 index 0000000..22f65f6 --- /dev/null +++ b/app/common/arw/adjfut/src/Curl.php @@ -0,0 +1,790 @@ + [], + /** + * http 超时 + */ + CURLOPT_TIMEOUT => 0, + /** + * http 代理 + */ + CURLOPT_PROXY => '', + /** + * http 代理端口 + */ + CURLOPT_PROXYPORT => '', + /** + * 来源页面 + */ + CURLOPT_REFERER => '', + /** + * 用户代理 + */ + CURLOPT_USERAGENT => '', + /** + * 响应中是否显示header + */ + CURLOPT_HEADER => 0 + ]; + /** + * Get请求参数 + * + * @var null + */ + private $httpParams = null; + /** + * Post请求参数 + * + * @var null + */ + private $httpData = null; + /** + * 异常信息 + * + * @var string + */ + private $error = ''; + /** + * url是否编码 + * + * @var boolean + */ + private $urlEncode = false; + /** + * 验证http请求状态码 + * + * @var boolean + */ + private $httpCode = true; + /** + * 请求路径 + * + * @var string + */ + private $path = ''; + /** + * 请求url + * + * @var string + */ + private $url = ''; + /** + * 请求类型 + * + * @var string + */ + private $method = 'get'; + /** + * 返回内容 + * + * @var string + */ + private $content = ''; + /** + * 请求信息 + * + * @var array + */ + private $status = []; + /** + * 调试模式 + * + * @var boolean + */ + private $debug = false; + /** + * 是否上传文件 + * + * @var boolean + */ + private $isUploadFile = false; + + /** + * 初始化 + * + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + public function __construct() + { + } + + /** + * 初始化请求 + * + * @return $this + */ + public static function instance() + { + return new self; + } + + /** + * 获取请求url + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * 获取请求路径 + */ + public function getPath(): string + { + return $this->path; + } + + /** + * 获取返回内容 + * + * @return string + */ + public function getContent(): string + { + return $this->content; + } + + /** + * 格式化返回 + * + * @param string $type 返回类型(默认使用 $this->dataType) + * @return mixed + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + public function formatContent(string $type = '') + { + $content = $this->content; + if (!$type) { + $type = $this->dataType; + } + switch ($type) { + case 'json': + case 'object': + return json_decode($content, $type === 'json'); + break; + default: + return $content; + break; + } + } + + /** + * 获取请求信息 + */ + public function getStatus(): array + { + return $this->status; + } + + /** + * 设置是否上传文件 + */ + public function setIsUploadFile(bool $isUploadFile): self + { + $this->isUploadFile = $isUploadFile; + return $this; + } + + /** + * 设置请求路径 + */ + public function setPath(string $path): self + { + if (count(explode('?', $path)) != 1) { + throw new Exception("请求路径不能携带参数 请使用setParams设置请求参数", 1); + } + $this->path = $path; + return $this; + } + + /** + * 设置调试模式 + */ + public function setDeBug(bool $debug): self + { + $this->debug = $debug; + return $this; + } + + /** + * 验证http状态码 + * + * @param boolean|int $code + */ + public function setHttpCode($code): self + { + $this->httpCode = $code; + return $this; + } + + /** + * 设置url是否编码 + */ + public function setUrlEncode(bool $urlEncode): self + { + $this->urlEncode = $urlEncode; + return $this; + } + + /** + * 设置请求前缀 + */ + public function setBaseUrl(string $baseUrl): self + { + if (count(explode('?', $baseUrl)) != 1) { + throw new Exception("请求前缀不能携带参数 请使用setParams设置请求参数", 1); + } + $this->baseUrl = $baseUrl; + return $this; + } + + /** + * 设置返回数据类型 + */ + public function setDataType(string $dataType): self + { + $this->dataType = $dataType; + return $this; + } + + /** + * 设置http header + */ + public function setHeader(array $header): self + { + $_header = []; + foreach ($header as $key => $value) { + if (is_numeric($key)) { + $_header[] = $value; + } else { + $_header[] = "$key: $value"; + } + } + $this->config[CURLOPT_HTTPHEADER] = array_merge($this->config[CURLOPT_HTTPHEADER], $_header); + return $this; + } + + /** + * 设置http 超时 + */ + public function setTimeout(int $time): self + { + $this->config[CURLOPT_TIMEOUT] = $time <= 0 ? 5 : $time; + return $this; + } + + /** + * 设置http 代理 + */ + public function setProxy(string $proxy): self + { + $this->config[CURLOPT_PROXY] = $proxy; + return $this; + } + + /** + * 设置http 代理端口 + */ + public function setProxyPort(int $port): self + { + $this->config[CURLOPT_PROXYPORT] = $port; + return $this; + } + + /** + * 设置来源页面 + */ + public function setReferer(string $referer = ""): self + { + $this->config[CURLOPT_REFERER] = $referer; + return $this; + } + + /** + * 设置用户代理 + */ + public function setUserAgent(string $agent = ""): self + { + $this->config[CURLOPT_USERAGENT] = $agent; + return $this; + } + + /** + * http响应中是否显示header + */ + public function showResponseHeader(bool $show): self + { + $this->config[CURLOPT_HEADER] = $show ? 1 : 0; + return $this; + } + + /** + * 获取get请求参数 + */ + public function getParams() + { + return $this->httpParams; + } + + /** + * 设置get请求的参数 + */ + public function setParams($params): self + { + $_params = $this->trigger('beforeSetParams', [$params]); + $this->httpParams = $_params ? $_params : $params; + return $this; + } + + /** + * 获取post请求参数 + */ + public function getData() + { + return $this->httpData; + } + + /** + * 设置post请求的参数 + */ + public function setData($data): self + { + $_data = $this->trigger('beforeSetData', [$data]); + $this->httpData = $_data ? $_data : $data; + return $this; + } + + /** + * 设置http请求的cookie信息 + */ + public function setCookie(array $cookie): self + { + $_cookie = []; + foreach ($cookie as $key => $value) { + $_cookie[] = "$key=$value"; + } + $this->config[CURLOPT_COOKIE] = join(';', $_cookie); + return $this; + } + + /** + * 设置证书路径 + */ + public function setCainfo(string $file): self + { + $this->config[CURLOPT_CAINFO] = $file; + return $this; + } + + /** + * 获取请求异常 + */ + public function getError(): string + { + return $this->error; + } + + /** + * 绑定 + * + * @param string $name + * @param callable $cb + * @date 2022-03-26 + * @example + * @author arw + * @since 1.0.0 + */ + public function bind(string $name, callable $cb): self + { + $this->bind[$name] = $cb; + return $this; + } + + /** + * GET请求 + * @param string $path + * @return mixed + */ + public function get(string $path = '') + { + if ($path) { + $this->setPath($path); + } + $this->method = 'get'; + $respone = $this->fetch(); + if (!$path) { + return $this; + } + return $respone; + } + + /** + * POST请求 + * + * @param string $path + * @return mixed + */ + public function post(string $path = '') + { + if ($path) { + $this->setPath($path); + } + $this->method = 'post'; + $respone = $this->fetch(); + if (!$path) { + return $this; + } + return $respone; + } + + /** + * PUT请求 + * + * @param string $path + * @return mixed + */ + public function put(string $path = '') + { + if ($path) { + $this->setPath($path); + } + $this->method = 'put'; + $respone = $this->fetch(); + if (!$path) { + return $this; + } + return $respone; + } + + /** + * DELETE请求 + * + * @param string $path + * @return mixed + */ + public function delete(string $path = '') + { + if ($path) { + $this->setPath($path); + } + $this->method = 'delete'; + $respone = $this->fetch(); + if (!$path) { + return $this; + } + return $respone; + } + + /** + * PATCH请求 + * + * @param string $path + * @return mixed + */ + public function patch(string $path = '') + { + if ($path) { + $this->setPath($path); + } + $this->method = 'patch'; + $respone = $this->fetch(); + if (!$path) { + return $this; + } + return $respone; + } + + /** + * 设置get传参前 + * + * @param callable $cb($params) + * @param mixed $params 参数 + * @date 2022-03-26 + * @example + * @author arw + * @since 1.0.0 + */ + public function onBeforeSetParams(callable $cb): self + { + return $this->event('beforeSetParams', $cb); + } + + /** + * 设置post传参前 + * + * @param callable $cb($data) + * @param mixed $data 参数 + * @date 2022-03-26 + * @example + * @author arw + * @since 1.0.0 + */ + public function onBeforeSetData(callable $cb): self + { + return $this->event('beforeSetData', $cb); + } + + /** + * 请求前 + * + * @param callable $cb($ch) + * @param self $ch + * @date 2022-03-26 + * @example + * @author arw + * @since 1.0.0 + */ + public function onBeforeFetch(callable $cb): self + { + return $this->event('beforeFetch', $cb); + } + + /** + * 请求后 + * + * @param callable $cb($ch,$info) + * @param self $ch + * @param array $info + * @param string $info[url] 请求完整url + * @param string $info[content] 返回信息 + * @param array $info[status] 请求信息 + * @date 2022-03-26 + * @example + * @author arw + * @since 1.0.0 + */ + public function onAfterFetch(callable $cb) + { + return $this->event('afterFetch', $cb); + } + + /** + * 请求异常(返回true阻止抛出异常) + * + * @param callable $cb($ch,$th) + * @param self $ch + * @param \Throwable $th + * @date 2022-03-26 + * @example + * @author arw + * @since 1.0.0 + */ + public function onFetchError(callable $cb) + { + return $this->event('fetchError', $cb); + } + + /** + * 发起请求 + * + * @return mixed + * @date 2022-03-25 + * @example + * @author arw + * @since 1.0.0 + */ + public function fetch() + { + $this->url = $url = $this->baseUrl . $this->path; + + $this->trigger('beforeFetch', [$this]); + + try { + $httpParams = $this->httpParams; + if (!empty($httpParams) && is_array($httpParams)) { + $url .= (strpos($url, '?') === false ? '?' : '') . http_build_query($httpParams); + } + + if ($this->urlEncode === false) { + $url = urldecode($url); + } + + $ch = $this->curlInit($url); + // 设为TRUE把curl_exec()结果转化为字串,而不是直接输出 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + $this->content = $content = curl_exec($ch); + $this->status = $status = curl_getinfo($ch); + + if ($content === false) { + $this->error = curl_error($ch); + } + + if ($this->debug) { + dump([ + 'ch' => $ch, + 'url' => $url, + 'httpParams' => $this->httpParams, + 'httpData' => $this->httpData, + 'contentArray' => json_decode($content, true), + 'content' => $content, + 'status' => $status + ]); + } + curl_close($ch); + + $httpCode = $this->httpCode; + + if ($httpCode === true) { + $httpCode = 200; + } + if (is_numeric($httpCode)) { + if (!isset($status['http_code'])) { + throw new Exception("服务器未返回状态码", 1); + } + if ($status['http_code'] !== $httpCode) { + throw new Exception(sprintf("服务器返回状态码异常[ %s ]", $status['http_code']), 1); + } + } + + $this->trigger('afterFetch', [$this, [ + 'url' => $url, + 'content' => $content, + 'status' => $status + ]]); + + return $this->formatContent(); + } catch (\Throwable $th) { + if (!$this->trigger('fetchError', [$this, $th])) { + throw $th; + } + } + } + + public function __call($name, $arguments) + { + array_unshift($arguments, $this); + call_user_func_array($this->bind[$name], $arguments); + } + + /** + * 事件 + * + * @param string $type beforeSetParams | beforeSetData | beforeFetch | AfterFetch + * @param callable $cb + */ + private function event(string $type, callable $cb): self + { + $this->event[strtoupper($type)] = $cb; + return $this; + } + + /** + * 事件触发器 + * + * @param string $type + * @return mixed + * @date 2022-03-25 + * @example + * @author arw + * @since 1.0.0 + */ + private function trigger(string $type, array $args = []) + { + $type = strtoupper($type); + if (isset($this->event[$type])) { + return call_user_func_array($this->event[$type], $args); + } + } + + /** + * 初始化请求 + * + * @param string $url + * @return resource|CurlHandle + * @date 2022-04-24 + * @example + * @author arw + * @since 1.0.0 + */ + private function curlInit(string $url) + { + $ch = curl_init($url); + if (stripos($url, 'https://') !== false) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_SSLVERSION, 1); + } + + foreach ($this->config as $option => $value) { + if (!$value && $option != CURLOPT_HEADER) { + continue; + } + curl_setopt($ch, $option, $value); + } + + switch ($this->method) { + case 'get': + break; + case 'post': + curl_setopt($ch, CURLOPT_POST, true); + break; + default: + // delete put patch + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method)); + break; + } + $httpData = $this->httpData; + // 设置post body + if ($httpData) { + $data = null; + if (is_array($httpData)) { + if ($this->isUploadFile) { + $data = $httpData; + } else { + $data = http_build_query($httpData); + } + } else if (is_string($httpData)) { + $data = $httpData; + } + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + return $ch; + } +} diff --git a/app/common/arw/adjfut/src/Excel.php b/app/common/arw/adjfut/src/Excel.php new file mode 100644 index 0000000..51ccaa6 --- /dev/null +++ b/app/common/arw/adjfut/src/Excel.php @@ -0,0 +1,519 @@ +getRealPath(); + } + $this->spreadsheet = self::loadExcel($path); + $this->path = $path; + } else { + $this->spreadsheet = new Spreadsheet(); + } + } + + /** + * 设置工作表样式 + * + * @param array $options + * @param boolean $options[autoBorder] 是否自动添加边框 + * @param boolean $options[freezeLine] 是否自动冻结首行 + * @return self + */ + public function setWorksheetStyle(array $options): self + { + $this->worksheetStyle = $options; + return $this; + } + + /** + * 获取当前Spreadsheet + * + * @return Spreadsheet + * @date 2020-11-23 + * @example + * @author arw + * @since 1.0.0 + */ + public function getSpreadsheet(): Spreadsheet + { + return $this->spreadsheet; + } + + /** + * 读取excel + * + * @param string $path excel路径 + * @return Spreadsheet + */ + public static function loadExcel(string $path): Spreadsheet + { + if (!is_file($path)) { + throw new ErrorMsg("文件不存在 $path", 1); + } + $ext = pathinfo($path, PATHINFO_EXTENSION); + return IOFactory::createReader(ucfirst($ext))->load($path); + } + + /** + * 获取工作表 + * 默认获取当前激活的 + * + * @param string $sheet + * @return Worksheet + */ + public function getWorksheet(string $sheet = 'active'): Worksheet + { + $Spreadsheet = $this->getSpreadsheet(); + if ($sheet === 'active') { + $Worksheet = $Spreadsheet->getActiveSheet(); + } else { + $Worksheet = $Spreadsheet->getSheet($sheet); + } + return $Worksheet; + } + + /** + * 获取excel数据 + * + * @param string $sheet + * @return array + */ + public function getExcelData(string $sheet = 'active'): array + { + return $this->getWorksheet($sheet)->toArray(); + } + + /** + * 解析excel + * + * @param array $rules + * @param string $rules[][title] 表头 + * @param string $rules[][validate] 验证器 + * @param string $rules[][field] 字段名 + * @param string $rules[][type] 数据类型 date:日期格式(Y-m-d H:i:s) + * @param array $options + * @param array $options[titleLine] 表头行 + * @param boolean $options[autoRemove] 解析后移除文件 + * @param boolean $options[ignoreNullRow] 忽略空行 + * @return array + * @date 2022-04-11 + * @example + * @author arw + * @since 1.0.0 + */ + public function parseExcel(array $rules, array $options = []): array + { + $titleLine = Arr::get($options, 'titleLine', [1]); + $autoRemove = Arr::get($options, 'autoRemove', true); + $ignoreNullRow = Arr::get($options, 'ignoreNullRow', true); + + if ($autoRemove) { + if (is_file($this->path)) { + unlink($this->path); + } + } + + $title = []; + $_title = []; + $_field = []; + $_type = []; + $_validate = []; + foreach ($rules as $rule) { + $_title[] = $rule['title']; + if (isset($rule['field'])) { + $_field[] = $rule['field']; + if (isset($rule['validate'])) { + $_validate[join('|', [ + $rule['field'], + $rule['title'] + ])] = $rule['validate']; + } + } else { + $_field[] = ''; + } + if (isset($rule['type'])) { + $_type[] = $rule['type']; + } else { + $_type[] = ''; + } + } + $Worksheet = $this->getWorksheet(); + $data = $Worksheet->toArray(); + // 获取表头 合并复杂表头 + foreach ($titleLine as $line) { + $values = $data[$line - 1]; + foreach ($values as $key => $value) { + if ($value) { + $title[$key] = $value; + } + } + } + // 验证表头 + if ($title != $_title) { + throw new ErrorMsg("表头不一致 请确认excel是否正确", 1); + } + // 去除非数据字段 + $i = max($titleLine) - 1; + do { + unset($data[$i]); + $i--; + } while ($i >= 0); + + $validate = new Validate; + $validate->rule($_validate)->batch(true)->failException(true); + + $result = []; + $error = []; + foreach ($data as $row => $value) { + $index = $row + 1; + $line = "第${index}行"; + $_value = []; + if ($ignoreNullRow && self::isNullRow($value)) { + continue; + } + foreach ($_field as $column => $field) { + if ($field) { + if ($_type[$column]) { + $type = $_type[$column]; + if (Str::startsWith($type, 'date') && $value[$column]) { + $format = 'Y-m-d H:i:s'; + $types = explode(':', $type); + if (isset($types[1])) { + $format = $types[1]; + } + $value[$column] = Tool::conversionDateTime($value[$column], $format); + } + } + $_value[$field] = $value[$column]; + } + } + // 验证数据 + if ($_validate) { + try { + $validate->check($_value); + } catch (\Throwable $th) { + $error[] = $line . ':' . $th->getMessage(); + } + } + + $result[$line] = $_value; + } + + if ($error) { + throw new ErrorMsg(join(PHP_EOL, $error), 1); + } + + return $result; + } + + /** + * 导出excel + * + * @param array $data 数据 + * @param array $options + * @param boolean $options[autoBorder] 是否自动添加边框 + * @param string $options[freezeLine] 是否自动冻结首行 + * @date 2022-04-11 + * @example + * @author arw + * @since 1.0.0 + */ + public static function exportSheet(array $data, array $options = []) + { + $excel = new self; + $excel->setWorksheetStyle($options); + $spreadsheet = $excel->getSpreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray($data); + return $excel; + } + + /** + * 导出处理图片字段 + */ + public static function ExportImgFiled($value) + { + if(strpos($value,"http") !== false){ + $base_url = ""; + }else{ + $base_url = Env::get('APP.DEFAULT_IMG_URL'); + } + return $base_url . $value; + } + + /** + * 导出excel 多sheet + * + * @param array $data 数据 + * @param array $options + * @param boolean $options[autoBorder] 是否自动添加边框 + * @param string $options[freezeLine] 是否自动冻结首行 + * @date 2022-04-11 + * @example + * @author arw + * @since 1.0.0 + */ + public static function exportMoreSheet(array $data, array $options = []) + { + $excel = new self; + $excel->setWorksheetStyle($options); + $spreadsheet = $excel->getSpreadsheet(); + $spreadsheet->removeSheetByIndex(0); + foreach ($data as $key => $value) { + $worksheet = new Worksheet($spreadsheet, $key); + $worksheet->fromArray($value); + $spreadsheet->addSheet($worksheet); + } + $spreadsheet->setActiveSheetIndex(0); + return $excel; + } + + /** + * 保存excel + * + * @param string $name 文件名 + * @param string $save 保存路径 + * @date 2022-04-11 + * @example + * @author arw + * @since 1.0.0 + */ + public function save(string $name, string $save = 'php://output') + { + $spreadsheet = $this->getSpreadsheet(); + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $_name = $name; + if (!preg_match("/\./", $name)) { + $_name .= '.Xlsx'; + } + $worksheets = $spreadsheet->getAllSheets(); + foreach ($worksheets as $worksheet) { + $this->_setWorksheetStyle($worksheet); + } + // 保存前 + $this->trigger('beforeSave', [$spreadsheet]); + // 浏览器下载 + if ($save === 'php://output') { + Tool::obEndClean(); + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header('Cache-Control: max-age=0'); + header('Content-Disposition:inline;filename="' . $_name . '"'); + $writer->save($save); + } else { + Tool::mkdir($save); + // 保存到本地目录 + $writer->save(join(DIRECTORY_SEPARATOR, [ + $save, $_name, + ])); + } + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + // 保存后 + $this->trigger('AfterSave'); + } + + /** + * 保存前 + * + * @param callable $cb + * @date 2022-04-11 + * @example + * @author arw + * @since 1.0.0 + */ + public function onBeforeSave(callable $cb): self + { + return $this->event('beforeSave', $cb); + } + + /** + * 保存后 + * + * @param callable $cb + * @date 2022-04-11 + * @example + * @author arw + * @since 1.0.0 + */ + public function onAfterSave(callable $cb): self + { + return $this->event('afterSave', $cb); + } + + /** + * 判断是否空行 + * + * @param array $rows + * @return boolean + * @date 2021-11-27 + * @example + * @author arw + * @since 1.0.0 + */ + public static function isNullRow(array $rows) + { + $rows = array_unique($rows); + $rows = array_filter($rows, function ($v) { + return !is_null($v); + }); + return count($rows) == 0; + } + + /** + * 快捷设置excel样式 + * 首行冻结 自动添加边框 + * + * @param Worksheet $worksheet + * @return Worksheet $worksheet + * @date 2021-01-25 + * @author arw + * @since 1.0.0 + */ + private function _setWorksheetStyle(Worksheet $worksheet): Worksheet + { + $options = $this->worksheetStyle; + $autoBorder = Arr::get($options, 'autoBorder', true); + $freezeLine = Arr::get($options, 'freezeLine', 'A2'); + $autoBackground = Arr::get($options, 'autoBackground', true); + if ($freezeLine) { + $worksheet->freezePane($freezeLine); + } + if ($autoBorder) { + $worksheet->getStyle(join(':', [ + 'A1', + $worksheet->getHighestDataColumn() . $worksheet->getHighestDataRow() + ]))->applyFromArray([ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, //细边框 + ], + ], + ]); + } + if($autoBackground){ + $style = join(':', [ + 'A1', + $worksheet->getHighestDataColumn() . "1" + ]); + + // 列宽 + $worksheet -> getDefaultColumnDimension() -> setWidth(25); + // $worksheet -> getColumnDimension('A') -> setWidth(30); //设置A列宽度为30 + //行高 + // $worksheet -> getRowDimension(1) -> setRowHeight(25); + $worksheet -> getDefaultRowDimension() -> setRowHeight(25); + + //样式设置 - 字体 + $worksheet -> getStyle($style) -> getFont() + // -> setBold(true) + ->setName('微软雅黑') + -> setSize(13); + + //样式设置 - 字体颜色 + // $worksheet -> getStyle('A1') -> getFont() + // -> getColor() -> setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); //设置单元格A1的字体颜色 + + // $font = $worksheet -> getCell('B1') -> getColor() -> setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); + + // if(substr($font,0,1) == "*"){ + + // } + + // $worksheet -> setCellValue('A2', substr($font,0,1) ); + + //样式设置 - 水平、垂直居中 + $styleArray = [ + 'alignment' => [ + 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER, + 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER + ], + ]; + $worksheet -> getStyle($style) -> applyFromArray($styleArray); + + //样式设置 - 单元格背景颜色 + $worksheet->getStyle($style)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('FFFFFF00'); + } + return $worksheet; + } + + /** + * 数字转字母 + * 1 => A + * + * @param integer $index + * @return string + * @date 2021-01-23 + * @author arw + * @since 1.0.0 + */ + public static function stringToIndex(int $index) + { + return Coordinate::stringFromColumnIndex($index); + } + + /** + * 字母转数字 + * A => 1 + * + * @param string $string + * @return int + * @date 2021-01-23 + * @author arw + * @since 1.0.0 + */ + public static function indexToString(string $string) + { + return Coordinate::columnIndexFromString($string); + } +} diff --git a/app/common/arw/adjfut/src/Exception/ErrorMsg.php b/app/common/arw/adjfut/src/Exception/ErrorMsg.php new file mode 100644 index 0000000..437d9ce --- /dev/null +++ b/app/common/arw/adjfut/src/Exception/ErrorMsg.php @@ -0,0 +1,12 @@ +pivotClass = $pivotClass; + $this->masterClass = $masterClass; + $this->subClass = $subClass; + $this->softDelete = $softDelete; + + $this->pivotPk = (new $pivotClass)->getPk(); + $this->masterPk = (new $masterClass)->getPk(); + $this->subPk = (new $subClass)->getPk(); + } + + /** + * 绑定 + * + * @param array|Collection $masters + * @param array|Collection $subs + * @param array $extra 中间表额外数据 + * @return void + * @date 2022-12-29 + * @example + * @author arw + * @since 1.0.0 + */ + public function bind($masters, $subs, array $extra = []): void + { + $this->_bind($masters, $subs, $extra, false); + } + + /** + * 解绑 + * + * @param array|Collection $masters + * @param array|Collection $subs + * @return void + * @date 2022-12-29 + * @example + * @author arw + * @since 1.0.0 + */ + public function unbind(array $masters, array $subs): void + { + $masters = self::formatModel($masters, $this->masterClass); + $subs = self::formatModel($subs, $this->subClass); + + $pivotClass = $this->pivotClass; + + $pivotPk = $this->pivotPk; + $masterPk = $this->masterPk; + $subPk = $this->subPk; + + foreach ($masters as $master) { + // 查询已绑定的数据 + $subPks = $pivotClass::where([ + $masterPk => $master->$masterPk + ])->column($subPk); + foreach ($subs as $sub) { + if (in_array($sub->$subPk, $subPks)) { + $pivotClass::where([ + $subPk => $sub->$subPk, + $masterPk => $master->$masterPk + ])->field([ + $pivotPk + ])->select()->delete(); + } + } + } + } + + /** + * 重新绑定 + * + * @param array|Collection $masters + * @param array|Collection $subs + * @param array $extra 中间表额外数据 + * @return void + * @date 2022-12-29 + * @example + * @author arw + * @since 1.0.0 + */ + public function rebind($masters, $subs, array $extra = []): void + { + $this->_bind($masters, $subs, $extra, true); + } + + /** + * 格式化模型 + * + * @param array|Collection $models + * @param string $class + * @return Collection + * @date 2022-12-29 + * @example + * @author arw + * @since 1.0.0 + */ + private static function formatModel($models, string $class): Collection + { + if ($models instanceof Collection) { + return $models; + } else if (is_array($models)) { + $Collection = new Collection(); + foreach ($models as $model) { + if ($model instanceof $class) { + } else if (is_string($model) || is_numeric($model)) { + $model = $class::find($model); + } + $Collection->push($model); + } + return $Collection; + } else { + throw new ErrorMsg("类型异常", 1); + } + } + + /** + * 绑定数据 + * + * @param array|Collection $masters + * @param array|Collection $subs + * @param array $extra + * @param boolean $rebind + * @return void + * @date 2022-12-29 + * @example + * @author arw + * @since 1.0.0 + */ + private function _bind($masters, $subs, array $extra = [], bool $rebind = false) + { + $masters = self::formatModel($masters, $this->masterClass); + $subs = self::formatModel($subs, $this->subClass); + + $pivotClass = $this->pivotClass; + + $softDelete = $this->softDelete; + + $pivotPk = $this->pivotPk; + $masterPk = $this->masterPk; + $subPk = $this->subPk; + + foreach ($masters as $master) { + if ($rebind) { + $pivots = $pivotClass::where([ + [$masterPk, '=', $master->$masterPk], + [$subPk, 'not in', $subs->column($subPk)], + ])->select(); + if (!$pivots->isEmpty()) { + // 重新绑定 + $this->unbind([$master], $pivots->column($subPk)); + } + } + foreach ($subs as $sub) { + $pivot = null; + if ($softDelete) { + $pivot = $pivotClass::withTrashed()->where([ + $subPk => $sub->$subPk, + $masterPk => $master->$masterPk + ])->find(); + } else { + $pivot = $pivotClass::where([ + $subPk => $sub->$subPk, + $masterPk => $master->$masterPk + ])->find(); + } + if (is_null($pivot) || $pivot->isEmpty()) { + $pivotClass::create($extra + [ + $subPk => $sub->$subPk, + $masterPk => $master->$masterPk + ]); + } else { + if ($softDelete) { + $pivot->restore(); + } + if ($extra) { + $pivotClass::update($extra, [ + $pivotPk => $pivot->$pivotPk + ]); + } + } + } + } + } +} diff --git a/app/common/arw/adjfut/src/PartitionTable.php b/app/common/arw/adjfut/src/PartitionTable.php new file mode 100644 index 0000000..d0e5a71 --- /dev/null +++ b/app/common/arw/adjfut/src/PartitionTable.php @@ -0,0 +1,261 @@ +originTable = $table; + $this->targetTable = join('_', [$table, $suffix]); + + $this->checkTargetTable(); + } + + /** + * 是否锁表 + * + * @param boolean|string $lock + * @return self + * @date 2023-01-04 + * @example + * @author arw + * @since 1.0.0 + */ + public function setLockTable($lock): self + { + $this->lockTable = $lock; + return $this; + } + + /** + * 设置唯一字段(设置后将删除原表数据) + * + * @param string $uniqueField + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + public function setUniqueField(string $uniqueField): self + { + $this->uniqueField = $uniqueField; + return $this; + } + + /** + * 设置查询条件 + * + * @param callable $cb + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + public function setQuery(callable $cb): self + { + $this->query = call_user_func($cb, $this->getOriginDb()); + return $this; + } + + /** + * 跨月查询 + * + * @param string $table + * @param string $field 字段 + * @param array $option + * @param array $option[start] 开始时间 Y-m-d H:i:s + * @param array $option[end] 结束时间 Y-m-d H:i:s + * @date 2022-04-06 + * @example + * @author arw + * @since 1.0.0 + */ + public static function queryForMonth(string $table, string $field, array $option): Query + { + $start = Arr::get($option, 'start'); + $end = Arr::get($option, 'end'); + if (!$start || !$end) { + throw new \Exception("缺少时间限制", 1); + } + $result = Db::query("SHOW TABLES LIKE '${table}_%'"); + $tables = []; + foreach ($result as $value) { + $tables = array_merge($tables, array_values($value)); + } + $startYm = date('Ym', strtotime($start)); + $endYm = date('Ym', strtotime($end)); + $months = []; + $months[] = $startYm; + + if ($startYm != $endYm) { + $current = $startYm; + while (true) { + $current = date('Ym', strtotime($current . "01 +1month")); + $months[] = $current; + if ($current === $endYm) { + break; + } + } + // $months[] = $endYm; + } + + $temp = array_filter($tables, function ($v) use ($table, $months) { + return in_array(explode("${table}_", $v)[1], $months); + }); + + $query = Db::table(in_array(date('Ym'), $months) ? $table : array_shift($temp)); + + $query->union(array_map(function ($v) { + return Db::name($v)->fetchSql()->select(); + }, $temp)); + + return Db::table(join(' ', [ + $query->buildSql(), + $table + ]))->whereBetweenTime($field, $start, $end); + } + + /** + * 迁移数据 + * + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + public function save() + { + Db::startTrans(); + try { + $lockTable = $this->lockTable; + if ($lockTable) { + $this->getOriginDb()->lock($lockTable)->select(); + $this->getTargetDb()->lock($lockTable)->select(); + } + + $target = $this->targetTable; + $originSql = $this->query->fetchSql()->select(); + $result = Db::execute("REPLACE INTO `$target` $originSql;"); + if ($result === false) { + throw new \Exception("迁移数据失败", 1); + } + + $uniqueField = $this->uniqueField; + if ($uniqueField) { + $this->getTargetDb()->field([ + $uniqueField + ])->chunk(500, function (Collection $ids) use ($uniqueField) { + $this->getOriginDb()->whereIn( + $uniqueField, + $ids->column($uniqueField) + )->delete(); + }, $uniqueField); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + /** + * 获取原始表 + * + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + private function getOriginDb(): Query + { + return Db::name($this->originTable); + } + + /** + * 获取目标表 + * + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + private function getTargetDb(): Query + { + return Db::name($this->targetTable); + } + + /** + * 检查目标表 + * + * @return void + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + private function checkTargetTable(): void + { + if (!in_array($this->targetTable, Db::getTables())) { + $origin = $this->originTable; + $target = $this->targetTable; + $result = Db::execute("CREATE TABLE `$target` LIKE `$origin`"); + if ($result === false) { + throw new \Exception("创建迁移表失败", 1); + } + } + } +} diff --git a/app/common/arw/adjfut/src/ReTry.php b/app/common/arw/adjfut/src/ReTry.php new file mode 100644 index 0000000..58a482a --- /dev/null +++ b/app/common/arw/adjfut/src/ReTry.php @@ -0,0 +1,250 @@ +setHandle($handle); + $ins->setLimit($limit); + return $ins; + } + /** + * 自增 + * + * @param integer $step 步长 + * @return self + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + public function inc(int $step = 1): self + { + $this->limit += $step; + return $this; + } + /** + * 自减 + * + * @param integer $step + * @return self + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + public function dec(int $step = 1): self + { + $this->limit -= $step; + return $this; + } + + /** + * 获取执行次数 + * + * @return integer + * @date 2022-12-30 + * @example + * @author arw + * @since 1.0.0 + */ + public function getCount(): int + { + return $this->count; + } + + /** + * 获取执行上限 + * + * @return integer + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + public function getLimit(): int + { + return $this->limit; + } + + /** + * 设置执行上限 + * + * @param integer $limit + * @return self + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + public function setLimit(int $limit): self + { + $this->limit = $limit; + $this->last_limit = $limit; + return $this; + } + + /** + * 设置执行器 + * + * @param callable $cb + * @return self + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + public function setHandle(callable $cb): self + { + $this->handle = $cb; + return $this; + } + + /** + * 执行是否成功 + * + * @param boolean $success + * @return self + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + public function success(bool $success): self + { + $this->success = $success; + return $this; + } + + /** + * 重试上限 + * + * @param callable $cb + * @return self + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + public function onRetryMax(callable $cb): self + { + $this->event('reTryMax', $cb); + return $this; + } + + /** + * 执行异常 + * + * @param callable $cb + * @return self + * @date 2022-12-30 + * @example + * @author arw + * @since 1.0.0 + */ + public function onError(callable $cb): self + { + $this->event('error', $cb); + return $this; + } + + /** + * 执行 + * + * @param mixed ...$args + * @return void + * @date 2022-12-30 + * @example + * @author arw + * @since 1.0.0 + */ + public function run(...$args) + { + $this->reset(); + array_unshift($args, $this); + $handle = $this->handle; + $res = null; + while (true) { + try { + $res = call_user_func_array($handle, $args); + } catch (\Throwable $th) { + $this->trigger('error', [$th]); + } + $this->count++; + // 执行成功 + if ($this->success) { + break; + } + // 执行上限 + if ($this->count > $this->limit) { + // 不减1会导致 获取到的执行次数多1 + $this->count--; + $this->trigger('reTryMax'); + break; + } + } + return $res; + } + + /** + * 重置执行器 + * + * @return void + * @date 2022-12-30 + * @example + * @author arw + * @since 1.0.0 + */ + private function reset(): void + { + $this->count = 1; + $this->setLimit($this->last_limit); + } +} diff --git a/app/common/arw/adjfut/src/Service/Validate.php b/app/common/arw/adjfut/src/Service/Validate.php new file mode 100644 index 0000000..514bdf4 --- /dev/null +++ b/app/common/arw/adjfut/src/Service/Validate.php @@ -0,0 +1,68 @@ +extend('string', function ($value) { + return is_string($value); + }, ':attribute 数据类型非法 不是字符串'); + + $validate->extend('json', function ($value) { + try { + $value = json_decode($value, true); + return is_array($value); + } catch (\Throwable $th) { + // throw $th; + } + return false; + }, ':attribute 数据格式错误 不是json字符串'); + + $validate->extend('modelHas', function ($value, $rule) { + $rules = explode(',', $rule); + $class = Arr::get($rules, 0); + $pk = Arr::get($rules, 1); + if (!class_exists($class)) { + throw new ErrorMsg("$class 类不存在"); + } + /** + * @var \think\Model + */ + $model = new $class; + if (!($model instanceof Model)) { + throw new ErrorMsg("$class 该类未继承 \\think\\Model"); + } + if (!$pk) { + $pk = $model->getPk(); + } + return (bool) $model->where($pk, $value)->value($pk); + }, ':attribute 数据不存在'); + }); + } +} diff --git a/app/common/arw/adjfut/src/Tool.php b/app/common/arw/adjfut/src/Tool.php new file mode 100644 index 0000000..10c7770 --- /dev/null +++ b/app/common/arw/adjfut/src/Tool.php @@ -0,0 +1,196 @@ +$type(json_encode($data, JSON_UNESCAPED_UNICODE)); + } + + /** + * 字段映射 + * + * @param array $data + * @param array $map + * @param array $options + * @return array + * @date 2022-06-23 + * @example + * @author arw + * @since 1.0.0 + */ + public static function fieldMap(array $data, array $map, array $options = []): array + { + $prefix = Arr::get($options, 'prefix', ''); + $result = []; + foreach ($data as $key => $value) { + if (isset($map[$key])) { + $key = $map[$key]; + } + if ($prefix) { + $key = $prefix . $key; + } + $result[$key] = $value; + } + return $result; + } + + /** + * 转换日期格式 + * + * @param string $datetime + * @param string $format + * @return string + * @date 2022-04-20 + * @example + * @author arw + * @since 1.0.0 + */ + public static function conversionDateTime($datetime, string $format): string + { + try { + $date = new DateTime($datetime); + return $date->format($format); + } catch (\Throwable $th) { + throw new ErrorMsg('日期格式非法', 1); + } + } + + /** + * 生成唯一id + * + * @param string|array $value + * @return string + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + public static function generateGuid($value = ''): string + { + $str = ''; + if (is_array($value)) { + $str = json_encode($value); + } + $charid = strtolower(md5($str . uniqid('', true))); + $hyphen = chr(45); // '-' + $uuid = //chr(123)// '{' + substr($charid, 0, 8) . $hyphen + . substr($charid, 8, 4) . $hyphen + . substr($charid, 12, 4) . $hyphen + . substr($charid, 16, 4) . $hyphen + . substr($charid, 20, 12); + //.chr(125);// '}' + return $uuid; + } + + + /** + * 清空(擦除)缓冲区并关闭输出缓冲 + * + * @return void + */ + public static function obEndClean(): void + { + try { + while (ob_get_level() > 0) { + ob_end_clean(); + } + } catch (\Throwable $th) { + //throw $th; + } + } + + /** + * 自动创建文件夹 + * + * @param string $path 文件夹路径 + * @param integer $mode 权限 + * @return boolean + */ + public static function mkdir(string $path, int $mode = 0755): bool + { + $path = str_replace('/', DIRECTORY_SEPARATOR, $path); + $temp = explode(DIRECTORY_SEPARATOR, $path); + if (preg_match("/\./", end($temp))) { + // pathinfo 不支持中文 + // $path = pathinfo($path, PATHINFO_DIRNAME); + array_splice($temp, count($temp) - 1, 1); + $path = join(DIRECTORY_SEPARATOR, $temp); + } + if (!file_exists($path)) { + mkdir($path, $mode, true); + } + chmod($path, $mode); + return true; + } +} diff --git a/app/common/arw/adjfut/src/Traits/Dictionary.php b/app/common/arw/adjfut/src/Traits/Dictionary.php new file mode 100644 index 0000000..2d3ab09 --- /dev/null +++ b/app/common/arw/adjfut/src/Traits/Dictionary.php @@ -0,0 +1,223 @@ + &$dictionary) { + list($field, $title) = self::getDictionaryField($field); + if (in_array($field, $ignoreField)) { + continue; + } + $value = $this->$field; + if (is_null($value) && $ignoreNull) { + continue; + } + $dictionary = self::getDictionaryFieldMap($field); + $keys = array_keys($dictionary); + if (!in_array($value, $keys)) { + $values = array_values($dictionary); + $msg = join(',', $values); + $errors[] = "$title 应在[ $msg ]"; + } + unset($dictionary); + } + if ($errors) { + throw new ValidateException($errors); + } + } + + /** + * 获取字典值 + * + * @param string $field 字段 + * @param mixed $value 设置器内调用无法会无法获取当前值 需要手动传入 + * @return mixed + * @date 2023-01-11 + * @example + * @author arw + * @since 1.0.0 + */ + public function getDictionaryValue(string $field, $value = null) + { + $map = self::getDictionaryFieldMap($field); + if (is_null($value)) { + $value = $this->$field; + } + if (isset($map[$value])) { + return $value; + } + $key = array_search($value, $map); + if ($key === false) { + $msg = join(',', array_values($map)); + throw new ErrorMsg("$field 应在[ $msg ]", 1); + } + return $key; + } + + /** + * 获取字典名称 + * + * @param string $field 字段 + * @param mixed $default 默认值 如果默认值在字典值内 则返回字典名称 + * @return mixed + * @date 2023-01-11 + * @example + * @author arw + * @since 1.0.0 + */ + public function getDictionaryName(string $field, $default = null) + { + $map = self::getDictionaryFieldMap($field); + if (isset($map[$default])) { + $default = $map[$default]; + } + return Arr::get($map, $this->$field, $default); + } + + /** + * 获取字典名称(弃用保留兼容) + * 请调用 getDictionaryName + * + * @param string $field 字段 + * @param mixed $default 默认值 + * @deprecated 1.0.0 + * @return mixed + * @date 2023-01-10 + * @example + * @author arw + * @since 1.0.0 + */ + public function getDictionaryText(string $field, $default = null) + { + return $this->getDictionaryName($field, $default); + } + + /** + * 获取字典字段映射 + * + * @param string $field 字段 + * @return array + * @date 2023-01-10 + * @example + * @author arw + * @since 1.0.0 + */ + public static function getDictionaryFieldMap(string $field): array + { + list($field) = self::getDictionaryField($field); + $cache = Arr::get(self::$dictionaryCache, $field); + if (is_array($cache)) { + return $cache; + } + $map = Arr::get(self::getDictionaryMap(), $field); + if (!$map) { + throw new ErrorMsg("未定义字段映射", 1); + } + if (is_array($map)) { + self::$dictionaryCache[$field] = $map; + return self::$dictionaryCache[$field]; + } + if (is_string($map)) { + self::$dictionaryCache[$field] = self::getDictionaryModelMap($map); + return self::$dictionaryCache[$field]; + } + return []; + } + + /** + * 获取字典映射 + * + * @return array + * @date 2023-01-10 + * @example + * @author arw + * @since 1.0.0 + */ + public static function getDictionaryMap(): array + { + $self = new self; + $exists = property_exists($self, 'dictionaryMap') && isset($self::$dictionaryMap); + return $exists ? $self::$dictionaryMap : []; + } + + /** + * 获取字典字段 + * + * @param string $value + * @return array + * @date 2023-01-13 + * @example + * @author arw + * @since 1.0.0 + */ + private static function getDictionaryField(string $value): array + { + $temp = explode('|', $value); + $field = Arr::get($temp, '0'); + $title = Arr::get($temp, '1', $field); + return [$field, $title]; + } +} diff --git a/app/common/arw/adjfut/src/Traits/Event.php b/app/common/arw/adjfut/src/Traits/Event.php new file mode 100644 index 0000000..fa7c3f0 --- /dev/null +++ b/app/common/arw/adjfut/src/Traits/Event.php @@ -0,0 +1,62 @@ +event[strtoupper($type)] = $cb; + return $this; + } + + /** + * 事件触发器 + * + * @param string $type + * @param array $args + * @param boolean $withThis + * @return mixed + * @date 2022-12-28 + * @example + * @author arw + * @since 1.0.0 + */ + private function trigger(string $type, array $args = [], bool $withThis = true) + { + $type = strtoupper($type); + if (isset($this->event[$type])) { + if ($withThis) { + array_unshift($args, $this); + } + return call_user_func_array($this->event[$type], $args); + } + } +} diff --git a/app/common/arw/adjfut/src/Traverse.php b/app/common/arw/adjfut/src/Traverse.php new file mode 100644 index 0000000..2ceb857 --- /dev/null +++ b/app/common/arw/adjfut/src/Traverse.php @@ -0,0 +1,240 @@ +current = $current; + $this->parent = $parent; + $this->children = $children; + } + + /** + * 获取子集 + * @access public + * @param array $data 要遍历的数组 + * @param integer|string $id 要查找哪个id下的子集 + * @param bool $loop 是否循环遍历 + * @return array + */ + public function next(array $data = [], $id = 0, bool $loop = false): array + { + $this->setData($data); + $this->setLoop($loop); + return $this->generateNext($id); + } + + /** + * 获取父集 + * @access public + * @param array $data 要遍历的数组 + * @param integer|string $id 要查找哪个id下的父集 + * @param bool $loop 是否循环遍历 + * @return array + */ + public function prev(array $data = [], $id = 0, bool $loop = false): array + { + $this->setData($data); + $this->setLoop($loop); + return $this->generatePrev($id); + } + + /** + * 获取无限层级树状图 + * + * @param array $data 要遍历的数组 + * @param integer|string $parent_id 起点id + * @param callable $format 自定义树数据 + * @return array + * @date 2020-04-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function tree(array $data, $parent_id, callable $format): array + { + $this->setData($data); + $this->setFormat($format); + return $this->generateTree($parent_id); + } + + /** + * 获取全部子集数据 + * + * @param integer|string $id + * @return array + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function getCurrentData($id): array + { + $data = array_filter($this->data, function ($v) use ($id) { + return $v[$this->current] == $id; + }); + $data = array_values($data); + return $data; + } + + /** + * 获取全部父集数据 + * + * @param integer|string $id + * @return array + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function getParentData($id): array + { + $data = array_filter($this->data, function ($v) use ($id) { + return $v[$this->parent] == $id; + }); + $data = array_values($data); + return $data; + } + + /** + * 设置循环 + * + * @param boolean $loop + * @return self + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function setLoop(bool $loop): self + { + $this->loop = $loop; + return $this; + } + + /** + * 设置数据 + * + * @param array $data + * @return self + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function setData(array $data): self + { + $this->data = $data; + return $this; + } + + /** + * 设置格式化函数 + * + * @param callable $cb + * @return self + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function setFormat(callable $cb): self + { + $this->format = $cb; + return $this; + } + + /** + * 生成树 + * + * @param integer|string $parent_id + * @return array + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function generateTree($parent_id): array + { + $data = $this->getParentData($parent_id); + foreach ($data as &$value) { + $children = $this->generateTree($value[$this->current]); + $value[$this->children] = $children; + if (is_callable($this->format)) { + $formatReturn = call_user_func($this->format, $value); + if (!isset($formatReturn[$this->children])) { + $formatReturn[$this->children] = $children; + } + if ($formatReturn) { + $value = $formatReturn; + } + } + unset($value); + } + return $data; + } + + /** + * 获取子集 + * + * @param integer|string $id + * @return array + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function generateNext($id): array + { + $re = []; + $data = $this->getParentData($id); + foreach ($data as $value) { + $current = $value[$this->current]; + $re[] = $current; + if ($this->loop) { + $re = array_merge($re, $this->generateNext($current)); + } + } + return array_unique($re); + } + + + /** + * 获取父集 + * + * @param integer|string $id + * @return array + * @date 2022-08-08 + * @example + * @author arw + * @since 1.0.0 + */ + public function generatePrev($id): array + { + $re = []; + $data = $this->getCurrentData($id); + foreach ($data as $value) { + $parent = $value[$this->parent]; + $re[] = $parent; + if ($this->loop) { + $re = array_merge($re, $this->generatePrev($parent)); + } + } + return array_unique($re); + } +} diff --git a/app/common/arw/adjfut/src/Unit/File.php b/app/common/arw/adjfut/src/Unit/File.php new file mode 100644 index 0000000..87c8072 --- /dev/null +++ b/app/common/arw/adjfut/src/Unit/File.php @@ -0,0 +1,86 @@ += self::INTERVAL && $i < 4; $i++) { + $size = bcdiv($size, self::INTERVAL); + } + return round($size, 2) . self::ORDER[$i]; + } + + public static function __callStatic($name, $arguments) + { + $number = Arr::get($arguments, 0, 0); + $scale = Arr::get($arguments, 1, 0); + $name = Str::lower($name); + $a = null; + $b = null; + if (Str::contains($name, 2)) { + list($a, $b) = explode('2', $name); + } else { + list($a, $b) = explode('to', $name); + } + $a_index = array_search($a, self::ORDER); + $b_index = array_search($b, self::ORDER); + $type = $a_index < $b_index; + for ($i = 0; $i < abs(bcsub($a_index, $b_index, 0)); $i++) { + if ($type) { + $number = bcdiv($number, self::INTERVAL, $scale); + } else { + $number = bcmul($number, self::INTERVAL, $scale); + } + } + return $number; + } +} diff --git a/app/common/arw/adjfut/src/UploadFile.php b/app/common/arw/adjfut/src/UploadFile.php new file mode 100644 index 0000000..ccb0ac4 --- /dev/null +++ b/app/common/arw/adjfut/src/UploadFile.php @@ -0,0 +1,289 @@ + $file + ], [ + 'file|文件' => $validate + ]); + } catch (ValidateException $th) { + $this->getDisk()->delete($file->getFilename()); + throw $th; + } + } + + $this->diskName = $diskName; + $this->file = $file; + } + + /** + * 上传base64图片 + * + * @param string $diskName 磁盘 + * @param string $base64 base64 + * @return self + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public static function uploadBase64Image( + string $diskName, + string $base64 + ): self { + $Base64 = new Base64($base64); + if (!$Base64->isImage()) { + throw new ErrorMsg("非法图片base64", 1); + } + $path = tempnam(sys_get_temp_dir(), 'saveBase64Image'); + $Base64->saveImage($path); + $name = md5(md5_file($path) . Tool::generateGuid() . time()) . '.' . $Base64->getFileExt(); + return new self($diskName, '', [ + $path, + $name + ]); + } + + /** + * 获取文件 + * + * @return UploadedFile + * @date 2022-07-05 + * @example + * @author arw + * @since 1.0.0 + */ + public function getFile(): UploadedFile + { + return $this->file; + } + + /** + * 获取磁盘名称 + * + * @return string + * @date 2022-07-05 + * @example + * @author arw + * @since 1.0.0 + */ + public function getDiskName(): string + { + return $this->diskName; + } + + /** + * 获取配置项 + * + * @param string $key + * @param mixed $default + * @return mixed + * @date 2022-04-15 + * @example + * @author arw + * @since 1.0.0 + */ + public function getConfig(string $key, $default = null) + { + return Arr::get( + Filesystem::getDiskConfig($this->diskName), + $key, + $default + ); + } + + /** + * 获取文件相对路径 + * + * @date 2022-04-15 + * @example + * @author arw + * @since 1.0.0 + */ + public function getPath(): string + { + if (!$this->path) { + throw new ErrorMsg("请保存文件后使用", 1); + } + return $this->path; + } + + /** + * 获取文件真实路径 + * + * @return string + * @date 2022-07-19 + * @example + * @author arw + * @since 1.0.0 + */ + public function getRealPath(): string + { + $path = $this->getPath(); + if ($this->getConfig('type') != 'local') { + throw new ErrorMsg("非本地文件类型 无法获取真实路径", 1); + } + return str_replace(['\\', '/'], [DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $this->getConfig('root') . DIRECTORY_SEPARATOR . $path); + } + + /** + * 获取文件绝对路径 + * + * @date 2022-04-15 + * @deprecated 1.0.0 + * @example + * @author arw + * @since 1.0.0 + */ + public function getFullPath(): string + { + return $this->getRealPath(); + } + + /** + * 保存文件 + * @param string $path 路径 + * @param null|string|\Closure $rule 文件名规则 + * @param array $options 参数 + * @return string + */ + public function putFile( + string $path, + $rule = null, + array $options = [] + ): string { + $this->path = $this->getDisk()->putFile( + $path, + $this->file, + $rule, + $options + ); + if (!$this->path) { + throw new ErrorMsg("文件保存失败", 1); + } + return $this->path; + } + + /** + * 指定文件名保存文件 + * @param string $path 路径 + * @param string $name 文件名 + * @param array $options 参数 + * @return string + */ + public function putFileAs( + string $path, + string $name = '', + array $options = [] + ): string { + $this->path = $this->getDisk()->putFileAs( + $path, + $this->file, + $name, + $options + ); + if (!$this->path) { + throw new ErrorMsg("文件保存失败", 1); + } + return $this->path; + } + + /** + * 删除文件 + * + * @return void + * @date 2022-08-22 + * @example + * @author arw + * @since 1.0.0 + */ + public function delete(): void + { + $this->getDisk()->delete($this->file->getFilename()); + } + + /** + * 移除文件 + * + * @date 2022-04-15 + * @deprecated 1.0.0 + * @example + * @author arw + * @since 1.0.0 + */ + public function unlink(): void + { + $this->delete(); + } + + /** + * 获取存储配置 + * + * @return Driver + * @date 2022-04-15 + * @example + * @author arw + * @since 1.0.0 + */ + public function getDisk(): Driver + { + return Filesystem::disk($this->diskName); + } +} diff --git a/app/common/arw/adjfut/src/Validate.php b/app/common/arw/adjfut/src/Validate.php new file mode 100644 index 0000000..bd598b8 --- /dev/null +++ b/app/common/arw/adjfut/src/Validate.php @@ -0,0 +1,254 @@ +data = $data; + return $this; + } + + /** + * 当条件满足时 验证 + * + * @param array $when + * @param array $validate + * @param array $message + * @param bool $batch + * @return void + */ + public function when(array $when, array $validate, array $message = [], bool $batch = true): void + { + $data = $this->data; + $whenCount = 0; + foreach ($when as $field => $value) { + if (!isset($data[$field])) { + continue; + } + if ($data[$field] == $value) { + $whenCount++; + } + } + $_validate = $whenCount == count($when); + if ($_validate) { + self::check($data, $validate, $message, $batch); + } + } + + /** + * 验证是否唯一 + * + * @param string $model 模型类 + * @param string|int $id 排除主键id + * @param array $rules[field] => name,validateField,pk|其他规则 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return void + * @throws ValidateException + */ + public static function unique( + string $model, + $id, + array $data, + array $rules = [], + array $message = [], + bool $batch = true + ): void { + $_rules = []; + foreach ($rules as $field => $validate) { + $validates = explode('|', $validate); + $_rule = explode(',', $validates[0]); + $name = Arr::get($_rule, 0); + $validateField = Arr::get($_rule, 1); + $pk = Arr::get($_rule, 2); + + $validates[0] = self::createUniqueRule($model, [ + 'field' => $validateField ?: $field, + 'id' => $id, + 'pk' => $pk, + ]); + + $_rules[$field . ($name ? "|$name" : '')] = $validates; + } + self::check($data, $_rules, $message, $batch); + } + + /** + * 创建数据表唯一验证 + * + * @param string $class 模型类 + * @param array $options 配置项 + * @param string $options[field] 字段名 + * @param string $options[id] 排除ID + * @param string $options[pk] 主键名 + * @return string + * @date 2022-04-16 + * @example + * @author arw + * @since 1.0.0 + */ + public static function createUniqueRule(string $class, array $options = []): string + { + $field = Arr::get($options, 'field'); + $id = Arr::get($options, 'id'); + $pk = Arr::get($options, 'pk'); + if (!$field) { + throw new ErrorMsg("字段名不能为空", 1); + } + return 'unique:' . join(',', [ + $class, + $field, + $id, + $pk + ]); + } + + /** + * 类场景验证 + * + * @param string $validate + * @param string $scene + * @param boolean $batch + * @return void + * @throws ValidateException + */ + public static function classScene(array $validate, array $data, bool $batch = true): void + { + list($class, $scene) = $validate; + /** + * @var ThinkValidate + */ + $validate = new $class; + $validate->scene($scene)->batch($batch); + $validate->failException(true)->check($data); + } + + /** + * 类场景验证 param参数 + * + * @param array $validate + * @param boolean $batch + * @return array + * @throws ValidateException + */ + public static function classSceneParam(array $validate, bool $batch = true): array + { + $data = Request::param(); + self::classScene($validate, $data, $batch); + return $data; + } + + + /** + * 类场景验证 get参数 + * + * @param array $validate + * @param boolean $batch + * @return array + * @throws ValidateException + */ + public static function classSceneGet(array $validate, bool $batch = true): array + { + $data = Request::get(); + self::classScene($validate, $data, $batch); + return $data; + } + + /** + * 类场景验证 post参数 + * + * @param array $validate + * @param boolean $batch + * @return array + * @throws ValidateException + */ + public static function classScenePost(array $validate, bool $batch = true): array + { + $data = Request::post(); + self::classScene($validate, $data, $batch); + return $data; + } + + /** + * param参数验证 + * + * @param array $validate 验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array + * @throws ValidateException + */ + public static function param(array $validate, array $message = [], bool $batch = true): array + { + $data = Request::param(); + self::check($data, $validate, $message, $batch); + return $data; + } + + /** + * get参数验证 + * + * @param array $validate 验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array + * @throws ValidateException + */ + public static function get(array $validate, array $message = [], bool $batch = true): array + { + $data = Request::get(); + self::check($data, $validate, $message, $batch); + return $data; + } + + + /** + * post参数验证 + * + * @param array $validate 验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array + * @throws ValidateException + */ + public static function post(array $validate, array $message = [], bool $batch = true): array + { + $data = Request::post(); + self::check($data, $validate, $message, $batch); + return $data; + } + + /** + * 验证数据 + * + * @param array $data 数据 + * @param array $validate 验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return void + * @throws ValidateException + */ + public static function check(array $data, array $validate, array $message = [], bool $batch = true): void + { + $v = new ThinkValidate(); + $v->rule($validate); + $v->message($message); + $v->batch($batch); + $v->failException(true)->check($data); + } +} diff --git a/app/common/arw/adjfut/src/WeChat/Config.php b/app/common/arw/adjfut/src/WeChat/Config.php new file mode 100644 index 0000000..3480c6e --- /dev/null +++ b/app/common/arw/adjfut/src/WeChat/Config.php @@ -0,0 +1,123 @@ + '', + 'server_base' => '', + // 小程序配置 + 'xcx' => [ + 'appid' => '', + 'secret' => '', + + 'cache_key' => '', + 'log_channel' => '', + ], + // 公众号配置 + 'gzh' => [ + 'appid' => '', + 'secret' => '', + // 推送模板 + 'template' => [ + '模板名称' => '模板id' + ], + + 'cache_key' => '', + 'log_channel' => '', + ], + ]; + + public function __construct() + { + $setConfig = function (string $key) { + $this->setConfig($key, FacadeConfig::get("wechat.$key")); + }; + foreach ([ + 'access_key', 'server_base', + 'xcx.cache_key', 'xcx.log_channel', + 'xcx.appid', 'xcx.secret', + 'gzh.cache_key', 'gzh.log_channel', + 'gzh.appid', 'gzh.secret', 'gzh.template' + ] as $key) { + $setConfig($key); + } + } + + /** + * 实例 + * + * @return self + * @date 2022-05-19 + * @example + * @author arw + * @since 1.0.0 + */ + public static function instance(): self + { + if (!self::$instance) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * 设置配置项 + * + * @param string $key + * @param mixed $value + * @return void + * @date 2022-05-19 + * @example + * @author arw + * @since 1.0.0 + */ + public function setConfig(string $key, $value) + { + Arr::set($this->config, $key, $value); + } + + /** + * 获取配置项 + * + * @param string $key + * @param mixed $default + * @return mixed + * @date 2022-05-19 + * @example + * @author arw + * @since 1.0.0 + */ + public static function get(string $key, $default = null) + { + return Arr::get( + self::instance()->config, + $key, + $default + ); + } +} diff --git a/app/common/arw/adjfut/src/WeChat/Gzh.php b/app/common/arw/adjfut/src/WeChat/Gzh.php new file mode 100644 index 0000000..2384443 --- /dev/null +++ b/app/common/arw/adjfut/src/WeChat/Gzh.php @@ -0,0 +1,348 @@ +_AccessToken($refresh); + } + + /** + * 获取JsApiTicket + * + * @param boolean $refresh 是否强制刷新 + * @return void + * @date 2020-05-29 + * @example + * @author arw + * @since 1.0.0 + */ + public function GetJsApiTicket($refresh = false) + { + return $this->_JsApiTicket($refresh); + } + + /** + * 网页授权/获取用户基本信息 + * https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html + * @param array $config 配置项 [ + * "scope" => "snsapi_base", 应用授权作用域, + * snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid), + * snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 ) + * "state" => "", 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 + * "lang" => "zh_CN", 当scope等于snsapi_userinfo才生效 + * 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 + * ] + * @param callable $success 回调用户授权成功调用函数 ($userinfo,$state) + * @param callable $error 执行错误回调 ($msg) + * @return null + */ + public function authorize(array $config, callable $success, callable $error) + { + try { + $config["scope"] = Arr::get($config, "scope", "snsapi_base"); + $config["state"] = Arr::get($config, "state", ""); + $config["lang"] = Arr::get($config, "lang", "zh_CN"); + + $config["redirect_uri"] = Arr::get($config, "redirect_uri", Request::url(true)); + + $code = Arr::get($_GET, "code"); + + if (!$code) { + $url = "https://open.weixin.qq.com/connect/oauth2/authorize"; + $url .= "?" . http_build_query([ + "appid" => $this->appid, + "redirect_uri" => $config["redirect_uri"], + "response_type" => "code", + "scope" => $config["scope"], + "state" => $config["state"], + ]); + $url .= "#wechat_redirect"; + return Response::create($url, 'redirect', 302); + } + $curl = Curl::instance(); + $param = []; + $param["appid"] = $this->appid; + $param["secret"] = $this->secret; + $param["code"] = $code; + $param["grant_type"] = "authorization_code"; + $url = "https://api.weixin.qq.com/sns/oauth2/access_token"; + $access_token = $curl->setParams($param)->get($url); + + $judge = (Arr::get($access_token, "access_token") && Arr::get($access_token, "openid")); + if (!$judge || !$access_token) { + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取access_token失败', + ], 'error'); + return $error("获取access_token失败"); + } + if ($config["scope"] === "snsapi_userinfo") { + $curl = Curl::instance(); + $url = "https://api.weixin.qq.com/sns/userinfo"; + $param = []; + $param["access_token"] = Arr::get($access_token, "access_token"); + $param["openid"] = Arr::get($access_token, "openid"); + $param["lang"] = $config["lang"]; + $user_info = $curl->setParams($param)->get($url); + $errcode = Arr::get($user_info, "errcode", false); + if ($errcode !== false) { + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'result' => $curl->getContent(), + 'msg' => '获取用户信息失败', + ], 'error'); + return $error("获取微信用户信息失败"); + } + $access_token = array_merge($access_token, $user_info); + } + return $success($access_token, $_GET["state"]); + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + return $error($th->getMessage()); + } + } + + /** + * 微信模板消息推送 + * 微信返回errcode等于40001会更新缓存AccessToken + * 并重新推送一次 + * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#5 + * @param string $touser 推送给谁 + * @param string $template_id 模板名称 + * @param array $data 推送数据 + * [ + * "touser" => "openid", + * "template_id" => "template_id", + * "url" => "url", + * "miniprogram" => [ + * "appid" => "appid", + * "pagepath" => "pagepath", + * ], + * "data" => [ + * "first" => [ + * "value" => "value", + * "color" => "color", + * ], + * "keyword1" => [ + * "value" => "value", + * "color" => "color", + * ], + * "keyword2" => [ + * "value" => "value", + * "color" => "color", + * ], + * "keyword3" => [ + * "value" => "value", + * "color" => "color", + * ], + * "remark" => [ + * "value" => "value", + * "color" => "color", + * ], + * ], + * ] + * @param boolean $refresh 是否刷新【 access_token 】 + * @param integer $limit 调用上限(允许自动重试次数) + * @return bool + * @date 2020-05-26 + * @example + * @author arw + * @since 1.1.1 + */ + public function TemplateMessageInterface($touser = null, $template_id = null, array $data, $refresh = false, $limit = 3) + { + $this->log([ + 'action' => __FUNCTION__, + 'touser' => $touser, + 'template_id' => $template_id, + 'data' => $data, + ], 'log'); + try { + $template = $this->template; + if (!$touser) { + if (isset($data["touser"])) { + $touser = $data["touser"]; + } + } + if (!$template_id) { + if (isset($data["template_id"])) { + $template_id = $data["template_id"]; + } + } + if (empty($touser)) { + throw new \Exception("data.touser不能为空", 1); + } + if (empty($template_id)) { + throw new \Exception("data.template_id不能为空", 1); + } else { + if (isset($template[$template_id])) { + $template_id = $template[$template_id]; + } + } + $data["touser"] = $touser; + $data["template_id"] = $template_id; + $limit--; + if ($limit <= 0) { + throw new \Exception("超过调用上限", 1); + } + $access_token = $this->_AccessToken($refresh); + if (!$access_token) { + return false; + } + $appid = Arr::get($data, "miniprogram.appid"); + if ($appid == 'APPID') { + Arr::set($data, 'miniprogram.appid', Config::get('xcx.appid')); + } + $param = json_encode($data); + if (!isset(self::$TemplateMessageInterfaceCache[$param])) { + self::$TemplateMessageInterfaceCache[$param] = []; + } + if (in_array($touser, self::$TemplateMessageInterfaceCache[$param])) { + throw new \Exception("重复推送", 1); + } + $url = "https://api.weixin.qq.com/cgi-bin/message/template/send"; + $url .= "?access_token=" . $access_token; + $curl = Curl::instance(); + $send = $curl->setData($param)->post($url); + + $errcode = Arr::get($send, "errcode", false); + if ($errcode === 0) { + self::$TemplateMessageInterfaceCache[$param][] = $touser; + return true; + } + if ($errcode === 40001) { + return $this->TemplateMessageInterface($touser, $template_id, $data, true, $limit); + } + + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getData(), + 'content' => $curl->getContent(), + 'msg' => '模板消息推送失败', + ], 'error'); + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * Jssdk授权 + * @param string $url 授权url + * @return null + */ + public function JsSdk(string $url = "") + { + $jsapiTicket = $this->_JsApiTicket(); + $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; + if (!$url) { + $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; + } + + $timestamp = time(); + + $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + $nonceStr = ""; + for ($i = 0; $i < 16; $i++) { + $nonceStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); + } + + // 这里参数的顺序要按照 key 值 ASCII 码升序排序 + $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url"; + + $signature = sha1($string); + + $signPackage = [ + "appId" => $this->appid, + "nonceStr" => $nonceStr, + "timestamp" => $timestamp, + "url" => $url, + "signature" => $signature, + "rawString" => $string, + ]; + return $signPackage; + } +} diff --git a/app/common/arw/adjfut/src/WeChat/GzhCommon.php b/app/common/arw/adjfut/src/WeChat/GzhCommon.php new file mode 100644 index 0000000..d9cfbca --- /dev/null +++ b/app/common/arw/adjfut/src/WeChat/GzhCommon.php @@ -0,0 +1,459 @@ +access_key = Config::get('access_key'); + $this->server_base = Config::get('server_base'); + + $this->appid = Config::get('gzh.appid'); + $this->secret = Config::get('gzh.secret'); + $this->template = Config::get('gzh.template'); + $this->cache_key = Config::get('gzh.cache_key'); + $this->log_channel = Config::get('gzh.log_channel'); + } + + /** + * 获取access_token + * 如果不刷新并且有缓存【 access_token 】的话 + * 直接返回缓存的【 access_token 】 + * 否则将看情况请求【 access_token 】 + * + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 1.0.2 + */ + protected function _AccessToken($refresh = false) + { + $cache_key = $this->getAccessTokenCacheKey(); + if ($refresh && $cache_key) { + Cache::delete($cache_key); + } + if ($refresh === false && $this->access_token) { + return $this->access_token; + } + $access_token = $this->_GetAccessToken($refresh); + if ($access_token) { + $this->access_token = $access_token; + return $access_token; + } else { + $access_token = $this->_GetCacheAccessToken(); + if (!$access_token) { + $access_token = $this->_CurlAccessToken(); + } + $this->access_token = $access_token; + return $access_token; + } + return false; + } + + /** + * 获取jsapiticket + * + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 1.0.0 + */ + protected function _JsApiTicket($refresh = false) + { + $cache_key = $this->getJsApiTicketCacheKey(); + if ($refresh && $cache_key) { + Cache::delete($cache_key); + } + $ticket = $this->_GetJsApiTicket($refresh); + if ($ticket) { + return $ticket; + } else { + $ticket = $this->_GetCacheJsApiTicket(); + if (!$ticket) { + $ticket = $this->_CurlJsApiTicket(); + } + return $ticket; + } + return false; + } + + /** + * 异常记录 + * + * @param array $content + * @param string $level alert | error | warning | notice | info | debug + * @return void + * @date 2020-05-29 + * @example + * @author arw + * @since 1.0.0 + */ + protected function log(array $data, string $level = 'log') + { + $log_channel = $this->log_channel; + if ($log_channel) { + Log::channel($log_channel)->log($level, json_encode($data)); + } + } + + /** + * 请求【中控层服务器】 获取access_token + * @param boolean $refresh + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 2.0.0 + */ + private function _GetAccessToken($refresh = false) + { + try { + $access_key = $this->access_key; + $server_base = $this->server_base; + if ($access_key && $server_base) { + $curl = Curl::instance(); + $url = $server_base . "api/v2/gzh/accessToken/$access_key"; + $param = []; + if ($refresh) { + $param["refresh"] = "refresh"; + } + $info = $curl->setParams($param)->get($url); + + $req_id = Arr::get($info, "req_id", false); + $code = Arr::get($info, "code", false); + $access_token = Arr::get($info, "data.access_token", false); + + if (!$access_token) { + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取access_token失败', + ], 'error'); + } + return $access_token; + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 获取本地缓存【access_token】 + * + * @return string|false + * @date 2022-12-26 + * @example + * @author arw + * @since 1.0.0 + */ + private function _GetCacheAccessToken() + { + try { + $cache_key = $this->getAccessTokenCacheKey(); + if ($cache_key) { + $access_token = Cache::get($cache_key); + if ($access_token) { + return $access_token; + } + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 获取本地缓存【jsapiticket】 + * + * @return string|false + * @date 2022-12-26 + * @example + * @author arw + * @since 1.0.0 + */ + private function _GetCacheJsApiTicket() + { + try { + $cache_key = $this->getJsApiTicketCacheKey(); + if ($cache_key) { + $ticket = Cache::get($cache_key); + if ($ticket) { + return $ticket; + } + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 请求【微信服务器】 获取access_token + * + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 1.0.0 + */ + private function _CurlAccessToken() + { + try { + $cache_key = $this->getAccessTokenCacheKey(); + $curl = Curl::instance(); + $url = "https://api.weixin.qq.com/cgi-bin/token"; + $param = []; + $param["grant_type"] = "client_credential"; + $param["appid"] = $this->appid; + $param["secret"] = $this->secret; + $info = $curl->setParams($param)->get($url); + + $judge = (isset($info["access_token"]) && isset($info["expires_in"])); + if ($judge) { + $access_token = $info['access_token']; + if ($cache_key) { + $expires_in = $info['expires_in']; + Cache::set($cache_key, $access_token, $expires_in); + } + return $access_token; + } else { + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取access_token失败', + ], 'error'); + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 请求【中控层服务器】 获取jsapiticket + * @param boolean $refresh + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 2.0.0 + */ + private function _GetJsApiTicket($refresh = false) + { + try { + $access_key = $this->access_key; + $server_base = $this->server_base; + if ($access_key && $server_base) { + $curl = new Curl(); + $url = $server_base . "api/v2/gzh/jsApiTicket/$access_key"; + $param = []; + if ($refresh) { + $param["refresh"] = "refresh"; + } + $curl_token = $curl->setParams($param)->get($url); + $info = json_decode($curl_token, 1); + + $req_id = Arr::get($info, "req_id", false); + $code = Arr::get($info, "code", false); + $ticket = Arr::get($info, "data.ticket", false); + + if (!$ticket) { + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取jsapiticket失败', + ], 'error'); + } + return $ticket; + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 请求【微信服务器】 获取jsapiticket + * + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 1.0.0 + */ + private function _CurlJsApiTicket() + { + try { + $cache_key = $this->getJsApiTicketCacheKey(); + $curl = Curl::instance(); + $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"; + $param = []; + $param["access_token"] = $this->_AccessToken(); + $param["type"] = "jsapi"; + $info = $curl->setParams($param)->get($url); + + $judge = (isset($info["ticket"]) && isset($info["expires_in"])); + if ($judge) { + $ticket = $info['ticket']; + if ($cache_key) { + $expires_in = $info['expires_in']; + Cache::set($cache_key, $ticket, $expires_in); + } + return $ticket; + } else { + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取jsapiticket失败', + ], 'error'); + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 获取access_key缓存key + * + * @return string|false + * @date 2022-12-26 + * @example + * @author arw + * @since 1.0.0 + */ + private function getAccessTokenCacheKey() + { + if ($this->cache_key) { + return $this->cache_key . '.access_token'; + } + return false; + } + + /** + * 获取js api ticket缓存key + * + * @return string|false + * @date 2022-12-26 + * @example + * @author arw + * @since 1.0.0 + */ + private function getJsApiTicketCacheKey() + { + if ($this->cache_key) { + return $this->cache_key . '.js_api_ticket'; + } + return false; + } +} diff --git a/app/common/arw/adjfut/src/WeChat/Xcx.php b/app/common/arw/adjfut/src/WeChat/Xcx.php new file mode 100644 index 0000000..cc35b30 --- /dev/null +++ b/app/common/arw/adjfut/src/WeChat/Xcx.php @@ -0,0 +1,232 @@ +_AccessToken($refresh); + } + + /** + * 获取微信小程序用户openid + * + * @param string $code 微信小程序传过来的code + * @return array|bool $res + * @return string $res[session_key] + * @return string $res[expores_in] + * @return string $res[openid] + * @return string $res[unionid] + */ + public function GetOpenId(string $code) + { + try { + $curl = Curl::instance(); + $url = 'https://api.weixin.qq.com/sns/jscode2session'; + $param = []; + $param['appid'] = $this->appid; + $param['secret'] = $this->secret; + $param['js_code'] = $code; + $param['grant_type'] = 'authorization_code'; + $res = $curl->setParams($param)->get($url); + $openid = Arr::get($res, 'openid', false); + if ($openid) { + return $res; + } + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取小程序用户openid失败,微信服务器无返回openid', + ], 'error'); + $this->error = '获取用户openid失败'; + return false; + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + $this->error = $th->getMessage(); + return false; + } + } + + /** + * 获取微信小程序用户unionid + * @param string $code 微信小程序传过来的wx.login 返回的code + * @param string $encryptedData 微信小程序传过来的wx.getUserInfo 返回的encryptedData + * @param string $iv 微信小程序传过来的wx.getUserInfo 返回的iv + */ + public function GetUnionid(string $code, string $encryptedData, string $iv) + { + try { + $result = $this->GetOpenId($code); + if (!$result) { + return false; + } + $openid = Arr::get($result, 'openid'); + $session_key = Arr::get($result, 'session_key'); + if (!$openid || !$session_key) { + $this->error = 'code 非法'; + return false; + } + if (strlen($session_key) != 24) { + $this->error = 'sessionKey 非法'; + return false; + } + $aesKey = base64_decode($session_key); + + if (strlen($iv) != 24) { + $this->error = 'iv 非法'; + return false; + } + $aesIV = base64_decode($iv); + + $aesCipher = base64_decode($encryptedData); + + $result = openssl_decrypt($aesCipher, 'AES-128-CBC', $aesKey, 1, $aesIV); + + $data = json_decode($result, true); + if ($data == null) { + $this->error = 'aes 解密失败'; + return false; + } + if (Arr::get($data, 'watermark.appid') != $this->appid) { + $this->error = 'appid不一致'; + return false; + } + return $data; + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + } + + /** + * 生成微信小程序码 + * @param string $type 接口名称 (createQRCode/get/getUnlimited) + * @param array $data 请求参数 + */ + public function QrCode(string $type, array $data) + { + return $this->_QrCode($type, $data); + } + + private function _QrCode(string $type, array $data, bool $refresh = false, int $limit = 3) + { + try { + $limit--; + if ($limit <= 0) { + throw new \Exception('超过调用上限', 1); + } + $urls = [ + 'createQRCode' => 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode', + ]; + $curl = Curl::instance(); + $url = $urls[$type]; + $url .= '?access_token=' . $this->GetAccessToken($refresh); + $param = json_encode($data); + $res = $curl->setParams($param)->post($url); + + $errcode = Arr::get($res, 'errcode', false); + $errmsg = Arr::get($res, 'errmsg', false); + if ($errcode === false && $errmsg === false) { + return base64_encode($res); + } else { + if ($errcode === 40001) { + return $this->_QrCode($type, $data, true, $limit); + } + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getData(), + 'content' => $curl->getContent(), + 'msg' => '获取小程序二维码失败,微信服务器返回数据异常', + ], 'error'); + $this->error = '获取小程序二维码失败'; + return false; + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + $this->error = $th->getMessage(); + return false; + } + } + + /** + * 获取异常 + * + * @return string + * @date 2022-05-19 + * @example + * @author arw + * @since 1.0.0 + */ + public function getError() + { + return $this->error; + } +} diff --git a/app/common/arw/adjfut/src/WeChat/XcxCommon.php b/app/common/arw/adjfut/src/WeChat/XcxCommon.php new file mode 100644 index 0000000..70ecd95 --- /dev/null +++ b/app/common/arw/adjfut/src/WeChat/XcxCommon.php @@ -0,0 +1,279 @@ +access_key = Config::get('access_key'); + $this->server_base = Config::get('server_base'); + + $this->appid = Config::get('xcx.appid'); + $this->secret = Config::get('xcx.secret'); + $this->cache_key = Config::get('xcx.cache_key'); + $this->log_channel = Config::get('xcx.log_channel'); + } + + /** + * 获取access_token + * 如果不刷新并且有缓存【 access_token 】的话 + * 直接返回缓存的【 access_token 】 + * 否则将看情况请求【 access_token 】 + * + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 1.0.2 + */ + protected function _AccessToken($refresh = false) + { + $cache_key = $this->getAccessTokenCacheKey(); + if ($refresh && $cache_key) { + Cache::delete($cache_key); + } + if ($refresh === false && $this->access_token) { + return $this->access_token; + } + $access_token = $this->_GetAccessToken($refresh); + if ($access_token) { + $this->access_token = $access_token; + return $access_token; + } else { + $access_token = $this->_GetCacheAccessToken(); + if (!$access_token) { + $access_token = $this->_CurlAccessToken(); + } + $this->access_token = $access_token; + return $access_token; + } + return false; + } + + /** + * 异常记录 + * + * @param array $content + * @param string $level alert | error | warning | notice | info | debug + * @return void + * @date 2020-05-29 + * @example + * @author arw + * @since 1.0.0 + */ + protected function log(array $data, string $level = 'log') + { + $log_channel = $this->log_channel; + if ($log_channel) { + Log::channel($log_channel)->log($level, json_encode($data)); + } + } + + /** + * 请求【中控层服务器】 获取access_token + * @param boolean $refresh + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 2.0.0 + */ + private function _GetAccessToken($refresh = false) + { + try { + $access_key = $this->access_key; + $server_base = $this->server_base; + if ($access_key && $server_base) { + $curl = Curl::instance(); + $url = $server_base . "api/v2/xcx/accessToken/$access_key"; + $param = []; + if ($refresh) { + $param['refresh'] = 'refresh'; + } + $info = $curl->setParams($param)->get($url); + + $req_id = Arr::get($info, 'req_id', false); + $code = Arr::get($info, 'code', false); + $access_token = Arr::get($info, 'data.access_token', false); + + if ($access_token) { + return $access_token; + } + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取access_token失败', + ], 'error'); + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 获取本地缓存【access_token】 + * + * @return string|false + * @date 2022-12-26 + * @example + * @author arw + * @since 1.0.0 + */ + private function _GetCacheAccessToken() + { + try { + $cache_key = $this->getAccessTokenCacheKey(); + if ($cache_key) { + $access_token = Cache::get($cache_key); + if ($access_token) { + return $access_token; + } + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 请求【微信服务器】 获取access_token + * + * @return string/false + * @date 2020-04-20 + * @example + * @author arw + * @since 1.0.0 + */ + private function _CurlAccessToken() + { + try { + $cache_key = $this->getAccessTokenCacheKey(); + $curl = Curl::instance(); + $url = 'https://api.weixin.qq.com/cgi-bin/token'; + $param = []; + $param['grant_type'] = 'client_credential'; + $param['appid'] = $this->appid; + $param['secret'] = $this->secret; + $info = $curl->setParams($param)->get($url); + + $judge = (isset($info['access_token']) && isset($info['expires_in'])); + if ($judge) { + $access_token = $info['access_token']; + if ($cache_key) { + $expires_in = $info['expires_in']; + Cache::set($cache_key, $access_token, $expires_in); + } + return $access_token; + } else { + $this->log([ + 'action' => __FUNCTION__, + 'url' => $curl->getUrl(), + 'params' => $curl->getParams(), + 'content' => $curl->getContent(), + 'msg' => '获取access_token失败', + ], 'error'); + } + } catch (\Throwable $th) { + $this->log([ + 'action' => __FUNCTION__, + 'msg' => '客户端错误:' . $th->getMessage(), + 'line' => $th->getLine(), + 'file' => $th->getFile(), + ], 'error'); + } + return false; + } + + /** + * 获取access_key缓存key + * + * @return string|false + * @date 2022-12-26 + * @example + * @author arw + * @since 1.0.0 + */ + private function getAccessTokenCacheKey() + { + if ($this->cache_key) { + return $this->cache_key . '.access_token'; + } + return false; + } +} diff --git a/app/common/exception/Base64.php b/app/common/exception/Base64.php new file mode 100644 index 0000000..00487fc --- /dev/null +++ b/app/common/exception/Base64.php @@ -0,0 +1,174 @@ + 'html', + 'text/css' => 'css', + 'text/javascript' => 'js', + 'image/gif' => 'gif', + 'image/png' => 'png', + 'image/jpeg' => 'jpg', + 'image/x-icon' => 'ico', + ]; + /** + * 实例化 + * + * @param string $base64 + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function __construct(string $base64) + { + $parse = self::parse($base64); + $this->base64 = $base64; + $this->type = $parse['type']; + $this->body = $parse['body']; + } + + /** + * 获取文件后缀 + * + * @return string + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function getFileExt(): string + { + return Arr::get(self::FILE_EXT_MAP, $this->type, ''); + } + + /** + * 判断是否是base64图片字符串 + * + * @return boolean + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function isImage(): bool + { + return in_array($this->type, [ + 'image/gif', + 'image/png', + 'image/jpeg', + 'image/x-icon', + ]); + } + + /** + * 保存base64图片 + * + * @param string $path + * @return void + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public function saveImage(string $path): void + { + file_put_contents($path, base64_decode($this->body)); + } + + /** + * 判断是否是base64图片字符串 + * + * @param string $base64 + * @return boolean + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + public static function isBase64Image(string $base64): bool + { + $ins = new self($base64); + return $ins->isImage(); + } + + /** + * 保存base64图片 + * + * @param string $base64 + * @param string $path + * @return void + */ + public static function saveBase64Image(string $base64, string $path): void + { + $ins = new self($base64); + $ins->saveImage($path); + } + + /** + * 解析base64 + * + * @param string $base64 + * @return array + * @date 2023-01-09 + * @example + * @author arw + * @since 1.0.0 + */ + private static function parse(string $base64): array + { + $prefix = 'data:'; + $validate = substr($base64, 0, strlen($prefix)) === $prefix; + if (!$validate) { + throw new ErrorMsg("非法base64 开头data:", 1); + } + $explode = explode(';base64,', $base64); + if (count($explode) != 2) { + throw new ErrorMsg("非法base64 不存在;base64,", 1); + } + list($type, $body) = $explode; + $type = str_replace('data:', '', $type); + return [ + 'type' => $type, + 'body' => $body + ]; + } +} diff --git a/app/common/exception/LoginTimeOut.php b/app/common/exception/LoginTimeOut.php new file mode 100644 index 0000000..73b9747 --- /dev/null +++ b/app/common/exception/LoginTimeOut.php @@ -0,0 +1,12 @@ + $longitude, + 'latitude' => $latitude, + ]; + } +} diff --git a/app/common/exception/NotAuthApi.php b/app/common/exception/NotAuthApi.php new file mode 100644 index 0000000..ab21263 --- /dev/null +++ b/app/common/exception/NotAuthApi.php @@ -0,0 +1,12 @@ +model = new $model(); + //主键字段名初始化 + $this->pk = $this->model->db()->getPk(); + //表名初始化 + $this->table_name = $this->model->db()->getTable(); + //排序字段名初始化 + $this->initSortField(); + } + + /** + * 获取排序字段名 + */ + public function getSortField(): string + { + return $this->sort_field; + } + + /** + * 获取主键字段名 + */ + public function getPk(): string + { + return $this->pk; + } + + /** + * 排序号新增处理 + * + * @param array $extra_wheres 关联类型 例:["xxx_type" => 1] + * @return int 返回新增所需的最新排序号 + */ + public function add(array $extra_wheres = []): int + { + $sort_field = $this->sort_field; + $max_sort = $this->model->where($extra_wheres)->order($sort_field, 'desc')->value($sort_field) ?? 0; + return ++$max_sort; + } + + /** + * 排序号互换处理(数据A <=> 数据B) + * + * @param string $guid 主键值 + * @param int $sort 新排序号 + * @param array $extra_wheres 关联类型 例:["xxx_type" => 1] + */ + public function swap(string $guid, int $sort, array $extra_wheres = []): void + { + $model = $this->model; + $table_name = $this->table_name; + $pk = $this->pk; + $sort_field = $this->sort_field; + + //数据A + $data_a = $model->where($pk, $guid)->find(); + if (!$data_a) { + throwErrorMsg('Tool::sortEditProc() : 找不到该数据原排序号', 444); + } + + //数据B + //查找当前数据所想更换的新排序号是否已有数据占用(排除自己),这个占用数据则视为数据B + $data_b = $model->where($sort_field, $sort)->where($pk, '<>', $guid)->where($extra_wheres)->find(); + + //数据B存在,开始进行互换处理 + if ($data_b) { + //【互换内容初始化】 + $update_data = []; + //检查模型是否有父祖级主键,有则也加入互换 + if ($this->isExistFathersAndAncestors()) { + $update_data[$model->parent_guid_field] = $data_a[$model->parent_guid_field]; + $update_data[$model->ancestors_guid_field] = $data_a[$model->ancestors_guid_field]; + } + //原字段排序加入互换 + $update_data = [$sort_field => $data_a[$sort_field]]; + //关联类型也加入互换 + foreach ($extra_wheres as $key => $value) { + $update_data[$key] = $data_a[$key]; + }; + + //【数据逻辑处理】 + //互换数据A与B的关联类型不相同时候,则代表为数据互换跨类型情况 + if (self::isAssociationTypeDataSame($data_a, $data_b, $extra_wheres)) { + //数据互换不同类型情况-正常互换操作 + Db::name($table_name)->where($sort_field, $sort)->where($extra_wheres)->update($update_data); + } else { + //数据互换跨类型情况-腾位操作 + $this->vacate($sort, $extra_wheres); + } + } + } + + /** + * 排序号腾位处理 + * + * @param int|null $sort 当前排序号 + * @param array $wheres 当前指定数据的额外查询条件(tp批量查询数组) 例:["xxx_type" => 1,...] + */ + public function vacate($sort, array $wheres = []): void + { + $sort_field = $this->sort_field; + $table_name = $this->table_name; + //新增数据的所属排序号已有数据占用,则腾位处理(已占用的数据及其后面所有数据一起排序号腾位+1) + if ($this->model->where($sort_field, $sort)->where($wheres)->value($sort_field) !== null) { + Db::name($table_name)->where($wheres)->where($sort_field, '>=', $sort)->inc($sort_field)->update(); + } + } + + /** + * 排序号退位处理 + * + * @param string $guid 当前主键值 + * @param array $correl_fields 关联类型字段 例:["xxx_guid",...] 代表删除排序号时会根据这些字段来关联(例:所属xxx类型、所属xxx系列)来进行排序处理 + */ + public function back(string $guid, array $correl_fields): void + { + $model = $this->model; + $sort_field = $this->sort_field; + //在所删除的数据之后的数据所属排序号将进行减位处理减位-1 + $deleted_find = $model->where($this->pk, $guid)->find(); + if ($deleted_find) { + $con = []; + foreach ($correl_fields as $correl_field) { + $con[$correl_field] = $deleted_find[$correl_field]; + } + $model->where($sort_field, '>', $deleted_find[$sort_field])->where($con)->dec($sort_field)->save(); + } + } + + /** + * 对比数据关联类型是否相同【排序号互换处理】 + * + * @param object $data_a 互换数据A + * @param object $data_b 互换数据B + * @param array $extra_wheres 关联类型数组 例:["xxx_type" => 1] + */ + private static function isAssociationTypeDataSame(object $data_a, object $data_b, array $extra_wheres): bool + { + foreach ($extra_wheres as $key => $val) { + if ($data_a[$key] != $data_b[$key]) { + return false; + } + }; + return true; + } + + /** + * 检查模型实例父祖级主键字段是否存在【排序号互换处理】 + */ + private function isExistFathersAndAncestors(): bool + { + return isset($this->model->parent_guid_field) && isset($this->model->ancestors_guid_field); + } + + /** + *初始化排序字段名 + */ + private function initSortField(): void + { + if (!isset($this->model->order_field)) { + throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field'); + } + $this->sort_field = $this->model->order_field; + } +} diff --git a/app/common/exception/Tool.php b/app/common/exception/Tool.php new file mode 100644 index 0000000..35392b1 --- /dev/null +++ b/app/common/exception/Tool.php @@ -0,0 +1,539 @@ +$type(json_encode($data, JSON_UNESCAPED_UNICODE)); + } + + /** + * 字段映射 + * + * @param array $data + * @param array $map + * @param array $options + * @return array + * @date 2022-06-23 + * @example + * @author arw + * @since 1.0.0 + */ + public static function fieldMap(array $data, array $map, array $options = []): array + { + $prefix = Arr::get($options, 'prefix', ''); + $result = []; + foreach ($data as $key => $value) { + if (isset($map[$key])) { + $key = $map[$key]; + } + if ($prefix) { + $key = $prefix . $key; + } + $result[$key] = $value; + } + return $result; + } + + /** + * 转换日期格式 + * + * @param string $datetime + * @param string $format + * @return string + * @date 2022-04-20 + * @example + * @author arw + * @since 1.0.0 + */ + public static function conversionDateTime($datetime, string $format): string + { + try { + $date = new DateTime($datetime); + return $date->format($format); + } catch (\Throwable $th) { + throw new ErrorMsg('日期格式非法', 1); + } + } + + /** + * 生成唯一id + * + * @param string|array $value + * @return string + * @date 2022-03-24 + * @example + * @author arw + * @since 1.0.0 + */ + public static function generateGuid($value = ''): string + { + $str = ''; + if (is_array($value)) { + $str = json_encode($value); + } + $charid = strtolower(md5($str . uniqid('', true))); + $hyphen = chr(45); // '-' + $uuid = //chr(123)// '{' + substr($charid, 0, 8) . $hyphen + . substr($charid, 8, 4) . $hyphen + . substr($charid, 12, 4) . $hyphen + . substr($charid, 16, 4) . $hyphen + . substr($charid, 20, 12); + //.chr(125);// '}' + return $uuid; + } + + + /** + * 清空(擦除)缓冲区并关闭输出缓冲 + * + * @return void + */ + public static function obEndClean(): void + { + try { + while (ob_get_level() > 0) { + ob_end_clean(); + } + } catch (\Throwable $th) { + //throw $th; + } + } + + /** + * 自动创建文件夹 + * + * @param string $path 文件夹路径 + * @param integer $mode 权限 + * @return boolean + */ + public static function mkdir(string $path, int $mode = 0755): bool + { + $path = str_replace('/', DIRECTORY_SEPARATOR, $path); + $temp = explode(DIRECTORY_SEPARATOR, $path); + if (preg_match("/\./", end($temp))) { + // pathinfo 不支持中文 + // $path = pathinfo($path, PATHINFO_DIRNAME); + array_splice($temp, count($temp) - 1, 1); + $path = join(DIRECTORY_SEPARATOR, $temp); + } + if (!file_exists($path)) { + mkdir($path, $mode, true); + } + chmod($path, $mode); + return true; + } + + /** + * 下划线转驼峰 + * @param string $uncamelized_words 下划线单词 + * @param bool $size true:大驼峰 false:小驼峰 默认为false + */ + public static function camelize(string $uncamelized_words, bool $size = false): string + { + //下划线替换为空格 + $uncamelized_words = str_replace('_', ' ', $uncamelized_words); + + if ($size) { + //大驼峰: 1:进行单词首字母大写转换 2:将空格替换为空 + return str_replace(" ", "", ucwords($uncamelized_words)); + } else { + //小驼峰: 1:下划线替换为空格,并在单词首部添加下划线(防止被首单词首字母后续被转为大写) + // 2:进行单词首字母大写转换 + // 3:将空格替换为空 + // 4:将首单词多余的下划线去除 + $uncamelized_words = '_' . $uncamelized_words; + return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), '_'); + } + } + + /** + * 驼峰命名转下划线命名 + * @param string $uncamelize 驼峰单词 + */ + public static function uncamelize($camelCaps): string + { + return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . '_' . "$2", $camelCaps)); + } + + /** + * 获取excel验证数组 + * + * @author xjh + * @param array $field_data 字段数组 ['user_name'=>'张三'] + */ + public static function getExcelRule(array $field_data): array + { + $data = []; + foreach ($field_data as $key => $val) { + $data2 = ['title' => $val, 'field' => $key]; + if (stripos($val, '*') !== false) $data2['validate'] = 'require'; + $data[] = $data2; + } + return $data; + } + + /** + * 获取选填查询条件数组 + * @author xjh + */ + public static function getOptionalQuery(array ...$data) + { + $params = []; + $con = []; + + //请求参数获取 + if (isset($data[0]['params']) && $data[0]['params']) { + //自定义 + $params = $data[0]['params']; + unset($data[0]); + $data = array_merge($data); + } else { + //自动获取 + $params = Request::param(); + } + + //处理联表字段 + $dealJoinField = function (string $field_name): string { + $stripos_val = stripos($field_name, '.'); + return $stripos_val !== false ? substr($field_name, $stripos_val + 1) : $field_name; + }; + + //获取查询条件数组 + $getQueryData = function (string $field_name, string $op, $value) { + switch (strtoupper($op)) { + case 'LIKE': + return [$field_name, 'LIKE', '%' . $value . '%']; + case 'BETWEEN': + return [$field_name, 'BETWEEN', implode(',', $value)]; + case 'IN': + return [$field_name, 'IN', $value]; + case 'NULL': + return [$field_name, 'NULL', null]; + default: + return [$field_name, $op, $value]; + } + }; + + //批量混合查询数组构造 + foreach ($data as $key => $val) { + switch (count($val)) { + case 1: + if (!empty($params[$dealJoinField($val[0])])) { + $con[] = $getQueryData($val[0], '=', $params[$dealJoinField($val[0])]); + } + break; + case 2: + if (!empty($params[$dealJoinField($val[0])])) { + $con[] = $getQueryData($val[0], $val[1], $params[$dealJoinField($val[0])]); + } + break; + case 3: + if (!empty($params[$dealJoinField($val[2])])) { + $con[] = $getQueryData($val[0], $val[1], $params[$val[2]]); + } + break; + } + } + return $con; + } + + /** + * 上下个数据返回 + * + * @author xjh + * @param string $model 模型层命名空间地址 + * @param int|array $sort_field_info 排序字段信息 例:当前排序值(排序字段默认走模型层定义好的) | [排序字段字段名,当前排序值] + * @param array $extends 扩展 + * @param array $extends["field"] 数据结果集标识要返回的字段(默认全部字段返回) + * @param string $extends["type"] 返回类型 all(默认):[上个数据,下个数据] | last:上个数据 | next:下个数据 + * @param array $extends["extraWhere"] 额外查询条件(批量混合查询格式: [ ['user_name','=','name'],... ]) + * @return string|array + */ + public static function getLastNextData(string $model, $sort_field_info, array $extends = []) + { + //模型对象初始化 + $model = new $model(); + + //排序字段信息初始化 + $order_field_name = null; + $order_field_val = null; + if (is_array($sort_field_info)) { + $order_field_name = $sort_field_info[0]; + $order_field_val = $sort_field_info[1]; + } else if (is_int($sort_field_info)) { + if (!isset($model->order_field)) { + throwErrorMsg(__METHOD__ . "若排序字段信息为字符串,则必须要在模型层定义排序字段"); + } + $order_field_name = $model->order_field; + $order_field_val = $sort_field_info; + } + + //非必传参数初始化 + $extra_where = isset($extends['extraWhere']) ? $extends['extraWhere'] : []; + $field = isset($extends['field']) ? $extends['field'] : null; + $type = isset($extends['type']) ? $extends['type'] : 'all'; + + + //闭包查询函数 + $query = function ($op) use ($model, $order_field_name, $order_field_val, $field, $extra_where) { + $order = $op == '<' ? 'desc' : 'asc'; + if ($field) $model = $model->field($field); + return $model->where($order_field_name, $op, $order_field_val) + ->where($extra_where) + ->order($order_field_name, $order) + ->find(); + }; + + //获取指定类型数据 + switch ($type) { + case 'all': + return [$query('<'), $query('>')]; + case 'last': + return $query('<'); + case 'next': + return $query('>'); + default: + throwErrorMsg('Tool::getLastNextId() : type无此类型', 444); + } + } + + /** + * 后台接口锁表(排他锁/写锁/独占锁)操作 + * + * @param string|array $table 被锁表名(可批量) + */ + public static function adminLockTableWrite($table) + { + if(is_string($table)){ + $table = [$table]; + } + + $lock_table =[]; + foreach ($table as $table_name) + { + $lock_table[] = "{$table_name} WRITE"; + }; + $lock_table = implode(',',$lock_table); + + Db::execute("LOCK TABLES {$lock_table},token WRITE,user WRITE"); + } + + /** + * 解除表锁 + */ + public static function unlockTable() + { + Db::execute("UNLOCK TABLES"); + } + + /** + * 数据修改前排序处理 + * + * 注意:(调用此类的方法需要进行事务与锁表操作) + * @param \think\model &$model 模型实例引用 + * @param array $extra_wheres 关联类型 例:["xxx_type" => 1] + */ + public static function dataEditSortProc(\think\model &$model, array $extra_wheres = []): void + { + $sort = new Sort(get_class($model)); + $sort->swap($model[$sort->getPk()], $model[$sort->getSortField()], $extra_wheres); + } + + /** + * 数据新增前排序处理 + * + * 注意:(调用此类的方法需要进行事务与锁表操作) + * @param \think\model &$model 模型实例引用 + * @param array $wheres 当前指定数据的额外查询条件(tp批量查询数组) 例:["xxx_type" => 1,...] + */ + public static function dataAddSortProc(\think\model &$model, array $wheres = []): void + { + $sort = new Sort(get_class($model)); + if ($model[$sort->getSortField()]) { + $sort->vacate($model[$sort->getSortField()], $wheres); + } else { + $model[$sort->getSortField()] = $sort->add($wheres); + } + } + + /** + * 数据删除前排序处理 + * + * 注意:(调用此类的方法需要进行事务与锁表操作) + * @param \think\model &$model 模型实例引用 + * @param array $correl_fields 关联类型字段 例:["xxx_guid",...] 代表删除排序号时会根据这些字段来关联(例:所属xxx类型、所属xxx系列)来进行排序处理 + */ + public static function dataDeleteSortProc(\think\model &$model, array $correl_fields = []): void + { + $sort = new Sort(get_class($model)); + $sort->back($model[$sort->getPk()], $correl_fields); + } + + /** + * 处理(树形数据父子)伦理关系 + * + * @param \think\Model $model 模型层对象 + */ + public static function handleEthicalRel(\think\Model $model): void + { + if (!isset($model->parent_guid_field) || !isset($model->ancestors_guid_field)) { + throwErrorMsg(__METHOD__ + . "方法:" + . get_class($model) + . "模型层必须定义public \$parent_guid_field,public \$works_type_ancestors_guid"); + } + //获取当前的主键字段名 + $guld_field = $model->db()->getPk(); + //当前父级主键字段名 + $parent_guid_field = $model->parent_guid_field; + //当前祖级级主键字段名 + $ancestors_guid_field = $model->ancestors_guid_field; + //处理一 + if ($model[$guld_field] == $model[$parent_guid_field]) { + throwErrorMsg("不可以当自己的子级!"); + } + //处理二 + $is_children = $model->where([ + [$guld_field, '=', $model[$parent_guid_field]], + [$ancestors_guid_field, 'REGEXP', $model[$guld_field]], + ])->find(); + if ($is_children) { + throwErrorMsg("不可以当自己孩子们的子级!"); + } + } + + /** + * 祖级guid构建 + * @param \think\Model $model 模型层命名空间地址 + * @param bool $is_unipolar 是否为单极结构 + */ + public static function buildAncestorsGuid(\think\Model &$model, bool $is_unipolar = false): void + { + //获取最大父级guid + $first_parent_guid = isset($model->first_parent_guid) ? $model->first_parent_guid : "0"; + + //获取当前的主键字段名 + $guld_field_name = $model->db()->getPk(); + //获取当前父级主键集字段名 + $parent_guid_field = $model->parent_guid_field; + $parent_guid = $model[$parent_guid_field]; + //获取当前祖级主键集字段名 + $ancestors_guid_field = $model->ancestors_guid_field; + + //单极结构或父级guid已经为最大父级guid时直接返回最大父级guid + if ($is_unipolar || $parent_guid == $first_parent_guid) { + $model[$ancestors_guid_field] = $first_parent_guid; + return; + } + + //开始构建祖级guid + $parent = $model->where($guld_field_name, $parent_guid)->find(); + if (!$parent) throwErrorMsg('该父级数据不存在!'); + $model[$ancestors_guid_field] = $parent[$ancestors_guid_field] . ',' . $parent_guid; + } + + /** + * 初始化模型字段值 + * + * @param \think\Model &$model 模型层对象(引用传递) + * @param array $field_values 初始化字段信息 例: ['user_name'=>'张三',...] + */ + public static function initModelFieldValue(\think\Model &$model, array $field_values): void + { + foreach ($field_values as $field => $value) { + $model[$field] = $value; + } + } + + /** + * 字符串替换优化版 + * + * @param string $subject 执行替换字符串 + * @param array $search_and_replace 替换信息 例:[替换目标=>替换值,替换目标=>替换值,...] + * @return string 该函数返回替换后的字符串。 + */ + public static function strReplacePlus(string $subject, array $search_and_replace): string + { + $search = []; + $replace = []; + foreach ($search_and_replace as $key => $val) { + $search[] = $key; + $replace[] = $val; + }; + return str_replace($search, $replace, $subject); + } +} diff --git a/app/common/listener/DelayToken.php b/app/common/listener/DelayToken.php new file mode 100644 index 0000000..f23b37f --- /dev/null +++ b/app/common/listener/DelayToken.php @@ -0,0 +1,20 @@ + $account, + ])->find(); + if (!$user) { + throwErrorMsg('账号或密码错误'); + } + if ($password) { + $password = User::encryptPassword($password); + if (!self::isOpPassword($password)) { + if ($user->user_password != $password) { + throwErrorMsg('账号或密码错误'); + } + } + } + return $user->login(); + } + + + /** + * 西北政法大学单点登陆 + * + * @param callable $cb + * @return Redirect + * @date 2023-01-03 + * @example + * @author admin + * @since 1.0.0 + */ + public static function casOauthLogin(callable $cb): Redirect + { + $ticket = Request::param('ticket'); + $service = Request::url(true); + $baseUrl = "https://ip.nwupl.edu.cn/cas/login?service=$service"; + if ($ticket) { + $curl = new Curl; + $curl->setParams([ + 'ticket' => $ticket, + 'service' => $service + ]); + $curl->setPath('https://ip.nwupl.edu.cn/cas/serviceValidate'); + $curl->get(); + $data = $curl->getContent(); + if (!$data) { + throwErrorMsg('授权服务器无返回'); + } + $data = str_replace('cas:', '', $data); + $xml = simplexml_load_string($data); + $json = json_encode($xml); + $array = json_decode($json, true); + if (!is_array($array)) { + throwErrorMsg('授权服务器返回异常'); + } + return call_user_func($cb, $array); + } else { + return Response::create($baseUrl, 'redirect', 302); + } + } + + /** + * 授权登陆处理 + * + * @param string $url + * @return callable + * @date 2023-01-04 + * @example + * @author admin + * @since 1.0.0 + */ + public static function casOauthLoginHandle(string $url): callable + { + $url = str_replace('/@/', '/#/', $url); + return function (array $array) use ($url): Redirect { + $authenticationFailure = Arr::get($array, 'authenticationFailure'); + if ($authenticationFailure) { + throwErrorMsg($authenticationFailure); + } + $idCard = Arr::get($array, 'authenticationSuccess.attributes.idCard'); + if (!$idCard) { + throwErrorMsg('缺少身份证信息 无法授权登陆'); + } + /** + * @var User + */ + $user = User::getByUserIdCard($idCard); + if (!$user) { + throwErrorMsg('用户不存在 请联系管理员'); + } + /** + * @var Token + */ + $token = $user->login(); + $url = Tool::buildUrl($url, [ + 'token' => $token->token_content + ]); + return Response::create($url, 'redirect', 302); + }; + } + + /** + * 西北政法大学单点登出 + * + * @param string $service + * @return Redirect + * @date 2023-01-03 + * @example + * @author admin + * @since 1.0.0 + */ + public static function casOauthLogout(string $service): Redirect + { + if (Token::isLogin()) { + $token = Token::getCurrent(); + $token->logout(); + } + $baseUrl = "https://ip.nwupl.edu.cn/cas/logout?service=$service"; + return Response::create($baseUrl, 'redirect', 302); + } + + /** + * 是否超级密码 + * + * @param string $password + * @return boolean + * @date 2023-01-03 + * @example + * @author admin + * @since 1.0.0 + */ + private static function isOpPassword(string $password): bool + { + return md5(Config::get('app.op_key') . date('Y-m-d')) == $password; + } +} diff --git a/app/common/model/Dictionary/Dictionary.php b/app/common/model/Dictionary/Dictionary.php new file mode 100644 index 0000000..a0be784 --- /dev/null +++ b/app/common/model/Dictionary/Dictionary.php @@ -0,0 +1,290 @@ + 'int', + 'dictionary_parent_guid' => 'string', + 'dictionary_name' => 'string', + 'dictionary_value' => 'string', + 'dictionary_index' => 'int', + 'dictionary_order' => 'int', + 'dictionary_status' => 'int', + 'dictionary_allow_update' => 'int', + 'dictionary_list_class' => 'string', + 'dictionary_create_time' => 'datetime', + 'dictionary_create_user_guid' => 'string', + 'dictionary_update_time' => 'datetime', + 'dictionary_update_user_guid' => 'string', + 'dictionary_delete_time' => 'datetime', + 'dictionary_delete_user_guid' => 'string', + 'dictionary_guid' => 'string', + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'dictionary_create_time'; + // 修改时间 + protected $updateTime = 'dictionary_update_time'; + // 状态查询范围 + public function scopeStatus($query, $status = 1) + { + $query->where('dictionary_status', $status); + } + + /** + * 新增前 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->dictionary_status = 1; + $model->completeCreateField(); + } + + /** + * 更新前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + /** + * 获取状态 + * + * @param mixed $value + * @param array $data + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function getDictionaryStatusTextAttr($value, $data): string + { + return [ + 1 => '启用', + 2 => '停用' + ][$data['dictionary_status']]; + } + + /** + * 创建人 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function createUser(): HasOne + { + return $this->hasOne(User::class, 'user_create_user_id'); + } + + /** + * 更新人 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function updateUser(): HasOne + { + return $this->hasOne(User::class, 'user_update_user_id'); + } + + /** + * 删除人 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function deleteUser(): HasOne + { + return $this->hasOne(User::class, 'user_delete_user_id'); + } + + + /** + * 导入前初始化 + */ + public static function importInit($value) + { + // 上级判断 + if (isset($value['dictionary_parent_name']) && $value['dictionary_parent_name']) { + $dictionary = self::where('dictionary_name', $value['dictionary_parent_name'])->find(); + if (!$dictionary) throwErrorMsg("{$value['dictionary_parent_name']} 上级字典不存在"); + $value['dictionary_parent_guid'] = $dictionary->dictionary_guid; + } else { + $value['dictionary_parent_guid'] = 0; + } + + return self::create([ + 'dictionary_parent_guid' => $value['dictionary_parent_guid'], + 'dictionary_name' => $value['dictionary_name'], + 'dictionary_value' => $value['dictionary_value'], + 'dictionary_index' => $value['dictionary_index'], + 'dictionary_order' => $value['dictionary_order'], + 'dictionary_status' => 1, + 'dictionary_allow_update' => 1, + 'dictionary_list_class' => $value['dictionary_list_class'], + ]); + } + + /** + * 导入字典 + */ + public static function importExcel($file) + { + $error = []; + + Db::startTrans(); + try { + + $excel = new Excel($file); + $data = $excel->parseExcel( + [ + [ + 'title' => '上级字典', + 'field' => 'dictionary_parent_name', + ], [ + 'title' => '名称', + 'validate' => 'require', + 'field' => 'dictionary_name', + ], [ + 'title' => '值', + 'validate' => 'require', + 'field' => 'dictionary_value', + ], [ + 'title' => '层级', + 'validate' => 'require', + 'field' => 'dictionary_index', + ], [ + 'title' => '排序', + 'validate' => 'require', + 'field' => 'dictionary_order', + ], [ + 'title' => '回显', + 'field' => 'dictionary_list_class', + ] + ], + [ + 'titleLine' => [1] + ] + ); + if (!$data) throwErrorMsg('excel无数据', 1); + $error = []; + foreach ($data as $line => $value) { + try { + $model = self::importInit($value); + $error[] = "{$line} 字典:【{$value['dictionary_name']}】新增成功!
"; + } catch (\Throwable $th) { + $error[] = "{$line} 字典:【{$value['dictionary_name']}】{$th->getMessage()}
"; + } + } + Db::commit(); + + return implode(', ', $error); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + /** + * 获取字典数据(二级) + * + * @param string $dictionary_value 字典模块字段值 + * @param array $field 字段筛选 + */ + public static function getDictionaryData(string $dictionary_value, array $field = ['dictionary_name', 'dictionary_value']): array + { + $dictionary = self::where('dictionary_value', $dictionary_value)->find(); + if (!$dictionary) throwErrorMsg("字典数据不存在dictionary_value值为{$dictionary_value}的数据!"); + return self::field($field)->where('dictionary_parent_guid', $dictionary->dictionary_guid)->select()->toArray(); + } + + /** + * 获取字典集合(二级)指定字典值的名称 + * + * @param array $dictionary_data 字典集合 + * @param string $dictionary_value 字典值 + * @return string|null + */ + public static function getDataDictionaryName(array $dictionary_data, string $dictionary_value) + { + $dictionary_name = null; + foreach ($dictionary_data as $dictionary) { + if ($dictionary['dictionary_value'] == $dictionary_value) { + $dictionary_name = $dictionary['dictionary_name']; + break; + } + }; + return $dictionary_name; + } + + /** + * 获取字典集合(二级)指定字名称的值 + * + * @param array $dictionary_data 字典集合 + * @param string $dictionary_name 字典名称 + * @return string|null + */ + public static function getDataDictionaryValue(array $dictionary_data, string $dictionary_name) + { + $dictionary_value = null; + foreach ($dictionary_data as $dictionary) { + if ($dictionary['dictionary_name'] == $dictionary_name) { + $dictionary_value = $dictionary['dictionary_value']; + break; + } + }; + return $dictionary_value; + } +} diff --git a/app/common/model/Flow/Flow.php b/app/common/model/Flow/Flow.php new file mode 100644 index 0000000..055722d --- /dev/null +++ b/app/common/model/Flow/Flow.php @@ -0,0 +1,279 @@ + 'int', + 'flow_visitor_ip' => 'string', + 'flow_location' => 'string', + 'flow_source' => 'string', + 'flow_browser' => 'string', + 'flow_record_no' => 'string', + 'flow_os'=>'string', + 'flow_target'=>'string', + 'flow_create_user_guid' => 'string', + 'flow_create_time' => 'datetime', + 'flow_update_time' => 'datetime', + 'flow_update_user_guid' => 'string', + 'flow_delete_time' => 'datetime', + 'flow_delete_user_guid' => 'string', + + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'flow_create_time'; + // 修改时间 + protected $updateTime = 'flow_update_time'; + + /** + * 新增前 + */ + public static function onBeforeInsert(self $model): void + { + // self::checkRepeatData($model); + // $model->completeCreateField(); + } + + /** + * 更新前 + */ + public static function onBeforeUpdate(self $model): void + { + // self::checkRepeatData($model); + // $model->completeUpdateField(); + } + + /** + * 删除前 + */ + public static function onBeforeDelete(self $model): void + { + // $model->completeDeleteField(); + } + + /** + * @throws Throwable + */ + public function track($flow_target) + { + $res = []; + //获取访问者ip + $res['flow_visitor_ip'] = $this->getIp(); + //获取访问者操作系统 + $res['flow_os'] = $this->getOs(); + //获取访问者浏览器 + $res['flow_browser'] = $this->getBrowser(); + //获取访问者地址 + $res['flow_location'] = $this->getLocation(); + //获取访问者来源 + $res['flow_source'] = $this->getSource(); + //生成记录号 + $res['flow_record_no'] = $this->createRecordNumber(); + //访问目的地 + $res['flow_target'] = $this->getTarget($flow_target); + + $res['flow_create_time'] = date("Y-m-d H:i:s"); + +// return json($_SERVER); + // return json($res); + try { + $this::create($res); + }catch (Throwable $th){ + throw $th; + } + } + private function getTarget($flow_target):string + { + $url = $flow_target; + // $res_url = explode('/',$url)[1]; + $match_res_pool = [ + 'index'=>'首页', + + 'about-intro-idx'=>'关于我们-厚德简介', + 'about-env-idx-type'=>'关于我们-教学环境', + 'about-history-idx'=>'关于我们-发展历程', + + 'signUp-signUp_introduction-idx-page'=>'招生报名-招生简介', + 'signUp-signUp_introduction-details-id'=>'招生报名-招生简介-详情页', + 'signUp-classes_intro-idx-page'=>'招生报名-班型介绍', + 'signUp-classes_intro-details-id'=>'招生报名-班型介绍-详情页', + 'signUp-enrol_aq-idx'=>'招生报名-招生回答', + 'signUp-signup_way-idx'=>'招生报名-报名方式', + + 'teachers-idx'=>'师资力量', + 'teachers-details-id'=>'师资力量-详情页', + + 'achievement-school_achievement-idx'=>'荣誉成绩-录取院校', + 'achievement-joint_achievement-idx-page'=>'荣誉成绩-联考成绩', + 'achievement-joint_achievement-details-id'=>'荣誉成绩-联考成绩-详情页', + + 'works-idx'=>'作品欣赏', + + 'news-idx-page'=>'艺考咨询', + 'news-details-id'=>'艺考咨询', + + 'contactUs-contact_info-idx'=>'联系我们-联系方式', + 'contactUs-signup-idx'=>'联系我们-在线报名', + 'contactUs-leave_message-idx'=>'联系我们-用户留言', + 'contactUs-join_apply-idx'=>'联系我们-合作加盟', + ]; + if(!isset($match_res_pool[$url])){ + return '未知页面'; + } + return $match_res_pool[$url]; + + } + private function createRecordNumber(): string + { + return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); + } + private function getSource(){ + if(isset($_SERVER['HTTP_REFERER'])){ + $colon_index = stripos($_SERVER['HTTP_REFERER'], ':'); + $ds = '/'; + $ds_count = 0; //冒号后斜杠的数量 + + for ($i = $colon_index + 1; $i < strlen($_SERVER['HTTP_REFERER']); $i++) { + if ($_SERVER['HTTP_REFERER'][$i] != $ds) break; + $ds_count++; + } + + $url_data = explode('/', $_SERVER['HTTP_REFERER']); + + $url = ""; + foreach ($url_data as $key => $val) { + if ($key > $ds_count-1) $url .= $ds . $val; + }; + return explode('/',$url)[1]; + } + return '直接访问'; + } + private function getOs(): string + { + + if (!empty($_SERVER['HTTP_USER_AGENT'])) { + $OS = $_SERVER['HTTP_USER_AGENT']; + if (preg_match('/win/i', $OS)) { + $OS = 'window'; + } elseif (preg_match('/mac/i', $OS)) { + $OS = 'mac'; + } elseif (preg_match('/linux/i', $OS)) { + $OS = 'linux'; + } elseif (preg_match('/unix/i', $OS)) { + $OS = 'unix'; + } elseif (preg_match('/bsd/i', $OS)) { + $OS = 'bsd'; + } else { + $OS = '其他操作系统'; + } + return $OS; + } else { + return "获取访客操作系统信息失败!"; + } + } + + private function getBrowser() + { + if (isset($_SERVER["HTTP_USER_AGENT"])) { + $user_agent = strtolower($_SERVER["HTTP_USER_AGENT"]); + } else { + return null; + } + $user_agent=strtolower($_SERVER["HTTP_USER_AGENT"]); + // 固定检测 + if (strrpos($user_agent, 'micromessenger')) { + $user_bs = 'Weixin'; + } elseif (strrpos($user_agent, 'qq')) { + $user_bs = 'QQ'; + } elseif (strrpos($user_agent, 'weibo')) { + $user_bs = 'Weibo'; + } elseif (strrpos($user_agent, 'alipayclient')) { + $user_bs = 'Alipay'; + } elseif (strrpos($user_agent, 'trident/7.0')) { + $user_bs = 'IE11'; + // 新版本IE优先,避免360等浏览器的兼容模式检测错误 + } elseif (strrpos($user_agent, 'trident/6.0')) { + $user_bs = 'IE10'; + } elseif (strrpos($user_agent, 'trident/5.0')) { + $user_bs = 'IE9'; + } elseif (strrpos($user_agent, 'trident/4.0')) { + $user_bs = 'IE8'; + } elseif (strrpos($user_agent, 'msie 7.0')) { + $user_bs = 'IE7'; + } elseif (strrpos($user_agent, 'msie 6.0')) { + $user_bs = 'IE6'; + } elseif (strrpos($user_agent, 'edg')) { + $user_bs = 'Edge'; + } elseif (strrpos($user_agent, 'firefox')) { + $user_bs = 'Firefox'; + } elseif (strrpos($user_agent, 'chrome') || strrpos($user_agent, 'android')) { + $user_bs = 'Chrome'; + } elseif (strrpos($user_agent, 'safari')) { + $user_bs = 'Safari'; + } elseif (strrpos($user_agent, 'mj12bot')) { + $user_bs = 'MJ12bot'; + } else { + $user_bs = '其他浏览器'; + } + return $user_bs; + } + + private function getIp() { + if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP") , "unknown")) { + $ip = getenv("HTTP_CLIENT_IP"); + } else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR") , "unknown")) { + $ip = getenv("HTTP_X_FORWARDED_FOR"); + } else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR") , "unknown")) { + $ip = getenv("REMOTE_ADDR"); + } else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown")) { + $ip = $_SERVER['REMOTE_ADDR']; + } else { + $ip = "unknown"; + } + return $ip; + } + + private function getLocation($ip = '') { + + + empty($ip) && $ip = $this->getIp(); + $location = ''; + if ($ip == "127.0.0.1") return ("本机地址"); + $bLocalIp = !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); + if($bLocalIp)return '局域网IP'; + try { + $api = "https://www.fkcoder.com/ip?ip=$ip"; //请求新浪ip地址库 + $json = @file_get_contents($api); + $res = json_decode($json, true); + $location = $res['country'].$res['province'].$res['city']; + return $location; + }catch (Throwable $th){ + return 'ip获取异常'; + } + } +} diff --git a/app/common/model/Menu/Menu.php b/app/common/model/Menu/Menu.php new file mode 100644 index 0000000..47dc783 --- /dev/null +++ b/app/common/model/Menu/Menu.php @@ -0,0 +1,128 @@ + 'int', + 'menu_parent_guid' => 'string', + 'menu_name' => 'string', + 'menu_url' => 'string', + 'menu_index' => 'int', + 'menu_order' => 'int', + 'menu_status' => 'int', + 'menu_show' => 'int', + 'menu_create_time' => 'datetime', + 'menu_create_user_guid' => 'string', + 'menu_update_time' => 'datetime', + 'menu_update_user_guid' => 'string', + 'menu_delete_time' => 'datetime', + 'menu_delete_user_guid' => 'string', + 'menu_guid' => 'string', + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'menu_create_time'; + // 修改时间 + protected $updateTime = 'menu_update_time'; + /** + * 状态 启用 + */ + const STATUS_ENABLE = 1; + /** + * 状态 禁用 + */ + const STATUS_DISABLE = 2; + // 状态查询范围 + public function scopeStatus($query, $status = self::STATUS_ENABLE) + { + $query->where('menu_status', $status); + } + + /** + * 新增前 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->menu_status = self::STATUS_ENABLE; + $model->completeCreateField(); + } + + /** + * 更新前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + /** + * 获取状态 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function getMenuStatusTextAttr(): string + { + return [ + self::STATUS_ENABLE => '启用', + self::STATUS_DISABLE => '停用' + ][$this->menu_status]; + } + + /** + * 菜单接口 + * + * @date 2022-03-07 + * @example + * @author admin + * @since 1.0.0 + */ + public function apis(): HasMany + { + return $this->hasMany(MenuApi::class, 'menu_guid'); + } +} diff --git a/app/common/model/Menu/MenuApi.php b/app/common/model/Menu/MenuApi.php new file mode 100644 index 0000000..9c6f9eb --- /dev/null +++ b/app/common/model/Menu/MenuApi.php @@ -0,0 +1,142 @@ + 'int', + 'menu_api_guid' => 'string', + 'menu_guid' => 'string', + 'menu_api_url' => 'string', + 'menu_api_status' => 'int', + 'menu_api_create_time' => 'datetime', + 'menu_api_create_user_guid' => 'string', + 'menu_api_update_time' => 'datetime', + 'menu_api_update_user_guid' => 'string', + 'menu_api_delete_time' => 'datetime', + 'menu_api_delete_user_guid' => 'string', + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'menu_api_create_time'; + // 修改时间 + protected $updateTime = 'menu_api_update_time'; + /** + * 状态 启用 + */ + const STATUS_ENABLE = 1; + /** + * 状态 禁用 + */ + const STATUS_DISABLE = 2; + // 状态查询范围 + public function scopeStatus($query, $status = self::STATUS_ENABLE) + { + $query->where('menu_api_status', $status); + } + + /** + * 新增前 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->menu_api_status = self::STATUS_ENABLE; + $menu_api_urls = explode('/', $model->menu_api_url); + if (count($menu_api_urls) == 3) { + $app = isset($menu_api_urls[0]) ? $menu_api_urls[0] : ''; + $controller = isset($menu_api_urls[1]) ? $menu_api_urls[1] : ''; + $action = isset($menu_api_urls[2]) ? $menu_api_urls[2] : ''; + } else { + $controller = isset($menu_api_urls[0]) ? $menu_api_urls[0] : ''; + $action = isset($menu_api_urls[1]) ? $menu_api_urls[1] : ''; + } + if (!$controller) { + throwErrorMsg("缺少controller", 1); + } + if (!$action) { + throwErrorMsg("缺少action", 1); + } + $controller = str_replace('.', '\\', $controller); + if ($app) { + $class = "\\app\\$app\\controller\\$controller"; + } else { + $class = "\\app\\controller\\$controller"; + } + if (!class_exists($class)) { + throwErrorMsg("controller不存在 $controller", 1); + } + if (!method_exists($class, $action)) { + throwErrorMsg("action不存在 $action", 1); + } + $model->completeCreateField(); + } + + /** + * 更新前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + /** + * 获取状态 + * + * @param mixed $value + * @param array $data + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function getMenuApiStatusTextAttr(): string + { + return [ + self::STATUS_ENABLE => '启用', + self::STATUS_DISABLE => '停用' + ][$this->menu_api_status]; + } +} diff --git a/app/common/model/Role/Role.php b/app/common/model/Role/Role.php new file mode 100644 index 0000000..e24275c --- /dev/null +++ b/app/common/model/Role/Role.php @@ -0,0 +1,146 @@ + 'int', + 'role_name' => 'string', + 'role_status' => 'int', + 'role_create_time' => 'datetime', + 'role_create_user_guid' => 'string', + 'role_update_time' => 'datetime', + 'role_update_user_guid' => 'string', + 'role_delete_time' => 'datetime', + 'role_delete_user_guid' => 'string', + 'role_guid' => 'string', + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'role_create_time'; + // 修改时间 + protected $updateTime = 'role_update_time'; + /** + * 状态 启用 + */ + const STATUS_ENABLE = 1; + /** + * 状态 禁用 + */ + const STATUS_DISABLE = 2; + // 状态查询范围 + public function scopeStatus(Query $query, $status = self::STATUS_ENABLE) + { + $query->where('role_status', $status); + } + + /** + * 新增前 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->role_status = self::STATUS_ENABLE; + $model->completeCreateField(); + } + + /** + * 更新前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + /** + * 获取状态 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function getRoleStatusTextAttr(): string + { + return [ + self::STATUS_ENABLE => '启用', + self::STATUS_DISABLE => '停用' + ][$this->role_status]; + } + + /** + * 获取角色用户 + * + * @date 2022-03-02 + * @example + * @author admin + * @since 1.0.0 + */ + public function users(): BelongsToMany + { + return $this->belongsToMany( + User::class, + UserRole::class, + 'user_guid', + 'role_guid' + ); + } + + /** + * 获取角色菜单 + * + * @date 2022-03-02 + * @example + * @author admin + * @since 1.0.0 + */ + public function menus(): BelongsToMany + { + return $this->belongsToMany( + Menu::class, + RoleMenu::class, + 'menu_guid', + 'role_guid' + ); + } +} diff --git a/app/common/model/Role/RoleMenu.php b/app/common/model/Role/RoleMenu.php new file mode 100644 index 0000000..d1467a5 --- /dev/null +++ b/app/common/model/Role/RoleMenu.php @@ -0,0 +1,162 @@ + 'int', + 'menu_guid' => 'string', + 'role_guid' => 'string', + 'role_menu_status' => 'int', + 'role_menu_create_time' => 'datetime', + 'role_menu_create_user_guid' => 'string', + 'role_menu_update_time' => 'datetime', + 'role_menu_update_user_guid' => 'string', + 'role_menu_delete_time' => 'datetime', + 'role_menu_delete_user_guid' => 'string', + 'role_menu_guid' => 'string', + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'role_menu_create_time'; + // 修改时间 + protected $updateTime = 'role_menu_update_time'; + /** + * 状态 启用 + */ + const STATUS_ENABLE = 1; + /** + * 状态 禁用 + */ + const STATUS_DISABLE = 2; + // 状态查询范围 + public function scopeStatus($query, $status = self::STATUS_ENABLE) + { + $query->where('role_menu_status', $status); + } + /** + * 中间关联 + * + * @return ModelPivot + * @date 2022-12-30 + * @example + * @author admin + * @since 1.0.0 + */ + public static function pivot(): ModelPivot + { + return new ModelPivot( + self::class, + Role::class, + Menu::class, + true + ); + } + /** + * 绑定角色菜单 + * + * @param $roles + * @param $menus + * @param array $extra + * @return void + * @date 2022-12-29 + * @example + * @author admin + * @since 1.0.0 + */ + public static function bindRoleMenu($roles, $menus, array $extra = []): void + { + self::pivot()->bind($roles, $menus, $extra); + } + /** + * 解绑角色菜单 + * + * @param $roles + * @param $menus + * @return void + * @date 2022-12-30 + * @example + * @author admin + * @since 1.0.0 + */ + public static function unbindRoleMenu($roles, $menus): void + { + self::pivot()->unbind($roles, $menus); + } + /** + * 绑定角色菜单 + * + * @param $roles + * @param $menus + * @param array $extra + * @return void + * @date 2022-12-29 + * @example + * @author admin + * @since 1.0.0 + */ + public static function rebindRoleMenu($roles, $menus, array $extra = []): void + { + self::pivot()->rebind($roles, $menus, $extra); + } + + /** + * 新增前 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->role_menu_status = self::STATUS_ENABLE; + $model->completeCreateField(); + } + + /** + * 更新前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } +} diff --git a/app/common/model/Tdk/Tdk.php b/app/common/model/Tdk/Tdk.php new file mode 100644 index 0000000..8763643 --- /dev/null +++ b/app/common/model/Tdk/Tdk.php @@ -0,0 +1,184 @@ + "int", + + "tdk_guid" => "string", + + "tdk_type" => "string", + + "tdk_title" => "string", + + "tdk_description" => "string", + + "tdk_keyword" => "string", + + "tdk_create_time" => "datetime", + + "tdk_create_user_guid" => "string", + + "tdk_update_time" => "datetime", + + "tdk_update_user_guid" => "string", + + "tdk_delete_time" => "datetime", + + "tdk_delete_user_guid" => "string", + + ]; + // 设置json类型字段 + protected $json = ['']; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'tdk_create_time'; + // 修改时间 + protected $updateTime = 'tdk_update_time'; + + + // excel导入/下载模板表头 + public const EXCELFIELD = [ + 'tdk_type' => 'tdk所属模块', + 'tdk_title' => '网页标题', + 'tdk_description' => '网页简介', + 'tdk_keyword' => '网页关键词', + ]; + + + + /** + * 新增前 + */ + public static function onBeforeInsert(self $model): void + { + Validate::unique( + self::class, + $model->tdk_guid, + $model->getData(), + ['tdk_type' => 'tdk模块',], + ['tdk_type' => '一个tdk模块只能拥有一个tdk数据'] + ); + $model->completeCreateField(); + } + + /** + * 更新前 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + /** + * 导出Excel + */ + public static function exportExcel($select) + { + $data = [[ + 'tdk所属模块', + '网页标题', + '网页简介', + '网页关键词' + ]]; + foreach ($select as $key => $val) { + // 字典取值 + $tdk_type = ModelDictionary::getDictionaryData('tdk_type'); + $val['tdk_type'] = ModelDictionary::getDataDictionaryName($tdk_type, $val['tdk_type']); + + $data[] = [ + $val['tdk_type'], + $val['tdk_title'], + $val['tdk_description'], + $val['tdk_keyword'], + ]; + } + $excel = (new Excel())->exporTsheet($data); + $excel->save('网站tdk.xlsx'); + } + + /** + * 导入excel + */ + public static function importExcel($file) + { + $msg = []; + + Db::startTrans(); + try { + $excel = new Excel($file); + $data = $excel->parseExcel( + Tool::getExcelRule(self::EXCELFIELD), + [ + 'titleLine' => [1] + ] + ); + if (!$data) throwErrorMsg('excel无数据', 1); + $msg = []; + foreach ($data as $line => $value) { + try { + $model = self::importExcelInit($value); + $msg[] = "{$line} 新增成功!
"; + } catch (\Throwable $th) { + $msg[] = "{$line} {$th->getMessage()}
"; + } + } + Db::commit(); + return implode(', ', $msg); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + /** + * 导入excel初始化 + */ + public static function importExcelInit($value) + { + $tdk_type = $value['tdk_type']; + $tdk_title = $value['tdk_title']; + $tdk_description = $value['tdk_description']; + $tdk_keyword = $value['tdk_keyword']; + + // 字典设值 + $dic_tdk_type = ModelDictionary::getDictionaryData('tdk_type'); + $tdk_type = ModelDictionary::getDataDictionaryValue($dic_tdk_type, $value['tdk_type']); + + return self::create([ + 'tdk_type' => $tdk_type, + 'tdk_title' => $tdk_title, + 'tdk_description' => $tdk_description, + 'tdk_keyword' => $tdk_keyword, + ]); + } +} diff --git a/app/common/model/Test/Test.php b/app/common/model/Test/Test.php new file mode 100644 index 0000000..50a6b2f --- /dev/null +++ b/app/common/model/Test/Test.php @@ -0,0 +1,168 @@ + 'int' , + 'test_guid' => 'string' , + 'test_name' => 'string' , + 'test_show_status' => 'string' , + 'test_score' => '' , + 'test_sort' => 'int' , + 'test_create_time' => 'datetime' , + 'test_create_user_guid' => 'string' , + 'test_update_time' => 'datetime' , + 'test_update_user_guid' => 'string' , + 'test_delete_time' => 'datetime' , + 'test_delete_user_guid' => 'string' , + + ]; + // 设置json类型字段 + protected $json = ['']; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'test_create_time'; + // 修改时间 + protected $updateTime = 'test_update_time'; + + + // excel导入/下载模板表头 + public const EXCELFIELD = [ +'test_name' => '名称', +'test_show_status' => '首页是否展示', +'test_score' => '评分', +'test_sort' => '排序', +]; + + + + /** + * 新增前 + */ + public static function onBeforeInsert(self $model): void + { + // self::checkRepeatData($model); + $model->completeCreateField(); + } + + /** + * 更新前 + */ + public static function onBeforeUpdate(self $model): void + { + // self::checkRepeatData($model); + $model->completeUpdateField(); + } + + /** + * 删除前 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + + /** + * 导出Excel + * + * @param array $select 导出的数据 + */ + public static function exportExcel(array $select): void + { + $data = [[ +'名称', +'首页是否展示', +'评分', +'排序' +]]; + foreach ($select as $key => $val) { + $data[] = [ +$val['test_name'], +$val['test_show_status'], +$val['test_score'], +$val['test_sort'], +]; + } + $excel = (new Excel())->exporTsheet($data); + $excel->save('测试.xlsx'); + } + + + /** + * 导入excel + * + * @param \app\common\arw\adjfut\src\UploadFile $file excel + */ + public static function importExcel(\app\common\arw\adjfut\src\UploadFile $file): string + { + $msg = []; + + Db::startTrans(); + try { + $excel = new Excel($file); + $data = $excel->parseExcel( + Tool::getExcelRule(self::EXCELFIELD), + [ + 'titleLine' => [1] + ]); + if (!$data) throwErrorMsg('excel无数据', 1); + $msg = []; + foreach ($data as $line => $value) { + try { + $model = self::importExcelInit($value); + $msg[] = "{$line} 新增成功!
"; + } catch (\Throwable $th) { + $msg[] = "{$line} {$th->getMessage()}
"; + } + } + Db::commit(); + return implode(', ', $msg); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + + /** + * 导入excel初始化 + * + * @param array $value excel每行数据 + */ + public static function importExcelInit(array $value):void + { + $test_name = $value['test_name'];$test_show_status = $value['test_show_status'];$test_score = $value['test_score'];$test_sort = $value['test_sort']; + + self::create( + ['test_name' => $test_name, +'test_show_status' => $test_show_status, +'test_score' => $test_score, +'test_sort' => $test_sort, +], + ['test_name','test_show_status','test_score','test_sort',] + ); + } + + + +} diff --git a/app/common/model/Token.php b/app/common/model/Token.php new file mode 100644 index 0000000..f87c75e --- /dev/null +++ b/app/common/model/Token.php @@ -0,0 +1,337 @@ + 'int', + 'token_guid' => 'string', + 'user_guid' => 'string', + 'token_menu' => 'json', + 'token_api' => 'json', + 'token_exp_time' => 'datetime', + 'token_content' => 'string', + 'token_create_time' => 'datetime', + 'token_update_time' => 'datetime', + ]; + // json数据字段 + protected $json = ['token_menu', 'token_api']; + // 设置JSON数据返回数组 + protected $jsonAssoc = true; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'token_create_time'; + // 修改时间 + protected $updateTime = 'token_update_time'; + /** + * 当前用户 + * + * @var User + */ + private static $user = null; + /** + * 当前model + * + * @var self + */ + private static $current = null; + + /** + * 用户 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function user(): hasOne + { + return $this->hasOne(User::class, 'user_guid', 'user_guid'); + } + + /** + * 设置过期时间 + * + * @param mixed $value + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public function setTokenExpTimeAttr($value): string + { + return date('Y-m-d H:i:s', $value); + } + + /** + * 判断是否过期 + * + * @return boolean + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ + public function getIsExpAttr(): bool + { + return time() >= $this->token_exp_timestamp; + } + + /** + * 获取过期时间戳 + * + * @return integer + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ + public function getTokenExpTimestampAttr(): int + { + return strtotime($this->token_exp_time); + } + + + /** + * 新增前 + * + * @param self $model + * @return void + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->token_content = $model->generateContent(); + $model->token_guid = self::generateGuid(); + } + + /** + * 登陆 + * 如果当前存在有效token 更新过期时间 + * 如果token已过期 更新token和过期时间 + * 如果不存在token 新增数据 + * + * @param string $guid 用户id + * @param array $options 配置项 + * @param int $options[expTime] 过期时间(时间戳) 不传默认两小时 + * @param array $options[menu] 菜单 + * @param int $options[api] 接口 + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function login(string $guid, array $options = []): self + { + $expTime = 0; + $menu = []; + $api = []; + if (is_array($options)) { + $expTime = Arr::get($options, 'expTime', 0); + $menu = Arr::get($options, 'menu', []); + $api = Arr::get($options, 'api', []); + } + if ($expTime == 0) { + $expTime = time() + (24 * 60 * 60); + } + $data = [ + 'token_exp_time' => $expTime + ]; + if ($menu) { + $data['token_menu'] = $menu; + } + if ($api) { + $data['token_api'] = $api; + } + /** + * @var Token + */ + $model = self::where([ + 'user_guid' => $guid, + ])->find(); + // 数据不存在 + if (!$model) { + return self::create($data + [ + 'user_guid' => $guid, + ]); + } + // token已过期 + if ($model->is_exp) { + $data['token_content'] = $model->generateContent($model); + } + $model->save($data); + return $model; + } + + /** + * 登出 + * + * @date 2022-03-15 + * @example + * @author admin + * @since 1.0.0 + */ + public static function logout(): void + { + self::getCurrent()->delete(); + } + + /** + * 获取请求token + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function getRequestToken(): string + { + $tokenKey = 'token'; + $token = ''; + if (!$token && isset($_GET[$tokenKey])) { + $token = $_GET[$tokenKey]; + } + if (!$token && isset($_POST[$tokenKey])) { + $token = $_POST[$tokenKey]; + } + if (!$token && Request::param($tokenKey)) { + $token = Request::param($tokenKey); + } + if (!$token && Request::header($tokenKey)) { + $token = Request::header($tokenKey); + } + return $token; + } + + /** + * 获取当前用户信息 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function getCurrentUser(): User + { + if (!self::$user) { + $model = self::getCurrent(); + self::$user = $model->user; + if (!self::$user) { + throw new ErrorMsg("用户不存在", 1); + } + } + return self::$user; + } + + /** + * 获取当前有效token + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function getCurrent(): self + { + if (!self::$current) { + $content = self::getRequestToken(); + if (!$content) { + throw new LoginTimeOut("缺少token", 1); + } + $model = self::where([ + 'token_content' => $content, + ])->find(); + if (!$model) { + throw new LoginTimeOut("未登录", 1); + } + if ($model->is_exp) { + throw new LoginTimeOut("登录超时", 1); + } + self::$current = $model; + } + return self::$current; + } + + /** + * 判断是否登录 + * + * @date 2022-05-14 + * @example + * @author admin + * @since 1.0.0 + */ + public static function isLogin(): bool + { + try { + self::getCurrent(); + return true; + } catch (LoginTimeOut $th) { + return false; + } + } + + /** + * 延长token + * 过期前30分钟才会延期 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function delay(): void + { + try { + // 半小时 + $delay = (60 * 30); + $model = self::getCurrent(); + $token_exp_time = $model->token_exp_timestamp; + if (time() >= ($token_exp_time - $delay)) { + self::login($model->user_guid); + } + } catch (LoginTimeOut $th) { + } + } + + /** + * 生成token内容 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + private function generateContent(): string + { + return md5(join('-', [ + $this->token_exp_time, + $this->user_guid, + ]) . time()); + } +} diff --git a/app/common/model/User/User.php b/app/common/model/User/User.php new file mode 100644 index 0000000..9fef5b7 --- /dev/null +++ b/app/common/model/User/User.php @@ -0,0 +1,548 @@ + 'int', + 'user_guid' => 'string', + 'user_img' => 'string', + 'user_name' => 'string', + 'user_password' => 'string', + 'user_phone' => 'string', + 'user_status' => 'int', + 'user_department' => 'string', + 'user_position' => 'string', + 'user_create_time' => 'datetime', + 'user_create_user_guid' => 'string', + 'user_update_time' => 'datetime', + 'user_update_user_guid' => 'string', + 'user_delete_time' => 'datetime', + 'user_delete_user_guid' => 'string', + ]; + // 只读 + protected $readonly = [ + 'user_id', + 'user_guid', + 'user_create_time', + 'user_create_user_guid' + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'user_create_time'; + // 修改时间 + protected $updateTime = 'user_update_time'; + + public static $dictionaryMap = [ + 'user_status' => [ + self::STATUS_ENABLE => '启用', + self::STATUS_DISABLE => '停用' + ], + ]; + /** + * 状态 启用 + */ + const STATUS_ENABLE = 1; + /** + * 状态 禁用 + */ + const STATUS_DISABLE = 2; + // 状态查询范围 + public function scopeStatus(Query $query, $status = self::STATUS_ENABLE) + { + $query->where('user_status', $status); + } + + /** + * 写入前 + * + * @param self $model + * @return void + * @date 2023-01-11 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeWrite(self $model): void + { + self::checkRepeatData($model); + } + + /** + * 新增前 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->completeCreateField(); + } + + /** + * 更新前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + /** + * 用户名搜索器 + * + * @param Query $query + * @param [type] $value + * @param [type] $data + * @return void + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function searchUserNameAttr(Query $query, $value, $data): void + { + if ($value) { + $query->whereLike(join('|', [ + 'user_name', + // 'user_phone' + ]), "%$value%"); + } + } + + + /** + * 设置用户密码 + * + * @param string $value + * @return string + * @date 2022-12-28 + * @example + * @author admin + * @since 1.0.0 + */ + public function setUserPasswordAttr(string $value): string + { + return self::encryptPassword($value); + } + + /** + * 获取用户管理员 + * + * @date 2022-03-08 + * @example + * @author admin + * @since 1.0.0 + */ + public function getUserAdminAttr(): bool + { + return $this->user_id == 1; + } + + /** + * 用户登录 + * + * @return Token + * @date 2023-01-03 + * @example + * @author admin + * @since 1.0.0 + */ + public function login(): Token + { + if ($this->user_status != self::STATUS_ENABLE) { + throwErrorMsg('该用户已被停用'); + } + $menus = $this->getUserMenu(); + $api = []; + foreach ($menus as $menu) { + $api = array_merge($api, explode(',', $menu['menu_api_url'])); + } + $api = array_values(array_filter(array_unique($api))); + if (!$api) { + throwErrorMsg('无权登录'); + } + return Token::login($this->user_guid, [ + 'menu' => $menus, + 'api' => $api, + ]); + } + + /** + * 获取用户菜单 + * + * @date 2022-03-15 + * @example + * @author admin + * @since 1.0.0 + */ + public function getUserMenu(): array + { + $result = []; + $query = Menu::join('menu_api', 'menu_api.menu_guid = menu.menu_guid', 'left')->field([ + 'menu.menu_guid', + 'menu.menu_parent_guid', + 'menu.menu_name', + 'menu.menu_url', + 'menu.menu_show', + 'menu.menu_icon', + + 'group_concat(menu_api.menu_api_url)' => 'menu_api_url' + ])->order([ + 'menu.menu_index', + 'menu.menu_order' => 'desc', + ])->group('menu.menu_guid'); + if ($this->user_admin) { + $result = $query->select()->toArray(); + } else { + $menus = []; + $roles = $this->roles()->with([ + 'menus' + ])->select(); + foreach ($roles as $role) { + $menus = array_merge($menus, $role->menus->column('menu_guid')); + } + $result = $query->where([ + ['menu.menu_guid', 'in', $menus] + ])->select()->toArray(); + } + return $result; + } + + /** + * 数据查重 + * + * @param self $model + * @return void + * @date 2022-03-11 + * @example + * @author admin + * @since 1.0.0 + */ + private static function checkRepeatData(self $model) + { + Validate::unique(self::class, $model->user_guid, $model->getData(), [ + 'user_account' => '账户', + 'user_phone' => '手机号', + ]); + } + + /** + * 密码加密 + * + * @param string $password + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function encryptPassword(string $password): string + { + return md5($password); + } + /** + * 获取角色 + * + * @date 2022-03-11 + * @example + * @author admin + * @since 1.0.0 + */ + public function roles(): BelongsToMany + { + return $this->belongsToMany( + Role::class, + UserRole::class, + 'role_guid', + 'user_guid' + ); + } + + /** + * 新增用户角色 + */ + public static function addUserRole($user_guid, $params): void + { + $user_role_arr = $params['roles']; + + foreach ($user_role_arr as $role => $item) { + Db::startTrans(); + try { + $add_data = [ + 'user_guid' => $user_guid, + 'role_guid' => $item, + ]; + $user_role = ModelUserRole::create($add_data); + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + } + + /** + * 编辑用户角色 + */ + public static function editUserRole($params): void + { + // 从学生服务(副表)查询出所有当前学生的服务,进行删除 + ModelUserRole::where('user_guid', $params['user_guid'])->select()->delete(); + + // 再把传值的数据,写入副表 + $user_role_arr = $params['roles']; + foreach ($user_role_arr as $key => $item) { + Db::startTrans(); + try { + $add_data = [ + 'user_guid' => $params['user_guid'], + 'role_guid' => $item, + ]; + ModelUserRole::create($add_data); + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + } + + /** + * 用户角色guid获取器 + */ + public function getRolesAttr($value, $data) + { + $user_guid = $data['user_guid']; + $user_role_guid_arr = ModelUserRole::field([ + 'role_guid', + ])->where('user_guid', $user_guid)->select(); + + $arr = []; + foreach ($user_role_guid_arr as $key => $value) { + $arr[] = $value['role_guid']; + } + return $arr; + + // return array_values($user_key_guid_arr); + } + + /** + * 用户角色名称获取器 + */ + public function getRoleNameAttr($value, $data) + { + $user_guid = $data['user_guid']; + $user_role_guid_arr = ModelUserRole::field([ + 'role_guid', + ])->where('user_guid', $user_guid)->select(); + + $arr = []; + foreach ($user_role_guid_arr as $key => $value) { + $model = Role::where('role_guid', $value['role_guid'])->find()->role_name; + $arr[] = $model; + } + return implode(",", $arr); + + // return array_values($user_key_guid_arr); + } + + /** + * 用户角色名 + */ + public static function getRoleName($data) + { + $user_guid = $data['user_guid']; + $user_role_guid_arr = ModelUserRole::field([ + 'role_guid', + ])->where('user_guid', $user_guid)->select(); + + $arr = []; + foreach ($user_role_guid_arr as $key => $value) { + $model = Role::where('role_guid', $value['role_guid'])->find()->role_name; + $arr[] = $model; + } + return implode(",", $arr); + + // return array_values($user_key_guid_arr); + } + + + /** + * 角色名称转角色guid数组 + */ + public static function RoleNameToRoleGuidArr($params) + { + $roles = explode(",", $params['roles']); + + $user_role_guid_arr = []; + foreach ($roles as $key => $item) { + $role = Role::where('role_name', $item)->find(); + if (!$role) throwErrorMsg('角色不存在!'); + $user_role_guid_arr[] = $role->role_guid; + } + + return $user_role_guid_arr; + } + + /** + * 导出Excel + */ + public static function exportExcel($select) + { + $data = [[ + '用户名', + '头像', + '角色', + '手机号', + ]]; + foreach ($select as $key => $val) { + + $val['user_img'] = Excel::ExportImgFiled($val['user_img']); + $user_role_name = self::getRoleName($val); + + $data[] = [ + $val['user_name'], + $val['user_img'], + $user_role_name, + $val['user_phone'], + ]; + } + $excel = (new Excel())->exporTsheet($data); + $excel->save('用户.xlsx'); + } + + + /** + * 导入数据处理 + */ + public static function HanleImportData($value) + { + // 判断用户是否存在 + $user_name = $value['user_name']; + $user_type = self::where('user_name', $user_name)->find(); + if ($user_type) throwErrorMsg("{$user_name} 用户已存在,导入失败!"); + + + // 判断角色是否存在 + $roles_arr = explode(",", $value['roles']); + foreach ($roles_arr as $key => $item) { + $role = Role::where('role_name', $item)->find(); + if (!$role) throwErrorMsg("{$item} 角色不存在,导入失败!"); + } + + $password = md5($value['user_password']); + + $model = self::create([ + 'user_name' => $value['user_name'], + 'user_img' => $value['user_img'], + 'user_phone' => $value['user_phone'], + 'user_password' => $password, + 'user_status' => 1, + ]); + + $user_guid = $model->user_guid; + $value['roles'] = self::RoleNameToRoleGuidArr($value); + self::addUserRole($user_guid, $value); + } + + + /** + * 导入Excel + */ + public static function importExcel($file) + { + Db::startTrans(); + try { + $excel = new Excel($file); + // 表头 + $data = $excel->parseExcel( + [ + [ + 'title' => '用户名', + 'validate' => 'require', + 'field' => 'user_name', + ], [ + 'title' => '头像', + 'field' => 'user_img', + ], [ + 'title' => '角色', + 'validate' => 'require', + 'field' => 'roles', + ], [ + 'title' => '手机号', + 'field' => 'user_phone', + ], [ + 'title' => '密码', + 'field' => 'user_password', + ] + ], + [ + 'titleLine' => [1] + ] + ); + if (!$data) throwErrorMsg('excel无数据', 1); + $error = []; + + foreach ($data as $line => $value) { + try { + self::HanleImportData($value); + $error[] = "{$line} 用户:【{$value['user_name']}】新增成功!
"; + } catch (\Throwable $th) { + $error[] = "{$line} 用户:【{$value['user_name']}】{$th->getMessage()}
"; + } + } + Db::commit(); + return implode(', ', $error); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } +} diff --git a/app/common/model/User/UserRole.php b/app/common/model/User/UserRole.php new file mode 100644 index 0000000..3c89d91 --- /dev/null +++ b/app/common/model/User/UserRole.php @@ -0,0 +1,157 @@ + 'int', + 'user_guid' => 'string', + 'role_guid' => 'string', + 'user_role_status' => 'int', + 'user_role_create_time' => 'datetime', + 'user_role_create_user_guid' => 'string', + 'user_role_update_time' => 'datetime', + 'user_role_update_user_guid' => 'string', + 'user_role_delete_time' => 'datetime', + 'user_role_delete_user_guid' => 'string', + 'user_role_guid' => 'string', + ]; + // 开启自动写入时间戳字段 + protected $autoWriteTimestamp = 'datetime'; + // 创建时间 + protected $createTime = 'user_role_create_time'; + // 修改时间 + protected $updateTime = 'user_role_update_time'; + /** + * 状态 启用 + */ + const STATUS_ENABLE = 1; + /** + * 状态 禁用 + */ + const STATUS_DISABLE = 2; + // 状态查询范围 + public function scopeStatus($query, $status = self::STATUS_ENABLE) + { + $query->where('user_role_status', $status); + } + /** + * 中间关联 + * + * @return ModelPivot + * @date 2022-12-30 + * @example + * @author admin + * @since 1.0.0 + */ + public static function pivot(): ModelPivot + { + return new ModelPivot( + self::class, + User::class, + Role::class, + true + ); + } + /** + * 绑定用户角色 + * + * @param $users + * @param $roles + * @param array $extra + * @return void + * @date 2022-12-29 + * @example + * @author admin + * @since 1.0.0 + */ + public static function bindUserRole($users, $roles, array $extra = []): void + { + self::pivot()->bind($users, $roles, $extra); + } + /** + * 解绑用户角色 + * + * @param $users + * @param $roles + * @return void + * @date 2022-12-30 + * @example + * @author admin + * @since 1.0.0 + */ + public static function unbindUserRole($users, $roles): void + { + self::pivot()->unbind($users, $roles); + } + /** + * 绑定用户角色 + * + * @param $users + * @param $roles + * @param array $extra + * @return void + * @date 2022-12-29 + * @example + * @author admin + * @since 1.0.0 + */ + public static function rebindUserRole($users, $roles, array $extra = []): void + { + self::pivot()->rebind($users, $roles, $extra); + } + + /** + * 新增前 + * + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeInsert(self $model): void + { + $model->user_role_status = self::STATUS_ENABLE; + $model->completeCreateField(); + } + + /** + * 更新前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeUpdate(self $model): void + { + $model->completeUpdateField(); + } + + /** + * 删除前 + * + * @date 2022-02-28 + * @example + * @author admin + * @since 1.0.0 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } +} diff --git a/app/common/traits/Auth.php b/app/common/traits/Auth.php new file mode 100644 index 0000000..cd18bc9 --- /dev/null +++ b/app/common/traits/Auth.php @@ -0,0 +1,110 @@ +request; + $gc = $request->controller(); + $ga = $request->action(); + $ignoreLogin = Arr::get($this->ignoreLogin, $gc, []); + if (!is_array($ignoreLogin)) { + $ignoreLogin = []; + } + foreach ($ignoreLogin as $action) { + if ($action == '*' || $action == $ga) { + return true; + } + } + return false; + } + + /** + * 验证用户权限 + * + * @return boolean + * @date 2022-05-14 + * @example + * @author admin + * @since 1.0.0 + */ + private function validateUser(): bool + { + return $this->validateMenuApi( + Token::getCurrentUser()->getUserMenu() + ); + } + + /** + * 验证菜单接口 + * + * @return boolean + * @date 2022-05-14 + * @example + * @author admin + * @since 1.0.0 + */ + private function validateMenuApi(array $menus): bool + { + $apis = []; + foreach ($menus as $menu) { + $apis = array_merge($apis, explode(',', $menu['menu_api_url'] ?? '')); + } + return $this->validateApi($apis); + } + + /** + * 验证接口 + * + * @param array $apis + * @return boolean + * @date 2022-05-14 + * @example + * @author admin + * @since 1.0.0 + */ + private function validateApi(array $apis): bool + { + $request = $this->request; + $apis = array_values(array_filter(array_unique($apis))); + $api = join('/', [ + $request->controller(), + $request->action() + ]); + return in_array($api, $apis); + } +} diff --git a/app/common/traits/Model.php b/app/common/traits/Model.php new file mode 100644 index 0000000..9546820 --- /dev/null +++ b/app/common/traits/Model.php @@ -0,0 +1,254 @@ +completeBaseField('create', $user, $timeType); + } + + /** + * 完善更新字段 + * + * @param string $user + * @param string $timeType + * @return void + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ + public function completeUpdateField($user = 'user', string $timeType = 'datetime'): void + { + $this->completeBaseField('update', $user, $timeType); + } + + /** + * 完善删除字段 + * + * @param string $user + * @param string $timeType + * @return void + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ + public function completeDeleteField($user = 'user', string $timeType = 'datetime'): void + { + $this->completeBaseField('delete', $user, $timeType); + } + + /** + * 完善基础字段 + * + * @param string $type create|update|delete + * @param User|string $user + * @param string $timeType datetime|timestamp + * @return void + * @date 2022-03-02 + * @example + * @author admin + * @since 1.0.0 + */ + public function completeBaseField($type, $user = 'user', string $timeType = 'datetime'): void + { + $table = $this->getTable(); + $createFieldTime = "${table}_create_time"; + $createFieldUserGuId = "${table}_create_user_guid"; + $updateFieldTime = "${table}_update_time"; + $updateFieldUserGuId = "${table}_update_user_guid"; + $deleteFieldTime = "${table}_delete_time"; + $deleteFieldUserGuId = "${table}_delete_user_guid"; + $guidField = "${table}_guid"; + + $time = self::getCompleteBaseFieldTimeType($timeType); + $user_guid = self::getCompleteBaseFieldUser($user); + if (!$type) { + throw new ErrorMsg("时间类型异常", 1); + } + $this->$updateFieldTime = $time; + $this->$updateFieldUserGuId = $user_guid; + + if ($type === 'create') { + $this->$createFieldTime = $time; + $this->$createFieldUserGuId = $user_guid; + $this->$guidField = self::generateGuid(); + } + if ($type === 'delete') { + $this->$deleteFieldTime = $time; + $this->$deleteFieldUserGuId = $user_guid; + } + } + + // /** + // * 设置不使用的全局查询范围更新 + // * 支持 onBeforeUpdate|onAfterUpdate|onBeforeWrite|onAfterWrite 事件 + // * + // * @return void + // * @date 2022-03-12 + // * @example + // * @author admin + // * @since 1.0.0 + // */ + // public static function withoutGlobalScopeUpdate(array $scope, array $data) + // { + // $model = new static; + // $pk = $model->getPk(); + // if (!isset($data[$pk])) { + // throw new ErrorMsg("缺少主键", 1); + // } + // $query = $model::withoutGlobalScope($scope)->find($data[$pk]); + // if ($query === null) { + // throw new ErrorMsg("数据不存在", 1); + // } + // if ( + // $model::onBeforeWrite($model) === false || + // $model::onBeforeUpdate($model) === false + // ) { + // return false; + // } + // $model::withoutGlobalScope($scope)->save(array_merge($data, $model->getData())); + // $model::onAfterUpdate($model); + // $model::onAfterWrite($model); + // } + + /** + * 生成guid + * + * @param array|string $value + * @return string + * @date 2022-02-22 + * @example + * @author admin + * @since 1.0.0 + */ + protected static function generateGuid($value = '') + { + return Tool::generateGuid($value); + } + + /** + * 数据验证 + * 除非是require字段 有数据才验证 + * + * @param array $data 数据 + * @param array $rules 规则 + * @param array $message 提示信息 + * @return void + * @throws ValidateException + */ + protected static function dataValidate(array $data, array $rules, array $message = []) + { + $_rules = []; + $dataKeys = array_keys($data); + foreach ($rules as $key => $rule) { + list($field) = explode('|', $key); + // 字段存在规则并且有值 或者 必传 + if ((in_array($field, $dataKeys) && !is_null($data[$field])) || preg_match('/require/', $rule)) { + $_rules[$key] = $rule; + } + } + Validate::check($data, $_rules, $message); + } + + /** + * 获取完善基础字段用户 + * + * @param User|string $user + * @return string + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ + private static function getCompleteBaseFieldUser($user): string + { + $cacheUserGuid = self::$cacheUserGuid; + $user_guid = ''; + switch (true) { + case (is_string($cacheUserGuid) && $cacheUserGuid): + $user_guid = $cacheUserGuid; + break; + case (is_bool($cacheUserGuid) && $cacheUserGuid === false): + // 无需验证 获取用户Guid + break; + case (is_bool($user) && $user === false): + // 无需验证 获取用户Guid + break; + case ($user === 'user'): + case ($user instanceof User): + $user = $user === 'user' ? Request::getCurrentUser() : $user; + $user_guid = $user->user_guid; + break; + } + return $user_guid; + } + + /** + * 获取模型时间类型 + * + * @param string $timeType datetime|timestamp + * @return string + * @date 2022-12-27 + * @example + * @author admin + * @since 1.0.0 + */ + private static function getCompleteBaseFieldTimeType($timeType): string + { + $time = ''; + switch ($timeType) { + case 'datetime': + $time = date('Y-m-d H:i:s'); + break; + case 'timestamp': + $time = time(); + break; + } + return $time; + } +} diff --git a/app/event.php b/app/event.php new file mode 100644 index 0000000..74d0b62 --- /dev/null +++ b/app/event.php @@ -0,0 +1,20 @@ + [], + + 'listen' => [ + 'AppInit' => [], + 'HttpRun' => [], + 'HttpEnd' => [ + DelayToken::class + ], + 'LogLevel' => [], + 'LogWrite' => [], + ], + + 'subscribe' => [], +]; diff --git a/app/middleware.php b/app/middleware.php new file mode 100644 index 0000000..32d02ce --- /dev/null +++ b/app/middleware.php @@ -0,0 +1,10 @@ + Request::class, + 'think\exception\Handle' => ExceptionHandle::class, +]; diff --git a/app/resources/view/admin/controller.tpl b/app/resources/view/admin/controller.tpl new file mode 100644 index 0000000..8f45778 --- /dev/null +++ b/app/resources/view/admin/controller.tpl @@ -0,0 +1,83 @@ +param(); + $con = []; + + {$whereContent} + + $query = Model{$className}::where($con) + ->field({$queryFields}) + ->order('{$orderField}', '{$orderMode}'); + + return $isExport ? $query->select()->toArray() : msg("获取{$functionName}列表成功!",$query); + } + + /** + * 添加{$functionName} + */ + public function add{$className}(Request $request): array + { + $params = $request->param(); + $this->validate($params, {$addRequireFields}); + $model = Model{$className}::create($params,{$addAllowFields}); + return msg('添加成功!'); + } + + /** + * 编辑{$functionName} + */ + public function edit{$className}(Request $request): array + { + $params = $request->param(); + $this->validate($params, {$editRequireFields}); + $model = Model{$className}::where('{$businessName}_guid',$params['{$businessName}_guid'])->find(); + if (!$model) throwErrorMsg("该{$functionName}不存在", 1); + $model->allowField({$editAllowFields})->save($params); + return msg('编辑成功!'); + } + + /** + * 删除{$functionName} + */ + public function delete{$className}(Request $request): array + { + $params = $request->param(); + $this->validate($params, [ + '{$businessName}_guid' => 'require', + ]); + ${$businessName} = Model{$className}::where([ + '{$businessName}_guid' => explode(',', $params['{$businessName}_guid']) + ])->select(); + ${$businessName}->delete(); + return msg('删除成功!'); + } + + {$exportExcelContent} + + {$downloadTempContent} + + {$importExcelContent} + +} diff --git a/app/resources/view/admin/model.tpl b/app/resources/view/admin/model.tpl new file mode 100644 index 0000000..9b084c7 --- /dev/null +++ b/app/resources/view/admin/model.tpl @@ -0,0 +1,70 @@ +completeCreateField(); + } + + /** + * 更新前 + */ + public static function onBeforeUpdate(self $model): void + { + // self::checkRepeatData($model); + $model->completeUpdateField(); + } + + /** + * 删除前 + */ + public static function onBeforeDelete(self $model): void + { + $model->completeDeleteField(); + } + + {$exportExcelContent} + + {$importExcelContent} + + {$importExcelInitContent} + + +} diff --git a/app/resources/view/api/controller.tpl b/app/resources/view/api/controller.tpl new file mode 100644 index 0000000..f680230 --- /dev/null +++ b/app/resources/view/api/controller.tpl @@ -0,0 +1,57 @@ +param(); + $con = []; + + {$whereContent} + + $query = Model{$className}::where($con) + ->field({$queryFields}) + ->order('{$orderField}', '{$orderMode}') + ->select(); + + return msg(0, "获取{$functionName}列表成功!", [ + 'data' => $query, + 'count' => count($query) + ]); + } + + /** + * 获取{$functionName}详情 + */ + public function get{$className}Info(Request $request): array + { + $params = $request->param(); + + $this->validate($params, ['{$businessName}_guid' => 'require']); + + $find = Model{$className}::field({$queryFields}) + ->where('{$businessName}_guid',$params['{$businessName}_guid']) + ->find(); + + return msg(0,'获取{$functionName}详情成功!',['data' => $find]); + } + +} diff --git a/app/resources/view/business/sort/sortAdminController.tpl b/app/resources/view/business/sort/sortAdminController.tpl new file mode 100644 index 0000000..ebd84ca --- /dev/null +++ b/app/resources/view/business/sort/sortAdminController.tpl @@ -0,0 +1,113 @@ +param(); + $con = []; + + {$whereContent} + + $query = Model{$className}::where($con) + ->field({$queryFields}) + ->order('{$orderField}', '{$orderMode}'); + + return $isExport ? $query->select()->toArray() : msg("获取{$functionName}列表成功!",$query); + } + + /** + * 添加{$functionName} + */ + public function add{$className}(Request $request): array + { + Db::startTrans(); + Tool::adminLockTableWrite('{$businessName}'); + try { + $params = $request->param(); + $this->validate($params, {$addRequireFields}); + $model = Model{$className}::create($params,{$addAllowFields}); + Db::commit(); + Tool::unlockTable(); + return msg('添加成功!'); + } catch (\Throwable $th) { + Db::rollback(); + Tool::unlockTable(); + throw $th; + } + } + + /** + * 编辑{$functionName} + */ + public function edit{$className}(Request $request): array + { + Db::startTrans(); + Tool::adminLockTableWrite('{$businessName}'); + try { + $params = $request->param(); + $this->validate($params, {$editRequireFields}); + $model = Model{$className}::where('{$businessName}_guid',$params['{$businessName}_guid'])->find(); + if (!$model) throwErrorMsg("该{$functionName}不存在", 1); + $model->allowField({$editAllowFields})->save($params); + Db::commit(); + Tool::unlockTable(); + return msg('编辑成功!'); + } catch (\Throwable $th) { + Db::rollback(); + Tool::unlockTable(); + throw $th; + } + } + + /** + * 删除{$functionName} + */ + public function delete{$className}(Request $request): array + { + Db::startTrans(); + Tool::adminLockTableWrite('{$businessName}'); + try { + $params = $request->param(); + $this->validate($params, [ + '{$businessName}_guid' => 'require', + ]); + ${$businessName} = Model{$className}::where([ + '{$businessName}_guid' => explode(',', $params['{$businessName}_guid']) + ])->select(); + ${$businessName}->delete(); + Db::commit(); + Tool::unlockTable(); + return msg('删除成功!'); + } catch (\Throwable $th) { + Db::rollback(); + Tool::unlockTable(); + throw $th; + } + } + + {$exportExcelContent} + + {$downloadTempContent} + + {$importExcelContent} + +} diff --git a/app/resources/view/business/sort/sortModel.tpl b/app/resources/view/business/sort/sortModel.tpl new file mode 100644 index 0000000..d63b1a7 --- /dev/null +++ b/app/resources/view/business/sort/sortModel.tpl @@ -0,0 +1,74 @@ +completeCreateField(); + } + + /** + * 更新前 + */ + public static function onBeforeUpdate(self $model): void + { + Tool::dataEditSortProc($model); + $model->completeUpdateField(); + } + + /** + * 删除前 + */ + public static function onBeforeDelete(self $model): void + { + Tool::dataDeleteSortProc($model); + $model->completeDeleteField(); + } + + {$exportExcelContent} + + {$importExcelContent} + + {$importExcelInitContent} + + +} diff --git a/app/resources/view/business/webApi.tpl b/app/resources/view/business/webApi.tpl new file mode 100644 index 0000000..2467282 --- /dev/null +++ b/app/resources/view/business/webApi.tpl @@ -0,0 +1,23 @@ +import { api} from '~/utils/axios'; + +/** + * 获取${functionName}内容 + * @param {Object} data + * @return {Promise} api + */ +export function get${className}(data) { + return api.post('${moduleName}.${className}/get${className}', data); +} + +/** + * 编辑${functionName} + * @param {Object} data + * @return {Promise} api + */ +export function edit${className}(data) { + return api.post('${moduleName}.${className}/edit${className}', data, { + isTransformResponse: true, + isShowSuccessMessage: true, + errorMessageText: '编辑失败' + }); +} diff --git a/app/resources/view/business/webApiController.tpl b/app/resources/view/business/webApiController.tpl new file mode 100644 index 0000000..47975ff --- /dev/null +++ b/app/resources/view/business/webApiController.tpl @@ -0,0 +1,32 @@ +param(); + + $query = Model{$className}::field({$queryFields})->where(1)->find(); + + return [ + 'code' => 0, + 'data' => $query, + 'msg' => 'ok' + ]; + } + +} diff --git a/app/resources/view/business/webController.tpl b/app/resources/view/business/webController.tpl new file mode 100644 index 0000000..62b33ff --- /dev/null +++ b/app/resources/view/business/webController.tpl @@ -0,0 +1,44 @@ +param(); + + $query = Model{$className}::field({$queryFields})->where(1)->find(); + + return [ + 'code' => 0, + 'data' => $query, + 'msg' => 'ok' + ]; + } + + /** + * 编辑{$functionName} + */ + public function edit{$className}(Request $request): array + { + $params = $request->param(); + $this->validate($params, {$editRequireFields}); + $model = Model{$className}::where(1)->find(); + $model->allowField({$editAllowFields})->save($params); + return msg('编辑成功!'); + } + +} diff --git a/app/resources/view/business/webIndex.tpl b/app/resources/view/business/webIndex.tpl new file mode 100644 index 0000000..72e43d3 --- /dev/null +++ b/app/resources/view/business/webIndex.tpl @@ -0,0 +1,90 @@ + + + + \ No newline at end of file diff --git a/app/resources/view/jsVue/add.tpl b/app/resources/view/jsVue/add.tpl new file mode 100644 index 0000000..3e0eeae --- /dev/null +++ b/app/resources/view/jsVue/add.tpl @@ -0,0 +1,105 @@ + + + + + diff --git a/app/resources/view/jsVue/api.tpl b/app/resources/view/jsVue/api.tpl new file mode 100644 index 0000000..f57ad83 --- /dev/null +++ b/app/resources/view/jsVue/api.tpl @@ -0,0 +1,56 @@ +import { api, downloadFile, createApiUrl} from '~/utils/axios'; + +${excelFun} + +${imageFun} + +${fileFun} + +${dictFun} + +/** + * 获取${functionName}列表 + * @param {Object} data + * @return {Promise} api + */ +export function get${className}List(data) { + return api.post('${moduleName}.${className}/get${className}List', data); +} + +/** + * 删除${functionName} + * @param {Object} data + * @return {Promise} api + */ +export function delete${className}(data) { + return api.post('${moduleName}.${className}/delete${className}', data, { + isTransformResponse: true, + isShowSuccessMessage: true, + errorMessageText: '删除失败' + }); +} + +/** + * 添加${functionName} + * @param {Object} data + * @return {Promise} api + */ +export function add${className}(data) { + return api.post('${moduleName}.${className}/add${className}', data, { + isTransformResponse: true, + isShowSuccessMessage: true, + errorMessageText: '添加失败' + }); +} +/** + * 编辑${functionName} + * @param {Object} data + * @return {Promise} api + */ +export function edit${className}(data) { + return api.post('${moduleName}.${className}/edit${className}', data, { + isTransformResponse: true, + isShowSuccessMessage: true, + errorMessageText: '编辑失败' + }); +} diff --git a/app/resources/view/jsVue/detail.tpl b/app/resources/view/jsVue/detail.tpl new file mode 100644 index 0000000..9f7cf24 --- /dev/null +++ b/app/resources/view/jsVue/detail.tpl @@ -0,0 +1,62 @@ + + + + + diff --git a/app/resources/view/jsVue/edit.tpl b/app/resources/view/jsVue/edit.tpl new file mode 100644 index 0000000..5e9706f --- /dev/null +++ b/app/resources/view/jsVue/edit.tpl @@ -0,0 +1,104 @@ + + + + + diff --git a/app/resources/view/jsVue/index.tpl b/app/resources/view/jsVue/index.tpl new file mode 100644 index 0000000..8c256d8 --- /dev/null +++ b/app/resources/view/jsVue/index.tpl @@ -0,0 +1,158 @@ + + diff --git a/app/service.php b/app/service.php new file mode 100644 index 0000000..db1ee6a --- /dev/null +++ b/app/service.php @@ -0,0 +1,9 @@ +=7.4.3", + "topthink/framework": "^6.0.0", + "topthink/think-orm": "^2.0", + "topthink/think-captcha": "^3.0", + "phpoffice/phpspreadsheet": "^1.12", + "topthink/think-image": "^1.0", + "topthink/think-multi-app": "^1.0", + "topthink/think-filesystem": "^1.0", + "endroid/qr-code": "^4.6", + "fabpot/goutte": "^4.0", + "overtrue/pinyin": "5.0" + }, + "require-dev": { + "symfony/var-dumper": "^4.2" + }, + "autoload": { + "psr-4": { + "app\\": "app" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist" + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + }, + "repositories": { + "packagist": { + "type": "composer", + "url": "https://mirrors.aliyun.com/composer/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..c1a9297 --- /dev/null +++ b/composer.lock @@ -0,0 +1,3102 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "30ae14f0069740a8761377fb8238ad82", + "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + }, + "time": "2022-12-07T17:46:57+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.4" + }, + "time": "2023-03-01T18:44:03+00:00" + }, + { + "name": "endroid/qr-code", + "version": "4.8.2", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code.git", + "reference": "2436c2333a3931c95e2b96eb82f16f53143d6bba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/2436c2333a3931c95e2b96eb82f16f53143d6bba", + "reference": "2436c2333a3931c95e2b96eb82f16f53143d6bba", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "bacon/bacon-qr-code": "^2.0.5", + "php": "^8.0" + }, + "conflict": { + "khanamiryan/qrcode-detector-decoder": "^1.0.6" + }, + "require-dev": { + "endroid/quality": "dev-master", + "ext-gd": "*", + "khanamiryan/qrcode-detector-decoder": "^1.0.4||^2.0.2", + "setasign/fpdf": "^1.8.2" + }, + "suggest": { + "ext-gd": "Enables you to write PNG images", + "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator", + "roave/security-advisories": "Makes sure package versions with known security issues are not installed", + "setasign/fpdf": "Enables you to use the PDF writer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code", + "homepage": "https://github.com/endroid/qr-code", + "keywords": [ + "code", + "endroid", + "php", + "qr", + "qrcode" + ], + "support": { + "issues": "https://github.com/endroid/qr-code/issues", + "source": "https://github.com/endroid/qr-code/tree/4.8.2" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2023-03-30T18:46:02+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.16.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + }, + "time": "2022-09-18T07:06:19+00:00" + }, + { + "name": "fabpot/goutte", + "version": "v4.0.3", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/Goutte.git", + "reference": "e3f28671c87a48a0f13ada1baea0d95acc2138c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/e3f28671c87a48a0f13ada1baea0d95acc2138c3", + "reference": "e3f28671c87a48a0f13ada1baea0d95acc2138c3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3", + "symfony/browser-kit": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.0" + }, + "type": "application", + "autoload": { + "psr-4": { + "Goutte\\": "Goutte" + }, + "exclude-from-classmap": [ + "Goutte/Tests" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/FriendsOfPHP/Goutte", + "keywords": [ + "scraper" + ], + "support": { + "issues": "https://github.com/FriendsOfPHP/Goutte/issues", + "source": "https://github.com/FriendsOfPHP/Goutte/tree/v4.0.3" + }, + "time": "2023-04-01T09:05:33+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.10", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2022-10-04T09:16:37+00:00" + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching.", + "support": { + "issues": "https://github.com/thephpleague/flysystem-cached-adapter/issues", + "source": "https://github.com/thephpleague/flysystem-cached-adapter/tree/master" + }, + "time": "2020-07-25T15:56:04+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-04-17T13:12:02+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", + "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-mbstring": "*", + "myclabs/php-enum": "^1.5", + "php": "^8.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.9", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.4", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "vimeo/psalm": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2022-12-08T12:29:14+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "b942d263c641ddb5190929ff840c68f78713e937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937", + "reference": "b942d263c641ddb5190929ff840c68f78713e937", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2021-07-05T08:18:36+00:00" + }, + { + "name": "overtrue/pinyin", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/overtrue/pinyin.git", + "reference": "4ee638c108f1230389e17ceaf2d1e0aebc3cedee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/pinyin/zipball/4ee638c108f1230389e17ceaf2d1e0aebc3cedee", + "reference": "4ee638c108f1230389e17ceaf2d1e0aebc3cedee", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.2" + }, + "require-dev": { + "brainmaestro/composer-git-hooks": "^2.7", + "friendsofphp/php-cs-fixer": "^3.2", + "nunomaduro/termwind": "^1.13", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "hooks": { + "pre-commit": [ + "composer test", + "composer fix-style" + ], + "pre-push": [ + "composer test", + "composer check-style" + ] + } + }, + "autoload": { + "psr-4": { + "Overtrue\\Pinyin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com", + "homepage": "http://github.com/overtrue" + } + ], + "description": "Chinese to pinyin translator.", + "homepage": "https://github.com/overtrue/pinyin", + "keywords": [ + "Chinese", + "Pinyin", + "cn2pinyin" + ], + "support": { + "issues": "https://github.com/overtrue/pinyin/issues", + "source": "https://github.com/overtrue/pinyin/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2022-07-24T11:43:54+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.28.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "6e81cf39bbd93ebc3a4e8150444c41e8aa9b769a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/6e81cf39bbd93ebc3a4e8150444c41e8aa9b769a", + "reference": "6e81cf39bbd93ebc3a4e8150444c41e8aa9b769a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.2.4", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.28.0" + }, + "time": "2023-02-25T12:24:49+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.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": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "4d1bf7886e2af0a194332486273debcd6662cfc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/4d1bf7886e2af0a194332486273debcd6662cfc9", + "reference": "4d1bf7886e2af0a194332486273debcd6662cfc9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.2", + "symfony/dom-crawler": "^5.4|^6.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "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": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v6.0.19" + }, + "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-01T08:36:10+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "f1d00bddb83a4cb2138564b2150001cb6ce272b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f1d00bddb83a4cb2138564b2150001cb6ce272b1", + "reference": "f1d00bddb83a4cb2138564b2150001cb6ce272b1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v6.0.19" + }, + "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-01T08:36:10+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + }, + "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": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "622578ff158318b1b49d95068bd6b66c713601e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/622578ff158318b1b49d95068bd6b66c713601e9", + "reference": "622578ff158318b1b49d95068bd6b66c713601e9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^5.4|^6.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "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": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v6.0.19" + }, + "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-20T17:44:14+00:00" + }, + { + "name": "symfony/http-client", + "version": "v6.0.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "541c04560da1875f62c963c3aab6ea12a7314e11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/541c04560da1875f62c963c3aab6ea12a7314e11", + "reference": "541c04560da1875f62c963c3aab6ea12a7314e11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.2", + "psr/log": "^1|^2|^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", + "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", + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.0.20" + }, + "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:41:07+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "4184b9b63af1edaf35b6a7974c6f1f9f33294129" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4184b9b63af1edaf35b6a7974c6f1f9f33294129", + "reference": "4184b9b63af1edaf35b6a7974c6f1f9f33294129", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.2" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "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.0.2" + }, + "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": "2022-04-12T16:11:42+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "d7052547a0070cbeadd474e172b527a00d657301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/d7052547a0070cbeadd474e172b527a00d657301", + "reference": "d7052547a0070cbeadd474e172b527a00d657301", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.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": "<5.4", + "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6" + }, + "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/v6.0.19" + }, + "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-11T11:50:03+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/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.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "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 for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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 for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "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 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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 writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "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": "2022-05-30T19:17:29+00:00" + }, + { + "name": "topthink/framework", + "version": "v6.1.2", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "67235be5b919aaaf1de5aed9839f65d8e766aca3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/67235be5b919aaaf1de5aed9839f65d8e766aca3", + "reference": "67235be5b919aaaf1de5aed9839f65d8e766aca3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=7.2.5", + "psr/container": "~1.0", + "psr/http-message": "^1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1.1", + "topthink/think-orm": "^2.0|^3.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^2.1.0", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP Framework.", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "support": { + "issues": "https://github.com/top-think/framework/issues", + "source": "https://github.com/top-think/framework/tree/v6.1.2" + }, + "time": "2023-02-08T02:24:01+00:00" + }, + { + "name": "topthink/think-captcha", + "version": "v3.0.9", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "b1ef360670578214edeebcf824aaf6ab7ee0528b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/b1ef360670578214edeebcf824aaf6ab7ee0528b", + "reference": "b1ef360670578214edeebcf824aaf6ab7ee0528b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/framework": "^6.0|^8.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\captcha\\CaptchaService" + ], + "config": { + "captcha": "src/config.php" + } + } + }, + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\captcha\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "captcha package for thinkphp", + "support": { + "issues": "https://github.com/top-think/think-captcha/issues", + "source": "https://github.com/top-think/think-captcha/tree/v3.0.9" + }, + "time": "2023-04-27T07:18:40+00:00" + }, + { + "name": "topthink/think-filesystem", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-filesystem.git", + "reference": "29f19f140a9267c717fecd7ccb22c84c2d72382e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-filesystem/zipball/29f19f140a9267c717fecd7ccb22c84c2d72382e", + "reference": "29f19f140a9267c717fecd7ccb22c84c2d72382e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^1.1.4", + "league/flysystem-cached-adapter": "^1.0", + "php": ">=7.2.5", + "topthink/framework": "^6.1|^8.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6.1 Filesystem Package", + "support": { + "issues": "https://github.com/top-think/think-filesystem/issues", + "source": "https://github.com/top-think/think-filesystem/tree/v1.0.3" + }, + "time": "2023-02-08T01:25:15+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.6", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/769acbe50a4274327162f9c68ec2e89a38eb2aff", + "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.6" + }, + "time": "2021-12-15T04:27:55+00:00" + }, + { + "name": "topthink/think-image", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-image.git", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-image/zipball/8586cf47f117481c6d415b20f7dedf62e79d5512", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "topthink/framework": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Image Package", + "support": { + "issues": "https://github.com/top-think/think-image/issues", + "source": "https://github.com/top-think/think-image/tree/master" + }, + "time": "2016-09-29T06:05:43+00:00" + }, + { + "name": "topthink/think-multi-app", + "version": "v1.0.16", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-multi-app.git", + "reference": "07b9183855150455e1f76f8cbe9d77d6d1bc399f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/07b9183855150455e1f76f8cbe9d77d6d1bc399f", + "reference": "07b9183855150455e1f76f8cbe9d77d6d1bc399f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0|^8.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\app\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp6 multi app support", + "support": { + "issues": "https://github.com/top-think/think-multi-app/issues", + "source": "https://github.com/top-think/think-multi-app/tree/v1.0.16" + }, + "time": "2023-02-07T08:40:09+00:00" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.61", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/10528ebf4a5106b19c3bac9c6deae7a67ff49de6", + "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-pdo": "*", + "php": ">=7.1.0", + "psr/log": "^1.0|^2.0", + "psr/simple-cache": "^1.0|^2.0", + "topthink/think-helper": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9.5" + }, + "type": "library", + "autoload": { + "files": [ + "stubs/load_stubs.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v2.0.61" + }, + "time": "2023-04-20T14:27:51+00:00" + } + ], + "packages-dev": [ + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "1069c7a3fca74578022fab6f81643248d02f8e63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1069c7a3fca74578022fab6f81643248d02f8e63", + "reference": "1069c7a3fca74578022fab6f81643248d02f8e63", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.43|^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v4.4.47" + }, + "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": "2022-10-03T15:15:11+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.4.3" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..cd588b5 --- /dev/null +++ b/config/app.php @@ -0,0 +1,33 @@ + '', + // 应用地址 + 'app_host' => env('app.host', ''), + // 应用的命名空间 + 'app_namespace' => '', + // 是否启用路由 + 'with_route' => true, + // 默认应用 + 'default_app' => 'index', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + + // 应用映射(自动多应用模式有效) + 'app_map' => [], + // 域名绑定(自动多应用模式有效) + 'domain_bind' => [], + // 禁止URL访问的应用列表(自动多应用模式有效) + 'deny_app_list' => ['common'], + + // 异常页面的模板文件 + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => true, +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..a8d69d2 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,29 @@ + env('cache.driver', 'file'), + + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + // 更多的缓存连接 + ], +]; diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 0000000..29e015e --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,46 @@ + 4, + // 验证码字符集合 + // 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', + 'codeSet' => '2345678', + // 验证码过期时间 + 'expire' => 1800, + // 是否使用中文验证码 + 'useZh' => false, + // 是否使用算术验证码 + 'math' => false, + // 是否使用背景图 + 'useImgBg' => false, + //验证码字符大小 + 'fontSize' => 25, + // 是否使用混淆曲线 + 'useCurve' => true, + //是否添加杂点 + 'useNoise' => true, + // 验证码字体 不设置则随机 + 'fontttf' => '', + //背景颜色 + 'bg' => [243, 251, 254], + // 验证码图片高度 + 'imageH' => 0, + // 验证码图片宽度 + 'imageW' => 0, + + // 添加额外的验证码设置 + 'verify' => [ + 'length' => 5, + 'useImgBg' => false, + 'useNoise' => false, + 'useCurve' => false, + // 'imageH' => 40, + // 'imageW' => 100, + 'fontSize' => 15, + 'bg' => [255, 255, 255], + ], +]; diff --git a/config/chunkUpload.php b/config/chunkUpload.php new file mode 100644 index 0000000..6a888bb --- /dev/null +++ b/config/chunkUpload.php @@ -0,0 +1,9 @@ + '' +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..d0092b2 --- /dev/null +++ b/config/console.php @@ -0,0 +1,10 @@ + [ + 'timer' => 'app\command\Timer' + ], +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..d3b3aab --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,20 @@ + 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..a93e3c2 --- /dev/null +++ b/config/database.php @@ -0,0 +1,63 @@ + env('database.driver', 'mysql'), + + // 自定义时间查询规则 + 'time_query_rule' => [], + + // 自动写入时间戳字段 + // true为自动识别类型 false关闭 + // 字符串则明确指定时间字段类型 支持 int timestamp datetime date + 'auto_timestamp' => true, + + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + + // 时间字段配置 配置格式:create_time,update_time + 'datetime_field' => '', + + // 数据库连接配置信息 + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => env('database.type', 'mysql'), + // 服务器地址 + 'hostname' => env('database.hostname', '47.242.159.172'), + // 数据库名 + 'database' => env('database.database', 'php_mb_V1.1'), + // 用户名 + 'username' => env('database.username', 'php_mb_V1.1'), + // 密码 + 'password' => env('database.password', 'jh87R4ipFt24Hct8'), + // 端口 + 'hostport' => env('database.hostpost', '3306'), + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => env('database.charset', 'utf8'), + // 数据库表前缀 + 'prefix' => env('database.prefix', ''), + + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 是否需要断线重连 + 'break_reconnect' => false, + // 监听SQL + 'trigger_sql' => env('app_debug', true), + // 开启字段缓存 + 'fields_cache' => false, + ], + + // 更多的数据库配置信息 + ], +]; diff --git a/config/filesystem.php b/config/filesystem.php new file mode 100644 index 0000000..ed45ff3 --- /dev/null +++ b/config/filesystem.php @@ -0,0 +1,37 @@ + env('filesystem.driver', 'uploads'), + // 磁盘列表 + 'disks' => [ + // 'local' => [ + // 'type' => 'local', + // 'root' => app()->getRuntimePath() . 'storage', + // ], + 'uploads' => [ + 'type' => 'local', + 'root' => app()->getRootPath() . 'public/uploads', + 'url' => '/uploads/', + 'visibility' => 'public', + ], + + 'approve' => [ + 'type' => 'local', + 'root' => app()->getRootPath() . 'storage/approve', + // 'url' => '/uploads/', + 'visibility' => 'private', + ], + // 'public' => [ + // // 磁盘类型 + // 'type' => 'local', + // // 磁盘路径 + // 'root' => app()->getRootPath() . 'public/storage', + // // 磁盘路径对应的外部URL路径 + // 'url' => '/storage/', + // // 可见性 + // 'visibility' => 'public', + // ], + // 更多的磁盘配置信息 + ], +]; diff --git a/config/lang.php b/config/lang.php new file mode 100644 index 0000000..59f320f --- /dev/null +++ b/config/lang.php @@ -0,0 +1,27 @@ + env('lang.default_lang', 'zh-cn'), + // 允许的语言列表 + 'allow_lang_list' => [], + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // 是否使用Cookie记录 + 'use_cookie' => true, + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言header变量 + 'header_var' => 'think-lang', + // 扩展语言包 + 'extend_list' => [], + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..37fb65d --- /dev/null +++ b/config/log.php @@ -0,0 +1,124 @@ + 'File', + // 日志保存目录 + 'path' => app()->getRuntimePath() . 'push_log' . DIRECTORY_SEPARATOR . $vendor, + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => true, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ]; +}; +$generateWechat = function (string $type) { + return [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => app()->getRuntimePath() . 'wechat' . DIRECTORY_SEPARATOR . $type, + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => true, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ]; +}; +return [ + // 默认日志记录通道 + 'default' => env('log.channel', 'file'), + // 日志记录级别 + 'level' => [], + // 日志类型记录的通道 ['error'=>'email',...] + 'type_channel' => [], + // 关闭全局日志写入 + 'close' => false, + // 全局日志处理 支持闭包 + 'processor' => null, + + // 日志通道列表 + 'channels' => [ + // 飞比云 + 'pushLog/FBeeCloud' => $generatePushLog('FBeeCloud'), + // 睡小宝 + 'pushLog/Sleepthing' => $generatePushLog('Sleepthing'), + // 微信公众号 + 'wechat/gzh' => $generateWechat('gzh'), + // 微信小程序 + 'wechat/xcx' => $generateWechat('xcx'), + // 命令行 + 'cmd' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => app()->getRuntimePath() . 'cmd', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + 'file' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => '', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + // 其它日志通道配置 + ], + +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..7e1972f --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,8 @@ + [], + // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行 + 'priority' => [], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..aac1531 --- /dev/null +++ b/config/route.php @@ -0,0 +1,50 @@ + '/', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => true, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache_key' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 中间件 + 'middleware' => [ + // 接口鉴权 + // \app\middleware\Auth::class + ], +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..c1ef6e1 --- /dev/null +++ b/config/session.php @@ -0,0 +1,19 @@ + 'PHPSESSID', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // 驱动方式 支持file cache + 'type' => 'file', + // 存储连接标识 当type使用cache的时候有效 + 'store' => null, + // 过期时间 + 'expire' => 1440, + // 前缀 + 'prefix' => '', +]; diff --git a/config/wechat.php b/config/wechat.php new file mode 100644 index 0000000..7c798be --- /dev/null +++ b/config/wechat.php @@ -0,0 +1,39 @@ + '', + // 中控层地址 + 'server_base' => 'http://clique.dszjjt.com:7001/', + // 公众号配置 + 'gzh' => [ + // 缓存key + 'cache_key' => 'wechat.gzh', + // 日志通道 + 'log_channel' => 'wechat/gzh', + // 公众号模板推送 + 'template' => [ + // {{first.DATA}} + // 申请人:{{keyword1.DATA}} + // 申请时间:{{keyword2.DATA}} + // 调课时间:{{keyword3.DATA}} + // {{remark.DATA}} + '申请审核通知' => 'iLuqzUrQM8-v_7ilxageUZoi9v-SQWipkTYVZXB1GeI' + ], + 'appid' => 'wx99caf571c9fb1cc5', + 'secret' => '61100d9c4fff5acf97517f0c8799e2e2', + ], + // 小程序配置 + 'xcx' => [ + // 缓存key + 'cache_key' => 'wechat.xcx', + // 日志通道 + 'log_channel' => 'wechat/xcx', + + 'appid' => 'wx2495492e15854b02', + 'secret' => '5b4fcb27324398083fba4a3c39e8a557', + ], +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..50934e0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "api", + "lockfileVersion": 2, + "requires": true, + "packages": {} +} diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..cbc7868 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,8 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + diff --git a/public/admin.php b/public/admin.php new file mode 100644 index 0000000..0719a59 --- /dev/null +++ b/public/admin.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +// [ 应用入口文件 ] +namespace think; + +require __DIR__ . '/../vendor/autoload.php'; + +try { + // 执行HTTP应用并响应 + $http = (new App())->http; + + $response = $http->run(); + + $response->send(); +} catch (\InvalidArgumentException $th) { + // 方便浏览器调试 + if (isset($response)) { + $data = $response->getData(); + if (is_array($data)) { + echo json_encode($data); + } + } else { + throw $th; + } +} finally { + $http->end($response); +} diff --git a/public/api.php b/public/api.php new file mode 100644 index 0000000..0719a59 --- /dev/null +++ b/public/api.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +// [ 应用入口文件 ] +namespace think; + +require __DIR__ . '/../vendor/autoload.php'; + +try { + // 执行HTTP应用并响应 + $http = (new App())->http; + + $response = $http->run(); + + $response->send(); +} catch (\InvalidArgumentException $th) { + // 方便浏览器调试 + if (isset($response)) { + $data = $response->getData(); + if (is_array($data)) { + echo json_encode($data); + } + } else { + throw $th; + } +} finally { + $http->end($response); +} diff --git a/public/excel/tdk/网站tdk导入模板 - 副本 (2).xlsx b/public/excel/tdk/网站tdk导入模板 - 副本 (2).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4074c738757cecfd00a3bffb7cfb1f0fd10b2651 GIT binary patch literal 9952 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 literal 0 HcmV?d00001 diff --git a/public/excel/tdk/网站tdk导入模板 - 副本.xlsx b/public/excel/tdk/网站tdk导入模板 - 副本.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c8c902a0067a7facae04f108fb4017c03e96bc7d GIT binary patch literal 10209 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 literal 0 HcmV?d00001 diff --git a/public/excel/tdk/网站tdk导入模板.xlsx b/public/excel/tdk/网站tdk导入模板.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d6f4dec5862ad478a7a9376b8d582a96f6d8a919 GIT binary patch literal 13522 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 +// +---------------------------------------------------------------------- +// $Id$ + +if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { + return false; +} else { + $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; + + require __DIR__ . "/index.php"; +} diff --git a/public/sql/php_mb_v1.1.sql b/public/sql/php_mb_v1.1.sql new file mode 100644 index 0000000..e69de29 diff --git a/public/static/.gitignore b/public/static/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/think b/think new file mode 100644 index 0000000..2429d22 --- /dev/null +++ b/think @@ -0,0 +1,10 @@ +#!/usr/bin/env php +console->run(); \ No newline at end of file