[
  {
    "path": ".gitignore",
    "content": "/.idea\n/.vscode\n/vendor\n*.log\nthinkphp\n.env\n.DS_Store\ncomposer.lock\n/public/uploads\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\n\nlanguage: php\n\nbranches:\n  only:\n    - stable\n\ncache:\n  directories:\n    - $HOME/.composer/cache\n\nbefore_install:\n  - composer self-update\n\ninstall:\n  - composer install --no-dev --no-interaction --ignore-platform-reqs\n  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .\n  - composer require --update-no-dev --no-interaction \"topthink/think-image:^1.0\"\n  - composer require --update-no-dev --no-interaction \"topthink/think-migration:^1.0\"\n  - composer require --update-no-dev --no-interaction \"topthink/think-captcha:^1.0\"\n  - composer require --update-no-dev --no-interaction \"topthink/think-mongo:^1.0\"\n  - composer require --update-no-dev --no-interaction \"topthink/think-worker:^1.0\"\n  - composer require --update-no-dev --no-interaction \"topthink/think-helper:^1.0\"\n  - composer require --update-no-dev --no-interaction \"topthink/think-queue:^1.0\"\n  - composer require --update-no-dev --no-interaction \"topthink/think-angular:^1.0\"\n  - composer require --dev --update-no-dev --no-interaction \"topthink/think-testing:^1.0\"\n  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .\n\nscript:\n  - php think unit\n\ndeploy:\n  provider: releases\n  api_key:\n    secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=\n  file:\n    - ThinkPHP_Core.zip\n    - ThinkPHP_Full.zip\n  skip_cleanup: true\n  on:\n    tags: true\n"
  },
  {
    "path": "LICENSE",
    "content": "ThinkPHP遵循Apache2开源协议发布，并提供免费使用。\n版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)\nAll rights reserved。\nThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。\n\nApache Licence是著名的非盈利开源组织Apache采用的协议。\n该协议和BSD类似，鼓励代码共享和尊重原作者的著作权，\n允许代码修改，再作为开源或商业软件发布。需要满足\n的条件： \n1． 需要给代码的用户一份Apache Licence ；\n2． 如果你修改了代码，需要在被修改的文件中说明；\n3． 在延伸的代码中（修改和有源代码衍生的代码中）需要\n带有原来代码中的协议，商标，专利声明和其他原来作者规\n定需要包含的说明；\n4． 如果再发布的产品中包含一个Notice文件，则在Notice文\n件中需要带有本协议内容。你可以在Notice中增加自己的\n许可，但不可以表现为对Apache Licence构成更改。 \n具体的协议参考：http://www.apache.org/licenses/LICENSE-2.0\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\nFOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\nCOPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\nANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE."
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\n  <a href=\"http://doc.cms.7yue.pro/\">\n  <img src=\"http://doc.cms.7yue.pro/left-logo.png\" width=\"250\"/></a>\n  <br>\n  Lin-CMS-TP5\n</h1>\n\n<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/PHP-%3E%3D7.1-blue.svg\" alt=\"php version\" data-canonical-src=\"https://img.shields.io/badge/PHP-%3E%3D7.1-blue.svg\" style=\"max-width:100%;\"></a>\n  <a href=\"https://www.kancloud.cn/manual/thinkphp5_1/353946\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/ThinkPHP-5.1.*-green.svg\" alt=\"ThinkPHP version\" data-canonical-src=\"https://img.shields.io/badge/ThinkPHP-5.1.*-green.svg\" style=\"max-width:100%;\"></a>\n  <img src=\"https://img.shields.io/badge/license-license--2.0-lightgrey.svg\" alt=\"LISENCE\" data-canonical-src=\"https://img.shields.io/badge/license-license--2.0-lightgrey.svg\" style=\"max-width:100%;\"></a>\n</p>\n\n# 简介\n\n## 预防针\n\n* 本项目非官方团队出品，仅出于学习、研究目的丰富下官方项目的语言支持，目前已被收录至官方团队仓库，[点击查看](https://github.com/TaleLin)\n* 本项目采取后跟进官方团队功能的形式，即官方团队出什么功能，这边就跟进开发什么功能，开发者不必担心前端适配问题。\n* 在上一点的基础上，我们会尝试加入一些自己的想法并实现。\n* 局限于本人水平，有些地方还需重构，已经纳入了计划中，当然也会有我没考虑到的，希望有更多人参与进来一起完善，毕竟PHP作为世界上最好的语言不能缺席。\n\n## 专栏教程\n\n* [《Lin CMS PHP&Vue教程》](https://course.7yue.pro/lin/lin-cms-php/)专栏教程连载更新中，通过实战开源前后端分离CMS——Lin CMS全家桶（lin-cms-vue & lin-cms-tp5）为一个前端应用实现内容管理系统。一套教程入门上手vue、ThinkPHP两大框架，自用、工作、私单一次打通。\n\n* 读者反馈：[《Lin CMS PHP&Vue教程》读者反馈贴](https://github.com/ChenJinchuang/lin-cms-tp5/issues/47)\n\n## 线上文档地址(完善中)\n\n[http://chenjinchuang.gitee.io/lin-cms-book/](http://chenjinchuang.gitee.io/lin-cms-book/)\n\n## 线上 Demo\n\n可直接参考官方团队的线上Demo：[http://face.cms.7yue.pro/](http://face.cms.7yue.pro/)，用户名:super，密码：123456\n\n## 什么是 Lin CMS？\n\n> Lin-CMS 是林间有风团队经过大量项目实践所提炼出的一套**内容管理系统框架**。Lin-CMS 可以有效的帮助开发者提高 CMS 的开发效率。\n\n本项目是基于ThinkPHP 5.1的 Lin CMS 后端实现。\n\n官方团队产品了解请访问[TaleLin](https://github.com/TaleLin)\n\n## Lin CMS 的特点\n\nLin CMS 的构筑思想是有其自身特点的。下面我们阐述一些 Lin 的主要特点。\n\n**Lin CMS 是一个前后端分离的 CMS 解决方案**\n\n这意味着，Lin 既提供后台的支撑，也有一套对应的前端系统，当然双端分离的好处不仅仅在于此，我们会在后续提供NodeJS和PHP版本的 Lin。如果你心仪 Lin，却又因为技术栈的原因无法即可使用，没关系，我们会在后续提供更多的语言版本。为什么 Lin 要选择前后端分离的单页面架构呢？\n\n首先，传统的网站开发更多的是采用服务端渲染的方式，需用使用一种模板语言在服务端完成页面渲染：比如 JinJa2、Jade 等。 服务端渲染的好处在于可以比较好的支持 SEO，但作为内部使用的 CMS 管理系统，SEO 并不重要。\n\n但一个不可忽视的事实是，服务器渲染的页面到底是由前端开发者来完成，还是由服务器开发者来完成？其实都不太合适。现在已经没有多少前端开发者是了解这些服务端模板语言的，而服务器开发者本身是不太擅长开发页面的。那还是分开吧，前端用最熟悉的 Vue 写 JS 和 CSS，而服务器只关注自己的 API 即可。\n\n其次，单页面应用程序的体验本身就要好于传统网站。\n\n更多关于Lin CMS的介绍请访问[Lin CMS线上文档](http://doc.cms.7yue.pro/)\n\n**框架本身已内置了 CMS 常用的功能**\n\nLin 已经内置了 CMS 中最为常见的需求：用户管理、权限管理、日志系统等。开发者只需要集中精力开发自己的 CMS 业务即可\n\n## Lin CMS TP5 的特点\n\n在当前项目的版本`(0.0.1)`中，特点更多来自于`ThinkPHP 5.1`框架本身带来的特点。通过充分利用框架的特性，实现高效的后端使用、开发，也就是说，只要你熟悉`ThinkPHP`框架，那么对于理解使用和二次开发本项目是没有难度的，即便对于框架的某些功能存在疑问也完全可以通过ThinkPHP官方的开发手册找到答案。当然我们更欢迎你通过[Issues](https://github.com/ChenJinchuang/lin-cms-tp5/issues)来向我们提问:)\n\n在下一个版本中`(>0.0.1)`,我们会在框架的基础上融入一些自己的东西来增强或者优化框架的使用、开发体验。\n\n## 所需基础\n\n由于 Lin 采用的是前后端分离的架构，所以你至少需要熟悉 PHP 和 Vue。\n\nLin 的服务端框架是基于 ThinkPHP5.1的，所以如果你比较熟悉ThinkPHP的开发模式，那将可以更好的使用本项目。但如果你并不熟悉ThinkPHP，我们认为也没有太大的关系，因为框架本身已经提供了一套完整的开发机制，你只需要在框架下用 PHP 来编写自己的业务代码即可。照葫芦画瓢应该就是这种感觉。\n\n但前端不同，前端还是需要开发者比较熟悉 Vue 的。但我想以 Vue 在国内的普及程度，绝大多数的开发者是没有问题的。这也正是我们选择 Vue 作为前端框架的原因。如果你喜欢 React Or Angular，那么加入我们，为 Lin 开发一个对应的版本吧。\n\n# 快速开始\n\n## Server 端必备环境\n\n* 安装MySQL（version： 5.7+）\n\n* 安装PHP环境(version： 7.1+)\n\n## 获取工程项目\n\n```bash\ngit clone https://github.com/ChenJinchuang/lin-cms-tp5.git\n```\n\n> 执行完毕后会生成lin-cms-tp5目录\n\n## 安装依赖包\n\n执行命令前请确保你已经安装了composer工具\n\n```bash\n# 进入项目根目录\ncd lin-cms-tp5\n# 先执行以下命令，全局替换composer源，解决墙的问题\ncomposer config -g repo.packagist composer https://mirrors.aliyun.com/composer/\n# 接着执行以下命令安装依赖包\ncomposer install\n```\n\n## 数据库配置\n\nLin 需要你自己在 MySQL 中新建一个数据库，名字由你自己决定。例如，新建一个名为` lin-cms `的数据库。接着，我们需要在工程中进行一项简单的配置。使用编辑器打开 Lin 工程根目录下``/config/database.php``，找到如下配置项：\n\n```php\n// 服务器地址\n  'hostname'        => '',\n// 数据库名\n  'database'        => 'lin-cms',\n// 用户名\n  'username'        => 'root',\n// 密码\n  'password'        => '',\n  \n  //省略后面一堆的配置项\n```\n\n**请务必根据自己的实际情况修改此配置项**\n\n## 导入数据\n\n接下来使用你本机上任意一款数据库可视化工具，为已经创建好的`lin-cms`数据库运行lin-cms-tp5根目录下的`schema.sql`文件，这个SQL脚本文件将为为你生成一些基础的数据库表和数据。\n\n## 运行\n\n如果前面的过程一切顺利，项目所需的准备工作就已经全部完成，这时候你就可以试着让工程运行起来了。在工程的根目录打开命令行，输入：\n\n```bash\nphp think run --port 5000 //启动thinkPHP内置的Web服务器\n```\n\n启动成功后会看到如下提示：\n\n```php\nThinkPHP Development server is started On <http://127.0.0.1:5000/>\nYou can exit with `CTRL-C`\n```\n\n打开浏览器，访问``http://127.0.0.1:5000``，你会看到一个欢迎界面，至此，Lin-cms-tp5部署完毕，可搭配[lin-cms-vue](https://github.com/TaleLin/lin-cms-vue)使用了。\n\n## 更新日志\n\n[查看日志](http://chenjinchuang.gitee.io/lin-cms-book/log/)\n\n## 常见问题\n\n[查看常见问题](http://chenjinchuang.gitee.io/lin-cms-book/qa/)\n\n## 讨论交流\n\n### QQ 交流群\n\nQQ 群号：643205479\n\n<img class=\"QR-img\" width=\"258\" height=\"300\" src=\"http://imglf3.nosdn0.126.net/img/Qk5LWkJVWkF3Nmdyc2xGcUtScEJLOVV1clErY1dJa0FsQ3E1aDZQWlZHZ2dCbSt4WXA1V3dRPT0.jpg?imageView&thumbnail=1680x0&quality=96&stripmeta=0&type=jpg\">\n\n### 微信公众号\n\n微信搜索：林间有风\n\n<img class=\"QR-img\" src=\"http://imglf6.nosdn0.126.net/img/YUdIR2E3ME5weEdlNThuRmI4TFh3UWhiNmladWVoaTlXUXpicEFPa1F6czFNYkdmcWRIbGRRPT0.jpg?imageView&thumbnail=500x0&quality=96&stripmeta=0&type=jpg\">\n"
  },
  {
    "path": "application/.htaccess",
    "content": "deny from all"
  },
  {
    "path": "application/api/behavior/Logger.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/5/16\n * Time: 17:19\n */\n\nnamespace app\\api\\behavior;\n\n\nuse app\\api\\model\\admin\\LinLog;\nuse app\\api\\service\\token\\LoginToken;\nuse app\\lib\\exception\\OperationException;\nuse think\\facade\\Request;\nuse think\\facade\\Response;\n\nclass Logger\n{\n    /**\n     * @param $params\n     * @throws OperationException\n     */\n    public function run($params)\n    {\n\n        // 行为逻辑\n        if (empty($params)) {\n            throw new OperationException([\n                'msg' => '日志信息不能为空'\n            ]);\n        }\n\n        if (is_array($params)) {\n            list('uid' => $uid, 'username' => $username, 'msg' => $message) = $params;\n        } else {\n            $tokenService = LoginToken::getInstance();\n            $uid = $tokenService->getCurrentUid();\n            $username = $tokenService->getCurrentUserName();\n            $message = $params;\n        }\n\n        $data = [\n            'message' => $username . $message,\n            'user_id' => $uid,\n            'username' => $username,\n            'status_code' => Response::getCode(),\n            'method' => Request::method(),\n            'path' => '/' . Request::path(),\n            'permission' => null\n        ];\n\n        LinLog::create($data);\n\n    }\n}"
  },
  {
    "path": "application/api/controller/cms/Admin.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/16\n * Time: 11:21\n */\n\nnamespace app\\api\\controller\\cms;\n\nuse app\\api\\service\\admin\\Admin as AdminService;\nuse app\\lib\\exception\\NotFoundException;\nuse app\\lib\\exception\\OperationException;\nuse app\\lib\\exception\\token\\ForbiddenException;\nuse LinCmsTp5\\exception\\ParameterException;\nuse PDOStatement;\nuse ReflectionException;\nuse think\\db\\exception\\DataNotFoundException;\nuse think\\db\\exception\\ModelNotFoundException;\nuse think\\db\\Query;\nuse think\\exception\\DbException;\nuse think\\facade\\Hook;\nuse think\\model\\Collection;\nuse think\\Request;\nuse think\\response\\Json;\n\nclass Admin\n{\n    /**\n     * @adminRequired\n     * @permission('查询所有可分配的权限','管理员','hidden')\n     * @return array\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws ReflectionException\n     */\n    public function getAllPermissions()\n    {\n        return AdminService::getAllPermissions();\n    }\n\n    /**\n     * @adminRequired\n     * @permission('查询所有用户','管理员','hidden')\n     * @param Request $request\n     * @param('page','分页数','integer')\n     * @param('count','分页值','integer')\n     * @param('group_id','分组id','integer')\n     * @return array\n     * @throws ParameterException\n     */\n    public function getAdminUsers(Request $request)\n    {\n        $page = $request->get('page/d', 0);\n        $count = $request->get('count/d', 10);\n        $groupId = $request->get('group_id/d');\n\n        return AdminService::getUsers($page, $count, $groupId);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('修改用户密码','管理员','hidden')\n     * @validate('ResetPasswordValidator')\n     * @param Request $request\n     * @param $id\n     * @return Json\n     * @throws NotFoundException\n     */\n    public function changeUserPassword(Request $request, $id)\n    {\n        $newPassword = $request->put('new_password');\n        AdminService::changeUserPassword($id, $newPassword);\n        Hook::listen('logger', \"修改了用户ID为{$id}的密码\");\n\n        return writeJson(200, null, '修改成功', 4);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('删除用户','管理员','hidden')\n     * @param int $id\n     * @param('id','用户id','require|integer')\n     * @return Json\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public function deleteUser(int $id)\n    {\n        AdminService::deleteUser($id);\n        Hook::listen('logger', \"删除了用户ID为：{$id}的用户\");\n        return writeJson(201, $id, '删除用户成功', 5);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('管理员更新用户信息','管理员','hidden')\n     * @param Request $request\n     * @param('id','用户id','require|integer')\n     * @param('group_ids','分组id','require|array|min:1')\n     * @return Json\n     * @throws NotFoundException\n     * @throws OperationException\n     * @throws ForbiddenException\n     * @throws DataNotFoundException\n     * @throws ModelNotFoundException\n     * @throws DbException\n     */\n    public function updateUser(Request $request, $id)\n    {\n        $groupIds = $request->put('group_ids');\n        AdminService::updateUserInfo($id, $groupIds);\n\n        Hook::listen('logger', \"更新了用户：{$id}的所属分组\");\n        return writeJson(201, $id, '更新用户成功', 6);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('查询所有分组','管理员','hidden')\n     * @return array|PDOStatement|string|\\think\\Collection|Collection\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     */\n    public function getGroupAll()\n    {\n        return AdminService::getAllGroups();\n    }\n\n    /**\n     * @adminRequired\n     * @permission('查询一个权限组及其权限','管理员','hidden')\n     * @param int $id\n     * @param('id','分组id','require|integer')\n     * @return Query\n     * @throws DbException\n     * @throws NotFoundException\n     */\n    public function getGroup(int $id)\n    {\n        return AdminService::getGroup($id);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('新建一个权限组','管理员','hidden')\n     * @param Request $request\n     * @param('name','分组名字','require')\n     * @param('permission_ids','权限id','require|array|min:1')\n     * @return Json\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public function createGroup(Request $request)\n    {\n        $name = $request->post('name');\n        $info = $request->post('info');\n        $permissionIds = $request->post('permission_ids');\n\n        $groupId = AdminService::createGroup($name, $info, $permissionIds);\n\n        Hook::listen('logger', \"创建了分组：{$name}\");\n        return writeJson(201, $groupId, '新增分组成功', 15);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('更新一个权限组','管理员','hidden')\n     * @param Request $request\n     * @param int $id\n     * @param('id','分组id','require|integer')\n     * @param('info','分组信息','require')\n     * @param('name','分组名字','require')\n     * @return Json\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     */\n    public function updateGroup(Request $request, int $id)\n    {\n        $name = $request->put('name');\n        $info = $request->put('info');\n\n        $res = AdminService::updateGroup($id, $name, $info);\n\n        Hook::listen('logger', \"更新了id为{$id}的分组\");\n        return writeJson(200, $res, '更新分组信息成功', 7);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('更新一个权限组','管理员','hidden')\n     * @param int $id\n     * @param('id','分组id','require|integer')\n     * @return Json\n     * @throws ForbiddenException\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public function deleteGroup(int $id)\n    {\n        AdminService::deleteGroup($id);\n\n        Hook::listen('logger', \"删除了id为{$id}的分组\");\n        return writeJson(200, null, '删除分组成功', 8);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('分配多个权限','管理员','hidden')\n     * @param Request $request\n     * @param('group_id','分组id','require|integer')\n     * @param('permission_ids','权限id','require|array|min:1')\n     * @return Json\n     * @throws DbException\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public function dispatchPermissions(Request $request)\n    {\n        $groupId = $request->post('group_id');\n        $permissionIds = $request->post('permission_ids');\n\n        AdminService::dispatchPermissions($groupId, $permissionIds);\n\n        Hook::listen('logger', \"修改了分组ID为{$groupId}的权限\");\n        return writeJson(200, null, '分配权限成功', 9);\n    }\n\n    /**\n     * @adminRequired\n     * @permission('删除多个权限','管理员','hidden')\n     * @param Request $request\n     * @param('group_id','分组id','require|integer')\n     * @param('permission_ids','权限id','require|array|min:1')\n     * @return Json\n     * @throws DbException\n     * @throws NotFoundException\n     */\n    public function removePermissions(Request $request)\n    {\n        $groupId = $request->post('group_id');\n        $permissionIds = $request->post('permission_ids');\n\n        $deleted = AdminService::removePermissions($groupId, $permissionIds);\n\n        Hook::listen('logger', \"修改了分组ID为{$groupId}的权限\");\n        return writeJson(200, $deleted, '删除权限成功', 10);\n    }\n}"
  },
  {
    "path": "application/api/controller/cms/File.php",
    "content": "<?php\n/*\n* Created by DevilKing\n* Date: 2019- 06-08\n*Time: 16:26\n*/\n\nnamespace app\\api\\controller\\cms;\n\nuse think\\facade\\Request;\nuse app\\lib\\file\\LocalUploader;\nuse app\\lib\\exception\\file\\FileException;\n\n/**\n * Class File\n * @package app\\api\\controller\\cms\n */\nclass File\n{\n    /**\n     * @return mixed\n     * @throws FileException\n     * @throws \\LinCmsTp\\exception\\FileException\n     */\n    public function postFile()\n    {\n        try {\n            $request = Request::file();\n        } catch (\\Exception $e) {\n            throw new FileException([\n                'msg' => '字段中含有非法字符',\n            ]);\n        }\n        $file = (new LocalUploader($request))->upload();\n        return $file;\n    }\n}\n"
  },
  {
    "path": "application/api/controller/cms/Log.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/26\n * Time: 22:20\n */\n\nnamespace app\\api\\controller\\cms;\n\nuse app\\api\\service\\admin\\Log as LogService;\nuse LinCmsTp5\\exception\\ParameterException;\nuse think\\Request;\n\nclass Log\n{\n\n    /**\n     * @groupRequired\n     * @permission('查询所有日志','日志')\n     * @param Request $request\n     * @param('page','分页数','integer')\n     * @param('count','分页值','integer')\n     * @param('start','开始日期','date')\n     * @param('end','结束日期','date')\n     * @return array\n     * @throws ParameterException\n     */\n    public function getLogs(Request $request)\n    {\n        $start = $request->get('start');\n        $end = $request->get('end');\n        $name = $request->get('name');\n        $page = $request->get('page/d', 0);\n        $count = $request->get('count/d', 10);\n\n        return LogService::getLogs($page, $count, $start, $end, $name);\n    }\n\n    /**\n     * @groupRequired\n     * @permission('搜索日志','日志')\n     * @param Request $request\n     * @param('page','分页数','integer')\n     * @param('count','分页值','integer')\n     * @param('start','开始日期','date')\n     * @param('end','结束日期','date')\n     * @return array\n     * @throws ParameterException\n     */\n    public function getUserLogs(Request $request)\n    {\n        $start = $request->get('start');\n        $end = $request->get('end');\n        $name = $request->get('name');\n        $keyword = $request->get('keyword');\n        $page = $request->get('page/d', 0);\n        $count = $request->get('count/d', 10);\n\n        return LogService::searchLogs($page, $count, $start, $end, $name, $keyword);\n    }\n\n    /**\n     * @groupRequired\n     * @permission('查询日志记录的用户','日志')\n     * @param Request $request\n     * @param('page','分页数','integer')\n     * @param('count','分页值','integer')\n     * @return array\n     * @throws ParameterException\n     */\n    public function getUsers(Request $request)\n    {\n        $page = $request->get('page/d', 0);\n        $count = $request->get('count/d', 10);\n\n        return LogService::getUserNames($page, $count);\n    }\n}"
  },
  {
    "path": "application/api/controller/cms/User.php",
    "content": "<?php\n\nnamespace app\\api\\controller\\cms;\n\n//use app\\api\\validate\\user\\LoginForm;  # 开启注释验证器以后，本行可以去掉，这里做更替说明\n//use app\\api\\validate\\user\\RegisterForm; # 开启注释验证器以后，本行可以去掉，这里做更替说明\nuse app\\api\\service\\admin\\User as UserService;\nuse app\\api\\service\\token\\LoginToken;\nuse app\\lib\\exception\\AuthFailedException;\nuse app\\lib\\exception\\NotFoundException;\nuse app\\lib\\exception\\OperationException;\nuse app\\lib\\exception\\RepeatException;\nuse app\\lib\\exception\\token\\ForbiddenException;\nuse app\\lib\\exception\\token\\TokenException;\nuse think\\db\\exception\\DataNotFoundException;\nuse think\\db\\exception\\ModelNotFoundException;\nuse think\\exception\\DbException;\nuse think\\facade\\Hook;\nuse think\\Request;\nuse think\\response\\Json;\n\nclass User\n{\n\n    /**\n     * @var LoginToken\n     */\n    private $loginTokenService;\n\n    /**\n     * User constructor.\n     */\n    public function __construct()\n    {\n        $this->loginTokenService = LoginToken::getInstance();\n    }\n\n\n    /**\n     * @adminRequired\n     * @permission('注册','管理员','hidden')\n     * @param Request $request\n     * @validate('RegisterForm')\n     * @return Json\n     * @throws NotFoundException\n     * @throws OperationException\n     * @throws RepeatException\n     * @throws ForbiddenException\n     * @throws DataNotFoundException\n     * @throws ModelNotFoundException\n     * @throws DbException\n     */\n    public function register(Request $request)\n    {\n        $params = $request->post();\n        $user = UserService::createUser($params);\n\n        Hook::listen('logger', \"新建了用户：{$user['username']}\");\n        return writeJson(201, $user['id'], '注册用户成功');\n    }\n\n    /**\n     * @param Request $request\n     * @validate('LoginForm')\n     * @return array\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     * @throws AuthFailedException\n     */\n    public function userLogin(Request $request)\n    {\n        $username = $request->post('username');\n        $password = $request->post('password');\n        $user = UserService::verify($username, $password);\n\n        $tokenExtend = UserService::generateTokenExtend($user);\n\n        $token = $this->loginTokenService->getToken($tokenExtend);\n\n        Hook::listen('logger', array('uid' => $user->id, 'username' => $user->identifier, 'msg' => '登陆成功获取了令牌'));\n        return [\n            'access_token' => $token['accessToken'],\n            'refresh_token' => $token['refreshToken']\n        ];\n    }\n\n    /**\n     * @return array\n     * @throws TokenException\n     */\n    public function refreshToken()\n    {\n        $token = $this->loginTokenService->getTokenFromHeaders();\n        $token = $this->loginTokenService->refresh($token);\n        return [\n            'access_token' => $token['accessToken']\n        ];\n    }\n\n    /**\n     * @loginRequired\n     */\n    public function getAllowedApis()\n    {\n        $uid = $this->loginTokenService->getCurrentUid();\n        return UserService::getPermissions($uid);\n    }\n\n    /**\n     * @loginRequired\n     * @return mixed\n     */\n    public function getInformation()\n    {\n        $uid = $this->loginTokenService->getCurrentUid();\n        return UserService::getInformation($uid);\n    }\n\n    /**\n     * @loginRequired\n     * @param Request $request\n     * @validate('UpdateUserForm')\n     * @return Json\n     * @throws RepeatException\n     */\n    public function update(Request $request)\n    {\n        $params = $request->put();\n        $row = UserService::updateUser($params);\n        return writeJson(200, $row, '用户信息更新成功');\n    }\n\n    /**\n     * @loginRequired\n     * @validate('ChangePasswordForm')\n     * @param Request $request\n     * @return Json\n     * @throws AuthFailedException\n     * @throws NotFoundException\n     */\n    public function changePassword(Request $request)\n    {\n        $oldPassword = $request->put('old_password');\n        $newPassword = $request->put('new_password');\n\n        $row = UserService::changePassword($oldPassword, $newPassword);\n\n        Hook::listen('logger', '修改了自己的密码');\n        return writeJson(200, $row, '密码修改成功');\n    }\n}\n"
  },
  {
    "path": "application/api/controller/v1/Book.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/20\n * Time: 19:57\n */\n\nnamespace app\\api\\controller\\v1;\n\nuse app\\api\\model\\Book as BookModel;\nuse think\\facade\\Hook;\nuse think\\Request;\nuse think\\response\\Json;\n\nclass Book\n{\n    /**\n     * 查询指定bid的图书\n     * @param $bid\n     * @param('bid','图书ID','require|number')\n     * @return mixed\n     */\n    public function getBook($bid)\n    {\n        $result = BookModel::get($bid);\n        return $result;\n    }\n\n    /**\n     * 查询所有图书\n     * @return mixed\n     */\n    public function getBooks()\n    {\n        $result = BookModel::all();\n        return $result;\n    }\n\n    /**\n     * 搜索图书\n     */\n    public function search()\n    {\n\n    }\n\n    /**\n     * 新建图书\n     * @param Request $request\n     * @return Json\n     */\n    public function create(Request $request)\n    {\n        $params = $request->post();\n        BookModel::create($params);\n        return writeJson(201, '', '新建图书成功');\n    }\n\n    public function update(Request $request)\n    {\n        $params = $request->put();\n        $bookModel = new BookModel();\n        $bookModel->save($params, ['id' => $params['id']]);\n        return writeJson(201, '', '更新图书成功');\n    }\n\n    /**\n     * @groupRequired\n     * @permission('删除图书','图书')\n     * @param $bid\n     * @return Json\n     */\n    public function delete($bid)\n    {\n        BookModel::destroy($bid);\n        Hook::listen('logger', '删除了id为' . $bid . '的图书');\n        return writeJson(201, '', '删除图书成功');\n    }\n}"
  },
  {
    "path": "application/api/model/BaseModel.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/2/19\n * Time: 11:22\n */\n\nnamespace app\\api\\model;\n\n\nuse think\\Model;\n\nclass BaseModel extends Model\n{\n\n}"
  },
  {
    "path": "application/api/model/Book.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/20\n * Time: 19:58\n */\n\nnamespace app\\api\\model;\n\n\nuse think\\model\\concern\\SoftDelete;\n\nclass Book extends BaseModel\n{\n    use SoftDelete;\n\n    protected $deleteTime = 'delete_time';\n    protected $autoWriteTimestamp = 'datetime';\n\n}"
  },
  {
    "path": "application/api/model/admin/LinFile.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:10 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse think\\Model;\nuse think\\model\\concern\\SoftDelete;\n\nclass LinFile extends Model\n{\n    use SoftDelete;\n\n    public $autoWriteTimestamp = 'datetime';\n}"
  },
  {
    "path": "application/api/model/admin/LinGroup.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:12 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse think\\Model;\nuse think\\model\\concern\\SoftDelete;\n\nclass LinGroup extends Model\n{\n    use SoftDelete;\n\n    public $autoWriteTimestamp = 'datetime';\n    public $hidden = ['level', 'create_time', 'update_time', 'delete_time'];\n\n\n    public function users()\n    {\n        return $this->belongsToMany('LinUser', 'Lin_user_group', 'user_id', 'group_id');\n    }\n\n    public function permissions()\n    {\n        return $this->belongsToMany('LinPermission', 'lin_group_permission', 'permission_id', 'group_id');\n    }\n}"
  },
  {
    "path": "application/api/model/admin/LinGroupPermission.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:13 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse think\\Model;\n\nclass LinGroupPermission extends Model\n{\n\n}"
  },
  {
    "path": "application/api/model/admin/LinLog.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:13 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse think\\Model;\nuse think\\model\\concern\\SoftDelete;\n\nclass LinLog extends Model\n{\n    use SoftDelete;\n\n    public $autoWriteTimestamp = 'datetime';\n    protected $hidden = ['update_time', 'delete_time'];\n\n    public static function getLogs(int $start, int $count, $params = []): array\n    {\n        $logList = self::withSearch(['name', 'start', 'end'], $params);\n\n        $total = $logList->count();\n        $logList = $logList->limit($start, $count)\n            ->order('create_time desc')\n            ->select();\n        return [\n            'logList' => $logList,\n            'total' => $total\n        ];\n    }\n\n    public static function searchLogs(int $start, int $count, $params = [])\n    {\n        $logList = self::withSearch(['name', 'start', 'end', 'keyword'], $params);\n\n        $total = $logList->count();\n        $logList = $logList->limit($start, $count)\n            ->order('create_time desc')\n            ->select();\n        return [\n            'logList' => $logList,\n            'total' => $total\n        ];\n    }\n\n\n    public static function getUserNames(int $start, int $count)\n    {\n        $users = self::field('username');\n\n        $total = $users->count();\n        $users = $users->limit($start, $count)\n            ->group('username')\n            ->select();\n\n        return [\n            'userList' => $users,\n            'total' => $total\n        ];\n    }\n\n    public function searchNameAttr($query, $value)\n    {\n        if ($value) {\n            $query->where('username', $value);\n        }\n    }\n\n    public\n    function searchStartAttr($query, $value)\n    {\n        if ($value) {\n            $query->where('create_time', '>= time', $value);\n        }\n    }\n\n    public\n    function searchEndAttr($query, $value)\n    {\n        if ($value) {\n            $query->where('create_time', '<= time', $value);\n        }\n    }\n\n    public\n    function searchKeywordAttr($query, $value)\n    {\n        if ($value) {\n            $query->whereLike('message', \"%{$value}%\");\n        }\n    }\n}"
  },
  {
    "path": "application/api/model/admin/LinPermission.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:14 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse think\\Model;\nuse think\\model\\concern\\SoftDelete;\n\nclass LinPermission extends Model\n{\n    use SoftDelete;\n\n    public $autoWriteTimestamp = 'datetime';\n    public $hidden = ['create_time', 'update_time', 'delete_time'];\n}"
  },
  {
    "path": "application/api/model/admin/LinUser.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:15 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse think\\facade\\Config;\nuse think\\Model;\nuse think\\model\\concern\\SoftDelete;\n\nclass LinUser extends Model\n{\n    use SoftDelete;\n\n    public $autoWriteTimestamp = 'datetime';\n    public $hidden = ['create_time', 'update_time', 'delete_time'];\n\n    public static function getUsers(int $start, int $count, array $params = [])\n    {\n        $userList = self::withSearch(['group_id'], $params)\n            ->where('username', '<>', 'root');\n        $total = $userList->count();\n\n        $userList = $userList\n            ->limit($start, $count)\n            ->with('groups')\n            ->select();\n\n        return [\n            'userList' => $userList,\n            'total' => $total\n        ];\n    }\n\n    public function groups()\n    {\n        return $this->belongsToMany('LinGroup', 'lin_user_group', 'group_id', 'user_id');\n    }\n\n    public function identity()\n    {\n        return $this->hasMany('LinUserIdentity', 'user_id');\n    }\n\n    public function searchGroupIdAttr($query, $value)\n    {\n        if ($value) {\n            $query->join('lin_group g', 'g.id=' . $value)->where('g.id', '<>', 1);\n        }\n    }\n\n    public function getAvatarAttr($value)\n    {\n        if ($value) {\n            $host = Config::get('file.host') ?? \"http://127.0.0.1:5000/\";\n            $dir = Config::get('file.store_dir');\n            return $host . $dir . '/' . $value;\n        }\n        return $value;\n    }\n}"
  },
  {
    "path": "application/api/model/admin/LinUserGroup.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:15 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse think\\Model;\n\nclass LinUserGroup extends Model\n{\n\n}"
  },
  {
    "path": "application/api/model/admin/LinUserIdentity.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:16 下午\n */\n\nnamespace app\\api\\model\\admin;\n\n\nuse app\\lib\\enum\\IdentityTypeEnum;\nuse app\\lib\\exception\\NotFoundException;\nuse think\\Model;\nuse think\\model\\concern\\SoftDelete;\n\nclass LinUserIdentity extends Model\n{\n    use SoftDelete;\n\n    public $autoWriteTimestamp = 'datetime';\n    protected $hidden = ['create_time', 'update_time', 'delete_time', 'credential'];\n\n    public static function resetPassword(LinUser $currentUser, string $newPassword): void\n    {\n        $user = self::where('identity_type', IdentityTypeEnum::PASSWORD)\n            ->where('identifier', $currentUser->getAttr('username'))\n            ->find();\n\n        if (!$user) {\n            throw new NotFoundException();\n        }\n\n        $user->credential = md5($newPassword);\n        $user->save();\n    }\n\n    public function checkPassword(string $password): bool\n    {\n        return $this->getAttr('credential') === md5($password);\n    }\n}"
  },
  {
    "path": "application/api/service/admin/Admin.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:09 下午\n */\n\nnamespace app\\api\\service\\admin;\n\n\nuse app\\api\\model\\admin\\LinGroup as LinGroupModel;\nuse app\\api\\model\\admin\\LinPermission as LinPermissionModel;\nuse app\\api\\model\\admin\\LinUser as LinUserModel;\nuse app\\api\\model\\admin\\LinUserGroup as LinUserGroupModel;\nuse app\\api\\model\\admin\\LinUserIdentity as LinUserIdentityModel;\nuse app\\lib\\authenticator\\PermissionScan;\nuse app\\lib\\enum\\GroupLevelEnum;\nuse app\\lib\\enum\\MountTypeEnum;\nuse app\\lib\\exception\\NotFoundException;\nuse app\\lib\\exception\\OperationException;\nuse app\\lib\\exception\\token\\ForbiddenException;\nuse LinCmsTp5\\exception\\ParameterException;\nuse PDOStatement;\nuse ReflectionException;\nuse think\\Db;\nuse think\\db\\exception\\DataNotFoundException;\nuse think\\db\\exception\\ModelNotFoundException;\nuse think\\Exception;\nuse think\\exception\\DbException;\nuse think\\model\\Collection;\n\nclass Admin\n{\n    /**\n     * @return array\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws ReflectionException\n     */\n    public static function getAllPermissions(): array\n    {\n        $permissionList = (new PermissionScan())->run();\n        foreach ($permissionList as $permission) {\n            $model = LinPermissionModel::where('name', $permission['name'])\n                ->where('module', $permission['module'])\n                ->find();\n            if (!$model) {\n                self::createPermission($permission['name'], $permission['module']);\n            }\n        }\n\n        $permissions = LinPermissionModel::where('mount', MountTypeEnum::MOUNT)\n            ->select()->toArray();\n        $result = [];\n        foreach ($permissions as $permission) {\n            $result[$permission['module']][] = $permission;\n        }\n        return $result;\n    }\n\n    /**\n     * @param int $page\n     * @param int $count\n     * @param int $groupId\n     * @return array\n     * @throws ParameterException\n     */\n    public static function getUsers(int $page, int $count, int $groupId = null): array\n    {\n        list($start, $count) = paginate($count, $page);\n        $params = $groupId ? ['group_id' => $groupId] : [];\n        $users = LinUserModel::getUsers($start, $count, $params);\n\n        return [\n            'items' => $users['userList'],\n            'count' => $count,\n            'page' => $page,\n            'total' => $users['total']\n        ];\n    }\n\n    /**\n     * @param int $uid\n     * @param string $newPassword\n     * @throws NotFoundException\n     */\n    public static function changeUserPassword(int $uid, string $newPassword): void\n    {\n        $user = LinUserModel::get($uid);\n        if (!$user) {\n            throw new NotFoundException();\n        }\n\n        LinUserIdentityModel::resetPassword($user, $newPassword);\n\n    }\n\n    /**\n     * @param int $uid\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public static function deleteUser(int $uid): void\n    {\n        $user = LinUserModel::get($uid, 'identity');\n        if (!$user) {\n            throw new NotFoundException();\n        }\n\n        Db::startTrans();\n        try {\n            $user->groups()->detach();\n            $user->together('identity')->delete();\n            Db::commit();\n        } catch (Exception $ex) {\n            DB::rollback();\n            throw new OperationException(['msg' => \"删除用户失败\"]);\n        }\n    }\n\n    /**\n     * @param int $uid\n     * @param array $groupIds\n     * @throws ForbiddenException\n     * @throws NotFoundException\n     * @throws OperationException\n     * @throws DataNotFoundException\n     * @throws ModelNotFoundException\n     * @throws DbException\n     */\n    public static function updateUserInfo(int $uid, array $groupIds): void\n    {\n        $user = LinUserModel::get($uid);\n        if (!$user) {\n            throw new NotFoundException();\n        }\n\n        $userGroupIds = LinUserGroupModel::where('user_id', $uid)->column('group_id');\n        $isAdmin = LinGroupModel::where('level', GroupLevelEnum::ROOT)\n            ->whereIn('id', $userGroupIds)\n            ->find();\n        if ($isAdmin) {\n            throw new ForbiddenException(['code' => 10078, 'msg' => '不允许调整root分组信息']);\n        }\n\n        foreach ($userGroupIds as $groupId) {\n            $group = LinGroupModel::get($groupId);\n            if ($group['level'] === GroupLevelEnum::ROOT) {\n                throw new ForbiddenException(['code' => 10073, 'msg' => '不允许添加用户到root分组']);\n            }\n\n            if (!$group) {\n                throw new NotFoundException(['code' => 10077]);\n            }\n        }\n\n        Db::startTrans();\n        try {\n            $user->groups()->detach();\n            $user->groups()->attach($groupIds);\n            Db::commit();\n        } catch (Exception $ex) {\n            DB::rollback();\n            throw new OperationException(['msg' => \"更新用户分组失败\"]);\n        }\n    }\n\n    /**\n     * @return array|PDOStatement|string|\\think\\Collection|Collection\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     */\n    public static function getAllGroups()\n    {\n        $groups = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT)->select();\n        if ($groups->isEmpty()) {\n            throw new NotFoundException();\n        }\n        return $groups;\n    }\n\n    /**\n     * @param int $id\n     * @return \\think\\db\\Query\n     * @throws DbException\n     * @throws NotFoundException\n     */\n    public static function getGroup(int $id)\n    {\n        $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT)\n            ->get($id, 'permissions');\n        if (!$group) {\n            throw new NotFoundException();\n        }\n\n        return $group;\n    }\n\n    /**\n     * @param string $name\n     * @param string $info\n     * @param array $permissionIds\n     * @return int\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public static function createGroup(string $name, string $info, array $permissionIds): int\n    {\n        $isExist = LinGroupModel::where('name', $name)->find();\n        if ($isExist) {\n            throw new OperationException(['msg' => '分组名已存在']);\n        }\n\n        foreach ($permissionIds as $permissionId) {\n            $permission = LinPermissionModel::where('mount', MountTypeEnum::MOUNT)\n                ->get($permissionId);\n            if (!$permission) {\n                throw new NotFoundException(['error_code' => 10231, 'msg' => '分配了不存在的权限']);\n            }\n        }\n\n        Db::startTrans();\n        try {\n            $group = LinGroupModel::create(['name' => $name, 'info' => $info], true);\n            $group->permissions()->saveAll($permissionIds);\n            Db::commit();\n            return $group->getAttr('id');\n        } catch (\\Exception $ex) {\n            Db::rollback();\n            throw new OperationException(['msg' => \"新增分组失败:{$ex->getMessage()}\"]);\n        }\n\n    }\n\n    /**\n     * @param int $id\n     * @param string $name\n     * @param string $info\n     * @return int\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     */\n    public static function updateGroup(int $id, string $name, string $info): int\n    {\n        $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT)\n            ->find($id);\n\n        if (!$group) {\n            throw new NotFoundException();\n        }\n\n        return $group->save(['name' => $name, 'info' => $info]);\n    }\n\n    /**\n     * @param int $id\n     * @throws ForbiddenException\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public static function deleteGroup(int $id): void\n    {\n        $group = LinGroupModel::find($id);\n\n        if (!$group) {\n            throw new NotFoundException();\n        }\n\n        if ($group->getAttr('level') === GroupLevelEnum::ROOT) {\n            throw new ForbiddenException(['msg' => '不允许删除root分组']);\n        }\n\n        if ($group->getAttr('level') === GroupLevelEnum::GUEST) {\n            throw new ForbiddenException(['msg' => '不允许删除guest分组']);\n        }\n\n        Db::startTrans();\n        try {\n            $group->permissions()->detach();\n            $group->users()->detach();\n            Db::commit();\n        } catch (Exception $ex) {\n            Db::rollback();\n            throw new OperationException(['msg' => \"删除分组失败:{$ex->getMessage()}\"]);\n        }\n    }\n\n    /**\n     * @param int $id\n     * @param array $permissionIds\n     * @throws DbException\n     * @throws NotFoundException\n     * @throws OperationException\n     */\n    public static function dispatchPermissions(int $id, array $permissionIds)\n    {\n        $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT)\n            ->get($id);\n        if (!$group) {\n            throw new NotFoundException();\n        }\n\n        foreach ($permissionIds as $permissionId) {\n            $permission = LinPermissionModel::where('mount', MountTypeEnum::MOUNT)\n                ->get($permissionId);\n            if (!$permission) {\n                throw new NotFoundException(['error_code' => 10231, 'msg' => '分配了不存在的权限']);\n            }\n        }\n\n        try {\n            $group->permissions()->attach($permissionIds);\n        } catch (Exception $ex) {\n            throw new OperationException(['msg' => '权限分配失败']);\n        }\n    }\n\n    /**\n     * @param int $id\n     * @param array $permissionIds\n     * @return int\n     * @throws DbException\n     * @throws NotFoundException\n     */\n    public static function removePermissions(int $id, array $permissionIds): int\n    {\n        $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT)\n            ->get($id);\n        if (!$group) {\n            throw new NotFoundException();\n        }\n\n        foreach ($permissionIds as $permissionId) {\n            $permission = LinPermissionModel::where('mount', MountTypeEnum::MOUNT)\n                ->get($permissionId);\n            if (!$permission) {\n                throw new NotFoundException(['error_code' => 10231, 'msg' => '分配了不存在的权限']);\n            }\n        }\n\n        return $group->permissions()->detach($permissionIds);\n    }\n\n    public static function createPermission(string $name, string $module): LinPermissionModel\n    {\n        return LinPermissionModel::create(['name' => $name, 'module' => $module, 'mount' => 1]);\n    }\n}"
  },
  {
    "path": "application/api/service/admin/Log.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/6\n * Time: 11:53 下午\n */\n\nnamespace app\\api\\service\\admin;\n\nuse app\\api\\model\\admin\\LinLog as LinLogModel;\nuse LinCmsTp5\\exception\\ParameterException;\n\nclass Log\n{\n    /**\n     * @param int $page\n     * @param int $count\n     * @param string|null $start\n     * @param string|null $end\n     * @param string|null $name\n     * @return array\n     * @throws ParameterException\n     */\n    public static function getLogs(int $page, int $count, string $start = null, string $end = null, string $name = null)\n    {\n        list($offset, $count) = paginate($count, $page);\n        $params = ['start' => $start, 'end' => $end, 'name' => $name];\n        $logsRes = LinLogModel::getLogs($offset, $count, $params);\n\n        return [\n            'items' => $logsRes['logList'],\n            'count' => $count,\n            'page' => $page,\n            'total' => $logsRes['total']\n        ];\n    }\n\n    /**\n     * @param int $page\n     * @param int $count\n     * @param string|null $start\n     * @param string|null $end\n     * @param string|null $name\n     * @param string|null $keyword\n     * @return array\n     * @throws ParameterException\n     */\n    public static function searchLogs(int $page, int $count, string $start = null,\n                                      string $end = null, string $name = null, string $keyword = null)\n    {\n        list($offset, $count) = paginate($count, $page);\n        $params = ['start' => $start, 'end' => $end, 'name' => $name, 'keyword' => $keyword];\n\n        $logsRes = LinLogModel::searchLogs($offset, $count, $params);\n\n        return [\n            'items' => $logsRes['logList'],\n            'count' => $count,\n            'page' => $page,\n            'total' => $logsRes['total']\n        ];\n    }\n\n    /**\n     * @param int $page\n     * @param int $count\n     * @return array\n     * @throws ParameterException\n     */\n    public static function getUserNames(int $page, int $count)\n    {\n        list($start, $count) = paginate($count, $page);\n        $usersRes = LinLogModel::getUserNames($start, $count);\n        $items = array_map(function ($item) {\n            return $item['username'];\n        }, $usersRes['userList']->toArray());\n\n        return [\n            'items' => $items,\n            'count' => $count,\n            'page' => $page,\n            'total' => $usersRes['total']\n        ];\n    }\n}"
  },
  {
    "path": "application/api/service/admin/User.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 6:09 下午\n */\n\nnamespace app\\api\\service\\admin;\n\nuse app\\api\\model\\admin\\LinGroup as LinGroupModel;\nuse app\\api\\model\\admin\\LinGroupPermission as LinGroupPermissionModel;\nuse app\\api\\model\\admin\\LinPermission as LinPermissionModel;\nuse app\\api\\model\\admin\\LinUser;\nuse app\\api\\model\\admin\\LinUser as LinUserModel;\nuse app\\api\\model\\admin\\LinUserGroup as LinUserGroupModel;\nuse app\\api\\model\\admin\\LinUserIdentity as LinUserIdentityModel;\nuse app\\api\\service\\token\\LoginToken;\nuse app\\lib\\enum\\GroupLevelEnum;\nuse app\\lib\\enum\\IdentityTypeEnum;\nuse app\\lib\\enum\\MountTypeEnum;\nuse app\\lib\\exception\\AuthFailedException;\nuse app\\lib\\exception\\NotFoundException;\nuse app\\lib\\exception\\OperationException;\nuse app\\lib\\exception\\RepeatException;\nuse app\\lib\\exception\\token\\ForbiddenException;\nuse think\\Db;\nuse think\\db\\exception\\DataNotFoundException;\nuse think\\db\\exception\\ModelNotFoundException;\nuse think\\Exception;\nuse think\\exception\\DbException;\nuse think\\Model;\n\nclass User\n{\n    /**\n     * @param array $params\n     * @return LinUserModel\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ForbiddenException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     * @throws OperationException\n     * @throws RepeatException\n     */\n    public static function createUser(array $params): LinUserModel\n    {\n        $user = LinUserModel::where('username', $params['username'])->find();\n        if ($user) {\n            throw new RepeatException(['msg' => '用户名已存在']);\n        }\n\n        if (isset($params['email'])) {\n            $user = LinUserModel::where('email', $params['email'])->find();\n            if ($user) {\n                throw new RepeatException(['msg' => '邮箱地址已存在']);\n            }\n        }\n\n        if (isset($params['group_ids'])) {\n            $groups = LinGroupModel::select($params['group_ids']);\n            foreach ($groups as $group) {\n                if ($group['level'] === GroupLevelEnum::ROOT) {\n                    throw new ForbiddenException(['msg' => '不允许分配用户到root分组']);\n                }\n            }\n\n            if ($groups->isEmpty()) {\n                throw new NotFoundException();\n            }\n        }\n\n        return self::registerUser($params);\n    }\n\n    /**\n     * @param string $username\n     * @param string $password\n     * @return Model\n     * @throws AuthFailedException\n     * @throws DataNotFoundException\n     * @throws DbException\n     * @throws ModelNotFoundException\n     * @throws NotFoundException\n     */\n    public static function verify(string $username, string $password): Model\n    {\n        $user = new LinUserIdentityModel();\n\n        $user = $user->where('identifier', $username)\n            ->where('identity_type', IdentityTypeEnum::PASSWORD)\n            ->find();\n\n        if (!$user) {\n            throw new NotFoundException(['msg' => '用户不存在']);\n        }\n\n        if (!$user->checkPassword($password)) {\n            throw new AuthFailedException();\n        }\n        return $user;\n    }\n\n    public static function generateTokenExtend(Model $linUserIdentityModel)\n    {\n        $user = LinUserModel::get($linUserIdentityModel['user_id']);\n        $userPermissions = self::getPermissions($user->getAttr('id'));\n        return [\n            'id' => $user->getAttr('id'),\n            'identifier' => $linUserIdentityModel->getAttr('identifier'),\n            'email' => $user->getAttr('email'),\n            'admin' => $userPermissions['admin'],\n            'permissions' => $userPermissions['permissions'],\n        ];\n    }\n\n    public static function getPermissions(int $uid): array\n    {\n        $user = LinUserModel::get($uid);\n\n        $groupIds = LinUserGroupModel::where('user_id', $uid)\n            ->column('group_id');\n\n        $root = LinGroupModel::where('level', GroupLevelEnum::ROOT)\n            ->whereIn('id', $groupIds)->find();\n\n        $user = $user->hidden(['username'])->toArray();\n        $user['admin'] = $root ? true : false;\n\n        if ($root) {\n            $permissions = LinPermissionModel::where('mount', MountTypeEnum::MOUNT)\n                ->select()\n                ->toArray();\n            $user['permissions'] = formatPermissions($permissions);\n        } else {\n            $permissionIds = LinGroupPermissionModel::whereIn('group_id', $groupIds)\n                ->column('permission_id');\n            $permissions = LinPermissionModel::where('mount', MountTypeEnum::MOUNT)\n                ->select($permissionIds)->toArray();\n\n            $user['permissions'] = formatPermissions($permissions);\n\n        }\n\n        return $user;\n    }\n\n    public static function getInformation(int $uid)\n    {\n        return LinUser::get($uid, 'groups');\n    }\n\n    public static function updateUser(array $params): int\n    {\n        $user = LoginToken::getInstance()->getTokenExtend();\n        if (isset($params['username']) && $params['username'] !== $user['username']) {\n            $isExit = LinUserModel::where('username', $params['username'])\n                ->find();\n            if ($isExit) {\n                throw new RepeatException(['msg' => \"用户名已被占用\"]);\n            }\n        }\n\n        if (isset($params['email']) && $params['email'] !== $user['email']) {\n            $isExit = LinUserModel::where('email', $params['email'])\n                ->find();\n            if ($isExit) {\n                throw new RepeatException(['msg' => \"邮箱已被占用\"]);\n            }\n        }\n\n        $user = LinUserModel::get($user['id']);\n        return $user->allowField(true)->save($params);\n    }\n\n    public static function changePassword(string $oldPassword, string $newPassword): int\n    {\n        $currentUser = LoginToken::getInstance()->getTokenExtend();\n        $user = new LinUserIdentityModel();\n\n        $user = $user::where('identity_type', IdentityTypeEnum::PASSWORD)\n            ->where('identifier', $currentUser['identifier'])\n            ->find();\n\n        if (!$user) {\n            throw new NotFoundException();\n        }\n\n        if (!$user->checkPassword($oldPassword)) {\n            throw new AuthFailedException();\n        }\n\n        $user->credential = md5($newPassword);\n        return $user->save();\n    }\n\n    /**\n     * @param array $params\n     * @return LinUserModel\n     * @throws OperationException\n     */\n    private static function registerUser(array $params): LinUserModel\n    {\n        Db::startTrans();\n        try {\n            $user = LinUserModel::create($params, true);\n            $user->identity()->save([\n                'identity_type' => IdentityTypeEnum::PASSWORD,\n                'identifier' => $user['username'],\n                'credential' => md5($params['password'])\n            ]);\n\n            // 判断是否同时分配了分组\n            if (isset($params['group_ids']) && count($params['group_ids']) > 0) {\n                $user->groups()->attach($params['group_ids']);\n            } else {\n                //  没有分配分组，添加到游客分组\n                $group = LinGroupModel::where('level', GroupLevelEnum::GUEST)->find();\n                $user->groups()->attach([$group['id']]);\n            }\n            Db::commit();\n            return $user;\n        } catch (Exception $ex) {\n            Db::rollback();\n            throw new OperationException(['msg' => \"注册用户失败：{$ex->getMessage()}\"]);\n        }\n\n    }\n\n    // private static function formatPermissions(array $permissions)\n    // {\n    //     $groupPermission = [];\n    //     foreach ($permissions as $permission) {\n    //         $item = [\n    //             'name' => $permission['name'],\n    //             'module' => $permission['module']\n    //         ];\n    //         $groupPermission[$permission['module']][] = $item;\n    //     }\n    //\n    //     $result[] = array_map(function ($item) {\n    //         return $item;\n    //     }, $groupPermission);\n    //     return $result;\n    // }\n}"
  },
  {
    "path": "application/api/service/token/LoginToken.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/9/5\n * Time: 6:09 下午\n */\n\nnamespace app\\api\\service\\token;\n\n\nuse app\\lib\\exception\\token\\TokenException;\nuse Firebase\\JWT\\BeforeValidException;\nuse Firebase\\JWT\\ExpiredException;\nuse Firebase\\JWT\\SignatureInvalidException;\nuse qinchen\\token\\Token as TokenUtils;\nuse qinchen\\token\\TokenConfig;\nuse think\\facade\\Config;\nuse think\\facade\\Request;\nuse UnexpectedValueException;\n\n\nclass LoginToken\n{\n    /**\n     * @var LoginToken\n     */\n    private static $instance;\n\n    /**\n     * @var TokenConfig\n     */\n    private $tokenConfig;\n\n    /**\n     * LoginToken constructor.\n     */\n    private function __construct()\n    {\n        $config = Config::pull('token');\n        $this->tokenConfig = (new TokenConfig())\n            ->dualToken($config['enable_dual_token'])\n            ->setAlgorithms($config['algorithms'])\n            ->setIat(time())\n            ->setIss($config['issuer'])\n            ->setAccessSecretKey($config['access_secret_key'])\n            ->setAccessExp($config['access_expire_time'])\n            ->setRefreshSecretKey($config['refresh_secret_key'])\n            ->setRefreshExp($config['refresh_expire_time']);\n    }\n\n    public static function getInstance(): LoginToken\n    {\n        if (!self::$instance instanceof self) {\n            self::$instance = new self();\n        }\n        return self::$instance;\n    }\n\n    /**\n     * 获取令牌\n     * @param array $extend 要插入到令牌扩展字段中的信息\n     * @return array\n     * @throws \\Exception\n     */\n    public function getToken(array $extend): array\n    {\n        $this->tokenConfig->setExtend($extend);\n        return TokenUtils::makeToken($this->tokenConfig);\n    }\n\n    /**\n     * 令牌刷新\n     * @param string $refreshToken 当开启了双令牌后颁发的refreshToken,用于刷新accessToken\n     * @return array\n     * @throws TokenException\n     */\n    public function refresh(string $refreshToken): array\n    {\n        try {\n            return TokenUtils::refresh($refreshToken, $this->tokenConfig);\n        } catch (SignatureInvalidException $signatureInvalidException) {\n            throw new TokenException(['msg' => '令牌签名错误']);\n        } catch (BeforeValidException $beforeValidException) {\n            throw new TokenException();\n        } catch (ExpiredException $expiredException) {\n            throw new TokenException(['error_code' => 10042, 'msg' => '令牌已过期，请重新登录']);\n        } catch (UnexpectedValueException $unexpectedValueException) {\n            throw new TokenException();\n        }\n    }\n\n    /**\n     * @param string|null $token\n     * @param string $tokenType\n     * @return array\n     * @throws TokenException\n     */\n    public function verify(string $token = null, string $tokenType = 'access')\n    {\n        $token = $token ?: $this->getTokenFromHeaders();\n        try {\n            return TokenUtils::verifyToken($token, $tokenType, $this->tokenConfig);\n        } catch (SignatureInvalidException $signatureInvalidException) {\n            throw new TokenException(['msg' => '令牌签名错误']);\n        } catch (BeforeValidException $beforeValidException) {\n            throw new TokenException();\n        } catch (ExpiredException $expiredException) {\n            throw new TokenException(['error_code' => 10041, 'msg' => '令牌已过期']);\n        } catch (UnexpectedValueException $unexpectedValueException) {\n            throw new TokenException();\n        }\n    }\n\n    /**\n     * 获取令牌扩展字段内容\n     * @param string|null $token\n     * @param string $tokenType\n     * @return array\n     * @throws TokenException\n     */\n    public function getTokenExtend(string $token = null, string $tokenType = 'access'): array\n    {\n        return (array)$this->verify($token, $tokenType)['extend'];\n    }\n\n    /**\n     * 获取指定令牌扩展内容字段的值\n     * @param string $val\n     * @return mixed\n     * @throws TokenException\n     */\n    public function getExtendVal(string $val)\n    {\n        return $this->getTokenExtend()[$val];\n    }\n\n    public function getCurrentUid()\n    {\n        return $this->getExtendVal('id');\n    }\n\n    public function getCurrentUserName()\n    {\n        return $this->getExtendVal('identifier');\n    }\n\n    public function getTokenFromHeaders(): string\n    {\n        $authorization = Request::header('authorization');\n\n        if (!$authorization) {\n            throw new TokenException(['msg' => '请求未携带Authorization信息']);\n        }\n\n        list($type, $token) = explode(' ', $authorization);\n\n        if ($type !== 'Bearer') throw new TokenException(['msg' => '接口认证方式需为Bearer']);\n\n        if (!$token || $token === 'undefined') {\n            throw new TokenException(['msg' => '尝试获取的Authorization信息不存在']);\n        }\n\n        return $token;\n    }\n}"
  },
  {
    "path": "application/api/validate/user/ChangePasswordForm.php",
    "content": "<?php\n\n\nnamespace app\\api\\validate\\user;\n\n\nuse LinCmsTp5\\validate\\BaseValidate;\n\nclass ChangePasswordForm extends BaseValidate\n{\n    protected $rule = [\n        'old_password|原始密码' => 'require',\n        'new_password|新密码' => 'require|confirm:confirm_password',\n        'confirm_password|确认密码' => 'require',\n    ];\n}"
  },
  {
    "path": "application/api/validate/user/LoginForm.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/28\n * Time: 21:38\n */\n\nnamespace app\\api\\validate\\user;\n\n\nuse LinCmsTp5\\validate\\BaseValidate;\n\nclass LoginForm extends BaseValidate\n{\n    protected $rule = [\n        'username' => 'require',\n        'password' => 'require',\n    ];\n\n    protected $message = [\n        'username' => '用户名不能为空',\n        'password' => '密码不能为空'\n    ];\n}"
  },
  {
    "path": "application/api/validate/user/RegisterForm.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/28\n * Time: 21:38\n */\n\nnamespace app\\api\\validate\\user;\n\n\nuse LinCmsTp5\\validate\\BaseValidate;\n\nclass RegisterForm extends BaseValidate\n{\n    protected $rule = [\n        'password' => 'require|confirm:confirm_password',\n        'confirm_password' => 'require',\n        'username' => 'require|length:2,10',\n        'group_ids' => 'array',\n        'email' => 'email'\n    ];\n}"
  },
  {
    "path": "application/api/validate/user/ResetPasswordValidator.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 10:58 下午\n */\n\nnamespace app\\api\\validate\\user;\n\n\nuse LinCmsTp5\\validate\\BaseValidate;\n\nclass ResetPasswordValidator extends BaseValidate\n{\n    protected $rule = [\n        'id' => 'require|integer',\n        'new_password|新密码' => 'require|confirm:confirm_password',\n        'confirm_password|确认密码' => 'require',\n    ];\n}"
  },
  {
    "path": "application/api/validate/user/UpdateUserForm.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/7\n * Time: 2:35 下午\n */\n\nnamespace app\\api\\validate\\user;\n\n\nuse LinCmsTp5\\validate\\BaseValidate;\n\nclass UpdateUserForm extends BaseValidate\n{\n    protected $rule = [\n        'username' => 'length:2,10',\n        'email' => 'email',\n        'nickname' => 'length:2,10',\n    ];\n}"
  },
  {
    "path": "application/command.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: yunwuxin <448901948@qq.com>\n// +----------------------------------------------------------------------\n\nreturn [];\n"
  },
  {
    "path": "application/common.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: 流年 <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// 应用公共文件\nuse LinCmsTp5\\exception\\ParameterException;\nuse think\\response\\Json;\n\n/**\n * 统一响应包装函数\n * @param $code\n * @param $errorCode\n * @param $data\n * @param $msg\n * @return Json\n */\nfunction writeJson($code, $data, $msg = 'ok', $errorCode = 0)\n{\n    $data = [\n        'code' => $errorCode,\n        'result' => $data,\n        'message' => $msg\n    ];\n    return json($data, $code);\n}\n\n/**\n * 分页参数处理函数\n * @param int $count\n * @param int $page\n * @return array\n * @throws ParameterException\n */\nfunction paginate(int $count = 10, int $page = 0)\n{\n    // $count = intval(Request::get('count', $count));\n    // $start = intval(Request::get('page', $page));\n    // $page = $start;\n    $count = $count >= 15 ? 15 : $count;\n    $start = $page * $count;\n\n    if ($start < 0 || $count < 0) throw new ParameterException();\n\n    return [$start, $count];\n}\n\n/**\n * 权限数组格式化函数\n * @param array $permissions\n * @return array\n */\nfunction formatPermissions(array $permissions)\n{\n    $groupPermission = [];\n    foreach ($permissions as $permission) {\n        $item = [\n            'permission' => $permission['name'],\n            'module' => $permission['module']\n        ];\n        $groupPermission[$permission['module']][] = $item;\n    }\n    $result = [];\n    foreach ($groupPermission as $key => $item) {\n        array_push($result, [$key => $item]);\n    }\n\n    return $result;\n}"
  },
  {
    "path": "application/http/middleware/Authentication.php",
    "content": "<?php\n\nnamespace app\\http\\middleware;\n\nuse app\\lib\\authenticator\\Authenticator;\nuse app\\lib\\exception\\token\\ForbiddenException;\n\nclass Authentication\n{\n    /**\n     * 权限验证\n     * @param $request\n     * @param \\Closure $next\n     * @return mixed\n     * @throws \\ReflectionException\n     * @throws \\app\\lib\\exception\\token\\TokenException\n     * @throws \\think\\Exception\n     */\n    public function handle($request, \\Closure $next)\n    {\n\n        $auth = (new Authenticator($request))->check();\n\n        if (!$auth) {\n            throw new ForbiddenException();\n        }\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "application/index/controller/Index.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/28\n * Time: 14:18\n */\n\nnamespace app\\index\\controller;\n\n\nclass Index\n{\n    /**\n     * 首次部署显示欢迎用的，部署完成后可以干掉这个index模块的整个目录\n     * @return \\think\\Response\n     */\n    public function index()\n    {\n        return response('<style type=\"text/css\">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: \n    pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: \n    \"Century Gothic\",\"Microsoft yahei\"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; \n    margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style=\"padding: 24px 48px;\"><p> \n    Lin <br/><span style=\"font-size:30px\">心上无垢，林间有风。</span></p></div>');\n    }\n}"
  },
  {
    "path": "application/lib/authenticator/Authenticator.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/2/19\n * Time: 9:54\n */\n\nnamespace app\\lib\\authenticator;\n\nuse app\\api\\service\\token\\LoginToken;\nuse app\\lib\\enum\\PermissionLevelEnum;\nuse app\\lib\\exception\\token\\DeployException;\nuse Exception;\nuse ReflectionClass;\nuse ReflectionException;\nuse think\\Request;\nuse WangYu\\Reflex;\n\nclass Authenticator\n{\n\n    private $parsedClass;\n\n    public function __construct(Request $request)\n    {\n        // 获取当前请求的控制层\n        $controller = $request->controller();\n        // 控制层下有二级目录，需要解析下。如controller/cms/Admin，获取到的是Cms.Admin\n        $controllerPath = explode('.', $controller);\n        // 获取当前请求的方法\n        $action = $request->action();\n        // 反射获取当前请求的控制器类\n        $class = new ReflectionClass('app\\\\api\\\\controller\\\\' . strtolower($controllerPath[0]) . '\\\\' . $controllerPath[1]);\n        $this->parsedClass = (new Reflex($class->newInstance()))->setMethod($action);\n    }\n\n    /**\n     * 入口方法\n     * @return bool\n     * @throws DeployException\n     * @throws ReflectionException\n     */\n    public function check(): bool\n    {\n        //判断是否开启加载文件函数注释\n        if (ini_get('opcache.save_comments') === '0' || ini_get('opcache.save_comments') === '') {\n            throw new DeployException();\n        }\n        // 获取方法权限控制等级\n        $actionPermissionLevel = $this->actionAuthorityLevel();\n        // 没有等级标识，直接通过\n        if (!$actionPermissionLevel) {\n            return true;\n        }\n\n        // 执行校验并返回校验结果\n        return $this->execute($actionPermissionLevel);\n\n    }\n\n    /**\n     * 执行各权限等级校验\n     * @param string $actionPermissionLevel\n     * @return bool\n     * @throws ReflectionException\n     */\n    public function execute(string $actionPermissionLevel): bool\n    {\n        // 账户信息，包含所拥有的权限列表\n        $userInfo = $this->getUserInfo();\n        //账户属于超级管理员，直接通过\n        if ($userInfo['admin'] === true) return true;\n        $actionPermissionName = $this->actionPermission();\n\n        return AuthenticatorExecutorFactory::getInstance($actionPermissionLevel)->handle($userInfo, $actionPermissionName);\n\n    }\n\n    /**\n     * 获取接口权限等级注解\n     * @return string\n     * @throws Exception\n     */\n    protected function actionAuthorityLevel(): string\n    {\n        $permissionLevel = null;\n\n        if ($this->parsedClass->isExist(PermissionLevelEnum::LOGIN_REQUIRED)) {\n            $permissionLevel = PermissionLevelEnum::LOGIN_REQUIRED;\n            return $permissionLevel;\n        }\n\n        if ($this->parsedClass->isExist(PermissionLevelEnum::GROUP_REQUIRED)) {\n            $permissionLevel = PermissionLevelEnum::GROUP_REQUIRED;\n            return $permissionLevel;\n        }\n        if ($this->parsedClass->isExist(PermissionLevelEnum::ADMIN_REQUIRED)) {\n            $permissionLevel = PermissionLevelEnum::ADMIN_REQUIRED;\n            return $permissionLevel;\n        }\n        return '';\n    }\n\n    protected function getUserInfo(): array\n    {\n        return LoginToken::getInstance()->getTokenExtend();\n    }\n\n    /**\n     * 获取接口权限注解内容\n     * @return string\n     * @throws Exception\n     */\n    protected function actionPermission(): string\n    {\n        $actionAuthContent = $this->parsedClass->get('permission');\n\n        $actionAuthContent = empty($actionAuthContent) ? '' : $actionAuthContent[0] . '/' . $actionAuthContent[1];\n        return $actionAuthContent;\n    }\n}\n"
  },
  {
    "path": "application/lib/authenticator/AuthenticatorExecutorFactory.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/8/14\n * Time: 11:21 下午\n */\n\nnamespace app\\lib\\authenticator;\n\n\nuse app\\lib\\authenticator\\executor\\IExecutor;\nuse app\\lib\\authenticator\\executor\\impl\\AdminRequireExecutorImpl;\nuse app\\lib\\authenticator\\executor\\impl\\GroupRequireExecutorImpl;\nuse app\\lib\\authenticator\\executor\\impl\\LoginRequireExecutorImpl;\nuse app\\lib\\enum\\PermissionLevelEnum;\n\nclass AuthenticatorExecutorFactory\n{\n    public static function getInstance(string $level): IExecutor\n    {\n        $instance = null;\n        switch ($level) {\n            case PermissionLevelEnum::LOGIN_REQUIRED:\n                $instance = new LoginRequireExecutorImpl();\n                break;\n            case PermissionLevelEnum::GROUP_REQUIRED:\n                $instance = new GroupRequireExecutorImpl();\n                break;\n            case PermissionLevelEnum::ADMIN_REQUIRED:\n                $instance = new AdminRequireExecutorImpl();\n                break;\n        }\n        return $instance;\n    }\n}"
  },
  {
    "path": "application/lib/authenticator/PermissionScan.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/2/19\n * Time: 9:59\n */\n\nnamespace app\\lib\\authenticator;\n\n\nuse app\\lib\\enum\\MountTypeEnum;\nuse Exception;\nuse ReflectionClass;\nuse ReflectionException;\nuse WangYu\\Reflex;\n\nclass PermissionScan\n{\n    private $namespaceList;\n\n    public function __construct()\n    {\n        $this->namespaceList = (new Scan())->scanController();\n    }\n\n    /**\n     * @throws ReflectionException\n     */\n    public function run()\n    {\n        return $this->getPermissionList();\n    }\n\n    /**\n     * @return array\n     * @throws ReflectionException\n     */\n    private function getPermissionList()\n    {\n        $permissionList = [];\n        // 遍历需要解析@permission注解的控制器类\n        foreach ($this->namespaceList as $value) {\n            // 反射控制器类\n            $class = new ReflectionClass($value);\n            // 类下面的所有方法的数组\n            $methods = $class->getMethods();\n            // 类下面所有含有@permission注解的方法的注解内容数组\n            $methodPermissionList = $this->getPermissionByMethods($class->newInstance(), $methods);\n\n            if (!empty($methodPermissionList)) {\n                // 插入类权限数组\n                if (empty($permissionList)) {\n                    $permissionList = $methodPermissionList;\n                } else {\n                    $permissionList = array_merge($permissionList, $methodPermissionList);\n                }\n            }\n        }\n        return $permissionList;\n    }\n\n    /**\n     * @param $class\n     * @param $methods\n     * @param string $annotationField\n     * @return array\n     * @throws Exception\n     */\n    private function getPermissionByMethods($class, $methods, $annotationField = 'permission')\n    {\n        $data = [];\n        $re = new Reflex($class);\n        foreach ($methods as $value) {\n            $re->setMethod($value->name);\n            $permissionAnnotationArray = $re->get($annotationField);\n\n            if (!empty($permissionAnnotationArray) && !in_array('hidden', $permissionAnnotationArray)) {\n                $permission = $this->handleAnnotation($permissionAnnotationArray);\n                array_push($data, $permission);\n            }\n        }\n\n        return $data;\n    }\n\n    public function handleAnnotation(array $annotation)\n    {\n        return [\n            'name' => $annotation[0],\n            'module' => $annotation[1],\n            'mount' => MountTypeEnum::MOUNT\n        ];\n    }\n}"
  },
  {
    "path": "application/lib/authenticator/Scan.php",
    "content": "<?php\n\n\nnamespace app\\lib\\authenticator;\n\n\nuse think\\facade\\Env;\n\nclass Scan\n{\n    // 控制器层命名空间\n    private $controller_namespace;\n    // 控制器层绝对路径\n    private $controller_path;\n    // 需要权限扫描的命名空间列表\n    private $authScanNamespaceList;\n\n    public function __construct()\n    {\n        // 指定控制器层的命名空间\n        $this->controller_namespace = 'app\\\\api\\\\controller\\\\';\n        // 拼接出当前应用模块下的控制器层目录在服务器上的绝对路径\n        $this->controller_path = Env::get('module_path') . 'controller';\n        // 初始化需权限扫描的命名空间列表\n        $this->authScanNamespaceList = [];\n    }\n\n    /**\n     * 入口方法，调用scanControllerLayerDir（）扫描控制器层\n     * @return array  控制器层下所有类的完整命名空间数组\n     */\n    public function scanController()\n    {\n        return $this->scanControllerLayerDir($this->controller_path);\n    }\n\n    /**\n     * 递归扫描控制器目录，扫描到类文件的时候push命名空间到$this->authScanNamespaceList\n     * @param string $path 扫描的目标目录\n     * @param string $subModule 可空，目标目录的子目录\n     * @return array 控制器层下所有类的完整命名空间数组\n     */\n    private function scanControllerLayerDir(string $path, string $subModule = '')\n    {\n        $files = scandir($path);\n        foreach ($files as $file) {\n            if ($file !== '.' && $file !== '..') {\n                if (strpos($file, '.php')) {\n                    $classFileName = substr($file, 0, -4);\n                    $module = $subModule ? $subModule . '\\\\' : '';\n                    $completeNamespace = $this->controller_namespace . $module . $classFileName;\n                    array_push($this->authScanNamespaceList, $completeNamespace);\n                } else {\n                    $ds = PHP_OS === 'WINNT' ? '\\\\' : DIRECTORY_SEPARATOR;\n                    $subDir = $path . $ds . $file;\n                    $this->scanControllerLayerDir($subDir, $file);\n                }\n            }\n        }\n        return $this->authScanNamespaceList;\n    }\n}"
  },
  {
    "path": "application/lib/authenticator/executor/IExecutor.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/8/14\n * Time: 11:27 下午\n */\n\nnamespace app\\lib\\authenticator\\executor;\n\n\ninterface IExecutor\n{\n    public function handle(array $userInfo = null, string $permissionName = ''): bool;\n}"
  },
  {
    "path": "application/lib/authenticator/executor/impl/AdminRequireExecutorImpl.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/8/14\n * Time: 11:29 下午\n */\n\nnamespace app\\lib\\authenticator\\executor\\impl;\n\n\nuse app\\lib\\authenticator\\executor\\IExecutor;\n\nclass AdminRequireExecutorImpl implements IExecutor\n{\n\n    public function handle(array $userInfo = null, string $permissionName = ''): bool\n    {\n        return $userInfo['admin'];\n    }\n}"
  },
  {
    "path": "application/lib/authenticator/executor/impl/GroupRequireExecutorImpl.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/8/14\n * Time: 11:29 下午\n */\n\nnamespace app\\lib\\authenticator\\executor\\impl;\n\n\nuse app\\lib\\authenticator\\executor\\IExecutor;\n\nclass GroupRequireExecutorImpl implements IExecutor\n{\n\n    public function handle(array $userInfo = null, string $permissionName = ''): bool\n    {\n        if (empty($userInfo['permissions'])) return false;\n\n        $permissionArray = [];\n        foreach ($userInfo['permissions'] as $permissionGroup) {\n            foreach ($permissionGroup as $group) {\n                foreach ($group as $permission) {\n\n                    $permission = (array)$permission;\n                    $permissionTag = $permission['permission'] . '/' . $permission['module'];\n                    array_push($permissionArray, $permissionTag);\n                }\n            }\n        }\n        return in_array($permissionName, $permissionArray);\n    }\n}"
  },
  {
    "path": "application/lib/authenticator/executor/impl/LoginRequireExecutorImpl.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/8/14\n * Time: 11:29 下午\n */\n\nnamespace app\\lib\\authenticator\\executor\\impl;\n\n\nuse app\\api\\service\\token\\LoginToken;\nuse app\\lib\\authenticator\\executor\\IExecutor;\n\nclass LoginRequireExecutorImpl implements IExecutor\n{\n\n    public function handle(array $userInfo = null, string $permissionName = ''): bool\n    {\n        LoginToken::getInstance()->verify();\n        return true;\n    }\n}"
  },
  {
    "path": "application/lib/enum/GroupLevelEnum.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/4\n * Time: 12:02 上午\n */\n\nnamespace app\\lib\\enum;\n\n\nclass GroupLevelEnum\n{\n    const ROOT = 1;\n    const GUEST = 2;\n    const USER = 3;\n}"
  },
  {
    "path": "application/lib/enum/IdentityTypeEnum.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 10:45 下午\n */\n\nnamespace app\\lib\\enum;\n\n\nclass IdentityTypeEnum\n{\n    const PASSWORD = 'USERNAME_PASSWORD';\n}"
  },
  {
    "path": "application/lib/enum/MountTypeEnum.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/6\n * Time: 10:39 下午\n */\n\nnamespace app\\lib\\enum;\n\n\nclass MountTypeEnum\n{\n    const MOUNT = 1; // 挂载\n    const UNMOUNT = 0;\n}"
  },
  {
    "path": "application/lib/enum/PermissionLevelEnum.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/8/14\n * Time: 11:15 下午\n */\n\nnamespace app\\lib\\enum;\n\n\nclass PermissionLevelEnum\n{\n    const LOGIN_REQUIRED = 'loginRequired';\n\n    const GROUP_REQUIRED = 'groupRequired';\n\n    const ADMIN_REQUIRED = 'adminRequired';\n}"
  },
  {
    "path": "application/lib/exception/AuthFailedException.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/7\n * Time: 12:16 下午\n */\n\nnamespace app\\lib\\exception;\n\n\nuse LinCmsTp5\\exception\\BaseException;\n\nclass AuthFailedException extends BaseException\n{\n    public $code = 403;\n    public $msg = '用户身份认证失败';\n    public $error_code = 10021;\n\n}"
  },
  {
    "path": "application/lib/exception/NotFoundException.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 9:59 下午\n */\n\nnamespace app\\lib\\exception;\n\n\nuse LinCmsTp5\\exception\\BaseException;\n\nclass NotFoundException extends BaseException\n{\n    public $code = 404;\n    public $msg = '资源不存在';\n    public $error_code = 10021;\n}"
  },
  {
    "path": "application/lib/exception/OperationException.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/3\n * Time: 11:38 下午\n */\n\nnamespace app\\lib\\exception;\n\n\nuse LinCmsTp5\\exception\\BaseException;\n\nclass OperationException extends BaseException\n{\n    public $code = 400;\n    public $msg = '操作失败';\n    public $error_code = 10001;\n}"
  },
  {
    "path": "application/lib/exception/RepeatException.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/10/7\n * Time: 10:37 上午\n */\n\nnamespace app\\lib\\exception;\n\n\nuse LinCmsTp5\\exception\\BaseException;\n\nclass RepeatException extends BaseException\n{\n    public $code = 400;\n    public $msg = '资源已存在';\n    public $error_code = 10071;\n}"
  },
  {
    "path": "application/lib/exception/file/FileException.php",
    "content": "<?php\n/*\n* Created by DevilKing\n* Date: 2019-06-08\n*Time: 16:11\n*/\nnamespace app\\lib\\exception\\file;\n\nuse LinCmsTp5\\exception\\BaseException;\n\nclass FileException extends BaseException\n{\n    public $code = 413;\n    public $msg  = '文件体积过大';\n    public $error_code = '60000';\n}\n"
  },
  {
    "path": "application/lib/exception/token/DeployException.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n */\n\nnamespace app\\lib\\exception\\token;\n\n\nuse LinCmsTp5\\exception\\BaseException;\n\nclass DeployException extends BaseException\n{\n    public $code = 500;\n    public $msg  = '请修改php.ini配置：opcache.save_comments=1或直接注释掉此配置(无效请在 etc/php.d/ext-opcache.ini 文件中修改)';\n    public $error_code = 50000;\n}\n"
  },
  {
    "path": "application/lib/exception/token/ForbiddenException.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: daogu\n * Date: 2017/6/1\n * Time: 22:19\n */\n\nnamespace app\\lib\\exception\\token;\n\n\nuse LinCmsTp5\\exception\\BaseException;\n\n/**\n * Class ForbiddenException\n * @package app\\lib\\exception\\token\n */\nclass ForbiddenException extends BaseException\n{\n    public $code = 403;\n    public $msg = '权限不足，请联系管理员';\n    public $error_code = 10002;\n}"
  },
  {
    "path": "application/lib/exception/token/TokenException.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2019/4/30\n * Time: 16:22\n */\n\nnamespace app\\lib\\exception\\token;\n\n\nuse LinCmsTp5\\exception\\BaseException;\n\n/**\n * Class TokenException\n * @package app\\lib\\exception\\token\n */\nclass TokenException extends BaseException\n{\n    public $code = 401;\n    public $msg  = '令牌解析失败';\n    public $error_code = 10000;\n}"
  },
  {
    "path": "application/lib/file/LocalUploader.php",
    "content": "<?php\n/*\n* Created by DevilKing\n* Date: 2019-06-08\n*Time: 16:19\n*/\n\nnamespace app\\lib\\file;\n\nuse app\\api\\model\\admin\\LinFile;\nuse app\\lib\\exception\\file\\FileException;\nuse LinCmsTp\\File;\nuse think\\facade\\Config;\nuse think\\facade\\Env;\n\n/**\n * Class LocalUploader\n * @package app\\lib\\file\n */\nclass LocalUploader extends File\n{\n    /**\n     * @return array\n     * @throws FileException\n     */\n    public function upload()\n    {\n        $ret = [];\n        $host = Config::get('file.host') ?? \"http://127.0.0.1:5000\";\n        foreach ($this->files as $key => $file) {\n            $md5 = $this->generateMd5($file);\n            $exists = LinFile::get(['md5' => $md5]);\n            if ($exists) {\n                array_push($ret, [\n                    'id' => $exists['id'],\n                    'key' => $key,\n                    'path' => $exists['path'],\n                    'url' => $host . '/' . $this->storeDir . '/' . $exists['path']\n                ]);\n            } else {\n                $size = $this->getSize($file);\n                $info = $file->move(Env::get('root_path') . '/' . 'public' . '/' . $this->storeDir);\n                if ($info) {\n                    $extension = '.' . $info->getExtension();\n                    $path = str_replace('\\\\', '/', $info->getSaveName());\n                    $name = $info->getFilename();\n                } else {\n                    throw new FileException([\n                        'msg' => \"存储本地文件失败\",\n                        'error_code' => 60001\n                    ]);\n                }\n                $linFile = LinFile::create([\n                    'name' => $name,\n                    'path' => $path,\n                    'size' => $size,\n                    'extension' => $extension,\n                    'md5' => $md5,\n                    'type' => 1\n                ]);\n                array_push($ret, [\n                    'id' => $linFile->id,\n                    'key' => $key,\n                    'path' => $path,\n                    'url' => $host . '/' . $this->storeDir . '/' . $path\n                ]);\n\n            }\n\n        }\n        return $ret;\n    }\n}\n"
  },
  {
    "path": "application/provider.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// 应用容器绑定定义\nreturn [\n];\n"
  },
  {
    "path": "application/tags.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// 应用行为扩展定义文件\nreturn [\n    // 应用初始化\n    'app_init' => [\n    ],\n    // 应用开始\n    'app_begin' => [],\n    // 模块初始化\n    'module_init' => [],\n    // 操作开始执行\n    'action_begin' => [],\n    // 视图内容过滤\n    'view_filter' => [],\n    // 日志写入\n    'log_write' => [],\n    // 应用结束\n    'app_end' => [],\n    // api日志\n    'logger' => [\n        'app\\\\api\\\\behavior\\\\Logger',\n    ]\n];\n"
  },
  {
    "path": "build.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\nreturn [\n    // 生成应用公共文件\n    '__file__' => ['common.php'],\n\n    // 定义demo模块的自动生成 （按照实际定义的文件名生成）\n    'demo'     => [\n        '__file__'   => ['common.php'],\n        '__dir__'    => ['behavior', 'controller', 'model', 'view'],\n        'controller' => ['Index', 'Test', 'UserType'],\n        'model'      => ['User', 'UserType'],\n        'view'       => ['index/index'],\n    ],\n\n    // 其他更多的模块定义\n];\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"topthink/think\",\n  \"description\": \"the new thinkphp framework\",\n  \"type\": \"project\",\n  \"keywords\": [\n    \"framework\",\n    \"thinkphp\",\n    \"ORM\"\n  ],\n  \"homepage\": \"http://thinkphp.cn/\",\n  \"license\": \"Apache-2.0\",\n  \"authors\": [\n    {\n      \"name\": \"liu21st\",\n      \"email\": \"liu21st@gmail.com\"\n    }\n  ],\n  \"require\": {\n    \"php\": \">=7.1.0\",\n    \"topthink/framework\": \"5.1.36\",\n    \"topthink/think-migration\": \"2.*\",\n    \"lin-cms-tp5/base-core\": \"dev-master\",\n    \"lin-cms-tp/validate-core\": \"dev-master\",\n    \"lin-cms-tp/utils-core\": \"dev-master\",\n    \"qinchen/web-utils\": \">0.0.1\"\n  },\n  \"autoload\": {\n    \"psr-4\": {\n      \"app\\\\\": \"application\"\n    }\n  },\n  \"extra\": {\n    \"think-path\": \"thinkphp\"\n  },\n  \"config\": {\n    \"preferred-install\": \"dist\"\n  },\n  \"repositories\": {\n  }\n}\n"
  },
  {
    "path": "config/app.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | 应用设置\n// +----------------------------------------------------------------------\n\nreturn [\n    // 应用名称\n    'app_name'               => '',\n    // 应用地址\n    'app_host'               => '',\n    // 应用调试模式\n    'app_debug'              => true,\n    // 应用Trace\n    'app_trace'              => false,\n    // 是否支持多模块\n    'app_multi_module'       => true,\n    // 入口自动绑定模块\n    'auto_bind_module'       => false,\n    // 注册的根命名空间\n    'root_namespace'         => [],\n    // 默认输出类型\n    'default_return_type'    => 'json',\n    // 默认AJAX 数据返回格式,可选json xml ...\n    'default_ajax_return'    => 'json',\n    // 默认JSONP格式返回的处理方法\n    'default_jsonp_handler'  => 'jsonpReturn',\n    // 默认JSONP处理方法\n    'var_jsonp_handler'      => 'callback',\n    // 默认时区\n    'default_timezone'       => 'Asia/Shanghai',\n    // 是否开启多语言\n    'lang_switch_on'         => false,\n    // 默认全局过滤方法 用逗号分隔多个\n    'default_filter'         => '',\n    // 默认语言\n    'default_lang'           => 'zh-cn',\n    // 应用类库后缀\n    'class_suffix'           => false,\n    // 控制器类后缀\n    'controller_suffix'      => false,\n\n    // +----------------------------------------------------------------------\n    // | 模块设置\n    // +----------------------------------------------------------------------\n\n    // 默认模块名\n    'default_module'         => 'index',\n    // 禁止访问模块\n    'deny_module_list'       => ['common'],\n    // 默认控制器名\n    'default_controller'     => 'Index',\n    // 默认操作名\n    'default_action'         => 'index',\n    // 默认验证器\n    'default_validate'       => '',\n    // 默认的空模块名\n    'empty_module'           => '',\n    // 默认的空控制器名\n    'empty_controller'       => 'Error',\n    // 操作方法前缀\n    'use_action_prefix'      => false,\n    // 操作方法后缀\n    'action_suffix'          => '',\n    // 自动搜索控制器\n    'controller_auto_search' => false,\n\n    // +----------------------------------------------------------------------\n    // | URL设置\n    // +----------------------------------------------------------------------\n\n    // PATHINFO变量名 用于兼容模式\n    'var_pathinfo'           => 's',\n    // 兼容PATH_INFO获取\n    'pathinfo_fetch'         => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],\n    // pathinfo分隔符\n    'pathinfo_depr'          => '/',\n    // HTTPS代理标识\n    'https_agent_name'       => '',\n    // IP代理获取标识\n    'http_agent_ip'          => 'X-REAL-IP',\n    // URL伪静态后缀\n    'url_html_suffix'        => 'html',\n    // URL普通方式参数 用于自动生成\n    'url_common_param'       => false,\n    // URL参数方式 0 按名称成对解析 1 按顺序解析\n    'url_param_type'         => 0,\n    // 是否开启路由延迟解析\n    'url_lazy_route'         => false,\n    // 是否强制使用路由\n    'url_route_must'         => false,\n    // 合并路由规则\n    'route_rule_merge'       => false,\n    // 路由是否完全匹配\n    'route_complete_match'   => true,\n    // 使用注解路由\n    'route_annotation'       => false,\n    // 域名根，如thinkphp.cn\n    'url_domain_root'        => '',\n    // 是否自动转换URL中的控制器和操作名\n    'url_convert'            => true,\n    // 默认的访问控制器层\n    'url_controller_layer'   => 'controller',\n    // 表单请求类型伪装变量\n    'var_method'             => '_method',\n    // 表单ajax伪装变量\n    'var_ajax'               => '_ajax',\n    // 表单pjax伪装变量\n    'var_pjax'               => '_pjax',\n    // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则\n    'request_cache'          => false,\n    // 请求缓存有效期\n    'request_cache_expire'   => null,\n    // 全局请求缓存排除规则\n    'request_cache_except'   => [],\n    // 是否开启路由缓存\n    'route_check_cache'      => false,\n    // 路由缓存的Key自定义设置（闭包），默认为当前URL和请求类型的md5\n    'route_check_cache_key'  => '',\n    // 路由缓存类型及参数\n    'route_cache_option'     => [],\n\n    // 默认跳转页面对应的模板文件\n    'dispatch_success_tmpl'  => Env::get('think_path') . 'tpl/dispatch_jump.tpl',\n    'dispatch_error_tmpl'    => Env::get('think_path') . 'tpl/dispatch_jump.tpl',\n\n    // 异常页面的模板文件\n    'exception_tmpl'         => Env::get('think_path') . 'tpl/think_exception.tpl',\n\n    // 错误显示信息,非调试模式有效\n    'error_message'          => '页面错误！请稍后再试～',\n    // 显示错误信息\n    'show_error_msg'         => false,\n    // 异常处理handle类 留空使用 \\think\\exception\\Handle\n    'exception_handle'       => 'LinCmsTp5\\exception\\ExceptionHandler',\n\n];\n"
  },
  {
    "path": "config/cache.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | 缓存设置\n// +----------------------------------------------------------------------\n\nreturn [\n    // 驱动方式\n    'type'   => 'File',\n    // 缓存保存目录\n    'path'   => '',\n    // 缓存前缀\n    'prefix' => '',\n    // 缓存有效期 0表示永久缓存\n    'expire' => 0,\n];\n"
  },
  {
    "path": "config/console.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | 控制台配置\n// +----------------------------------------------------------------------\nreturn [\n    'name'      => 'Think Console',\n    'version'   => '0.1',\n    'user'      => null,\n    'auto_path' => env('app_path') . 'command' . DIRECTORY_SEPARATOR,\n];\n"
  },
  {
    "path": "config/cookie.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | Cookie设置\n// +----------------------------------------------------------------------\nreturn [\n    // cookie 名称前缀\n    'prefix'    => '',\n    // cookie 保存时间\n    'expire'    => 0,\n    // cookie 保存路径\n    'path'      => '/',\n    // cookie 有效域名\n    'domain'    => '',\n    //  cookie 启用安全传输\n    'secure'    => false,\n    // httponly设置\n    'httponly'  => '',\n    // 是否使用 setcookie\n    'setcookie' => true,\n];\n"
  },
  {
    "path": "config/database.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\nreturn [\n    // 数据库类型\n    'type'            => 'mysql',\n    // 服务器地址\n    'hostname'        => '127.0.0.1',\n    // 数据库名\n    'database'        => 'lin_cms',\n    // 用户名\n    'username'        => 'root',\n    // 密码\n    'password'        => '',\n    // 端口\n    'hostport'        => '',\n    // 连接dsn\n    'dsn'             => '',\n    // 数据库连接参数\n    'params'          => [],\n    // 数据库编码默认采用utf8\n    'charset'         => 'utf8',\n    // 数据库表前缀\n    'prefix'          => '',\n    // 数据库调试模式\n    'debug'           => true,\n    // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)\n    'deploy'          => 0,\n    // 数据库读写是否分离 主从式有效\n    'rw_separate'     => false,\n    // 读写分离后 主服务器数量\n    'master_num'      => 1,\n    // 指定从服务器序号\n    'slave_no'        => '',\n    // 自动读取主库数据\n    'read_master'     => false,\n    // 是否严格检查字段是否存在\n    'fields_strict'   => true,\n    // 数据集返回类型\n    'resultset_type'  => 'collection',\n    // 自动写入时间戳字段\n    'auto_timestamp'  => false,\n    // 时间字段取出后的默认时间格式\n    'datetime_format' => 'Y-m-d H:i:s',\n    // 是否需要进行SQL性能分析\n    'sql_explain'     => false,\n    // Builder类\n    'builder'         => '',\n    // Query类\n    'query'           => '\\\\think\\\\db\\\\Query',\n    // 是否需要断线重连\n    'break_reconnect' => false,\n    // 断线标识字符串\n    'break_match_str' => []\n];\n"
  },
  {
    "path": "config/file.php",
    "content": "<?php\n\nreturn [\n    \"store_dir\" => 'uploads',       # 文件的存储路径\n    \"single_limit\" => 1024 * 1024 * 2, # 单个文件的大小限制，默认2M\n    \"total_limit\"=> 1024 * 1024 * 20, # 所有文件的大小限制，默认20M\n    \"nums\" => 10,                      # 文件数量限制，默认10\n    \"include\" => [],                   # 文件后缀名的排除项，默认排除[]，即允许所有类型的文件上传\n    \"exclude\" => []                   # 文件后缀名的包括项\n];\n"
  },
  {
    "path": "config/log.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | 日志设置\n// +----------------------------------------------------------------------\nreturn [\n    // 日志记录方式，内置 file socket 支持扩展\n    'type'        => 'File',\n    // 日志保存目录\n    'path'        => '',\n    // 日志记录级别\n    'level'       => [],\n    // 单文件日志写入\n    'single'      => false,\n    // 独立日志级别\n    'apart_level' => [],\n    // 最大日志文件数量\n    'max_files'   => 0,\n    // 是否关闭日志写入\n    'close'       => true,\n\n];\n"
  },
  {
    "path": "config/middleware.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | 中间件配置\n// +----------------------------------------------------------------------\nreturn [\n    // 默认中间件命名空间\n    'default_namespace' => 'app\\\\http\\\\middleware\\\\',\n    'ReflexValidate' => LinCmsTp\\Param::class  // 开启注释验证器，需要的中间件配置，请勿胡乱关闭\n];\n"
  },
  {
    "path": "config/secure.php",
    "content": "<?php\n/**\n * Created by PhpStorm.\n * User: 沁塵\n * Date: 2017/5/26\n * Time: 23:12\n */\nreturn [\n    'access_token_salt'=>'SDJxjkxc9o',\n    'refresh_token_salt' => 'SKTxigxc9o',\n];"
  },
  {
    "path": "config/session.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | 会话设置\n// +----------------------------------------------------------------------\n\nreturn [\n    'id'             => '',\n    // SESSION_ID的提交变量,解决flash上传跨域\n    'var_session_id' => '',\n    // SESSION 前缀\n    'prefix'         => 'think',\n    // 驱动方式 支持redis memcache memcached\n    'type'           => '',\n    // 是否自动开启 SESSION\n    'auto_start'     => true,\n];\n"
  },
  {
    "path": "config/template.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | 模板设置\n// +----------------------------------------------------------------------\n\nreturn [\n    // 模板引擎类型 支持 php think 支持扩展\n    'type'         => 'Think',\n    // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法\n    'auto_rule'    => 1,\n    // 模板路径\n    'view_path'    => '',\n    // 模板后缀\n    'view_suffix'  => 'html',\n    // 模板文件名分隔符\n    'view_depr'    => DIRECTORY_SEPARATOR,\n    // 模板引擎普通标签开始标记\n    'tpl_begin'    => '{',\n    // 模板引擎普通标签结束标记\n    'tpl_end'      => '}',\n    // 标签库标签开始标记\n    'taglib_begin' => '{',\n    // 标签库标签结束标记\n    'taglib_end'   => '}',\n];\n"
  },
  {
    "path": "config/token.php",
    "content": "<?php\n/**\n * Created by PhpStorm\n * Author: 沁塵\n * Date: 2020/9/5\n * Time: 6:31 下午\n */\nreturn [\n    # 是否开启双令牌模式，本项目必须\n    'enable_dual_token' => true,\n    # 令牌算法类型\n    'algorithms' => 'HS256',\n    # 令牌签发者\n    'issuer' => 'lin-cms-tp5',\n    # accessToken秘钥\n    'access_secret_key' => 'w.kx(c82jkA',\n    # accessToken过期时间，单位秒\n    'access_expire_time' => 7200,\n    # refreshToken秘钥\n    'refresh_secret_key' => 'xUh.@3s8A8',\n    # refreshToken过期时间，建议设置较长时间\n    # 在有效期内可用于刷新accessToken，单位秒\n    'refresh_expire_time' => 604800,\n];"
  },
  {
    "path": "config/trace.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// +----------------------------------------------------------------------\n// | Trace设置 开启 app_trace 后 有效\n// +----------------------------------------------------------------------\nreturn [\n    // 内置Html Console 支持扩展\n    'type' => 'Html',\n];\n"
  },
  {
    "path": "error_msg.md",
    "content": "# 错误码字典\n\n## 参数类错误\n99999 参数校验不通过\n\n## 通用错误\n10001 资源操作失败\n10021 资源不存在\n10071 资源已存在\n\n## 令牌类错误\n10000 令牌解析失败\n10041 access令牌过期\n10042 refresh令牌过期\n10002 权限不足\n10073 不允许添加用户到root分组\n10078 不允许调整root分组信息\n\n## 用户类错误\n\n10030 密码错误\n\n## 分组类错误\n### 3000x\n30001 分组创建失败\n30002 分组权限编辑失败\n30003 分组不存在\n30004 分组已存在\n\n## 服务端类错误\n### 5000x\n50000 服务环境配置错误\n\n## 上传类错误\n### 6000x\n60000 不符合上传条件\n60001 上传文件失败\n"
  },
  {
    "path": "extend/.gitignore",
    "content": "*\n!.gitignore"
  },
  {
    "path": "public/.htaccess",
    "content": "<IfModule mod_rewrite.c>\n  Options +FollowSymlinks -Multiviews\n  RewriteEngine On\n\n  RewriteCond %{REQUEST_FILENAME} !-d\n  RewriteCond %{REQUEST_FILENAME} !-f\n  RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]\n</IfModule>\n"
  },
  {
    "path": "public/index.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\n// [ 应用入口文件 ]\nnamespace think;\n\n// 加载基础文件\nrequire __DIR__ . '/../thinkphp/base.php';\n\n// 支持事先使用静态方法设置Request对象和Config对象\n\n// 执行应用并响应\nContainer::get('app')->run()->send();\n"
  },
  {
    "path": "public/robots.txt",
    "content": "User-agent: *\nDisallow:\n"
  },
  {
    "path": "public/router.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n// $Id$\n\nif (is_file($_SERVER[\"DOCUMENT_ROOT\"] . $_SERVER[\"SCRIPT_NAME\"])) {\n    return false;\n} else {\n    require __DIR__ . \"/index.php\";\n}\n"
  },
  {
    "path": "public/static/.gitignore",
    "content": "*\n!.gitignore"
  },
  {
    "path": "route/route.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: liu21st <liu21st@gmail.com>\n// +----------------------------------------------------------------------\n\nuse think\\facade\\Route;\n\nRoute::get('v1/test', 'api/v1.Test/index');\n\nRoute::group('', function () {\n    Route::group('cms', function () {\n        // 账户相关接口分组\n        Route::group('user', function () {\n            // 登陆接口\n            Route::post('login', 'api/cms.User/userLogin');\n            // 刷新令牌\n            Route::get('refresh', 'api/cms.User/refreshToken');\n            // 查询自己拥有的权限\n            Route::get('permissions', 'api/cms.User/getAllowedApis');\n            // 注册一个用户\n            Route::post('register', 'api/cms.User/register');\n            // 查询自己信息\n            Route::get('information', 'api/cms.User/getInformation');\n            // 用户更新信息\n            Route::put('', 'api/cms.User/update');\n            // 修改自己密码\n            Route::put('change_password', 'api/cms.User/changePassword');\n        });\n        // 管理类接口\n        Route::group('admin', function () {\n            // 查询所有可分配的权限\n            Route::get('permission', 'api/cms.Admin/getAllPermissions');\n            // 查询所有用户\n            Route::get('users', 'api/cms.Admin/getAdminUsers');\n            // 修改用户密码\n            Route::put('user/:id/password', 'api/cms.Admin/changeUserPassword');\n            // 删除用户\n            Route::delete('user/:id', 'api/cms.Admin/deleteUser');\n            // 更新用户信息\n            Route::put('user/:id', 'api/cms.Admin/updateUser');\n            // 查询所有权限组\n            Route::get('group/all', 'api/cms.Admin/getGroupAll');\n            // 新增权限组\n            Route::post('group', 'api/cms.Admin/createGroup');\n            // 查询指定分组及其权限\n            Route::get('group/:id', 'api/cms.Admin/getGroup');\n            // 更新一个权限组\n            Route::put('group/:id', 'api/cms.Admin/updateGroup');\n            // 删除一个分组\n            Route::delete('group/:id', 'api/cms.Admin/deleteGroup');\n            // 删除多个权限\n            Route::post('permission/remove', 'api/cms.Admin/removePermissions');\n            // 分配多个权限\n            Route::post('permission/dispatch/batch', 'api/cms.Admin/dispatchPermissions');\n\n        });\n        // 日志类接口\n        Route::group('log', function () {\n            Route::get('', 'api/cms.Log/getLogs');\n            Route::get('users', 'api/cms.Log/getUsers');\n            Route::get('search', 'api/cms.Log/getUserLogs');\n        });\n        //上传文件类接口\n        Route::post('file', 'api/cms.File/postFile');\n    });\n    Route::group('v1', function () {\n        Route::group('book', function () {\n            // 查询所有图书\n            Route::get('', 'api/v1.Book/getBooks');\n            // 新建图书\n            Route::post('', 'api/v1.Book/create');\n            // 查询指定bid的图书\n            Route::get(':bid', 'api/v1.Book/getBook');\n            // 搜索图书\n\n            // 更新图书\n            Route::put(':bid', 'api/v1.Book/update');\n            // 删除图书\n            Route::delete(':bid', 'api/v1.Book/delete');\n        });\n\n    });\n})->middleware(['Authentication', 'ReflexValidate'])->allowCrossDomain(true, $header = [\n    'Access-Control-Allow-Credentials' => 'true',\n    'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE',\n    'Access-Control-Allow-Headers' => 'tag, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With',\n]);\n\n"
  },
  {
    "path": "runtime/.gitignore",
    "content": "*\n!.gitignore"
  },
  {
    "path": "schema.sql",
    "content": "SET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- 文件表\n-- ----------------------------\nDROP TABLE IF EXISTS lin_file;\nCREATE TABLE lin_file\n(\n    id          int(10) unsigned NOT NULL AUTO_INCREMENT,\n    path        varchar(500)     NOT NULL,\n    type        varchar(10)      NOT NULL DEFAULT 'LOCAL' COMMENT 'LOCAL 本地，REMOTE 远程',\n    name        varchar(100)     NOT NULL,\n    extension   varchar(50)               DEFAULT NULL,\n    size        int(11)                   DEFAULT NULL,\n    md5         varchar(40)               DEFAULT NULL COMMENT 'md5值，防止上传重复文件',\n    create_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n    update_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),\n    delete_time datetime(3)               DEFAULT NULL,\n    PRIMARY KEY (id),\n    UNIQUE KEY md5_del (md5, delete_time)\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\n-- ----------------------------\n-- 日志表\n-- ----------------------------\nDROP TABLE IF EXISTS lin_log;\nCREATE TABLE lin_log\n(\n    id          int(10) unsigned NOT NULL AUTO_INCREMENT,\n    message     varchar(450)              DEFAULT NULL,\n    user_id     int(10) unsigned NOT NULL,\n    username    varchar(24)               DEFAULT NULL,\n    status_code int(11)                   DEFAULT NULL,\n    method      varchar(20)               DEFAULT NULL,\n    path        varchar(50)               DEFAULT NULL,\n    permission  varchar(100)              DEFAULT NULL,\n    create_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n    update_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),\n    delete_time datetime(3)               DEFAULT NULL,\n    PRIMARY KEY (id)\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\n-- ----------------------------\n-- 权限表\n-- ----------------------------\nDROP TABLE IF EXISTS lin_permission;\nCREATE TABLE lin_permission\n(\n    id          int(10) unsigned NOT NULL AUTO_INCREMENT,\n    name        varchar(60)      NOT NULL COMMENT '权限名称，例如：访问首页',\n    module      varchar(50)      NOT NULL COMMENT '权限所属模块，例如：人员管理',\n    mount       tinyint(1)       NOT NULL DEFAULT 1 COMMENT '0：关闭 1：开启',\n    create_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n    update_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),\n    delete_time datetime(3)               DEFAULT NULL,\n    PRIMARY KEY (id)\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\n-- ----------------------------\n-- 分组表\n-- ----------------------------\nDROP TABLE IF EXISTS lin_group;\nCREATE TABLE lin_group\n(\n    id          int(10) unsigned NOT NULL AUTO_INCREMENT,\n    name        varchar(60)      NOT NULL COMMENT '分组名称，例如：搬砖者',\n    info        varchar(255)              DEFAULT NULL COMMENT '分组信息：例如：搬砖的人',\n    level       tinyint(2)       NOT NULL DEFAULT 3 COMMENT '分组级别 1：root 2：guest 3：user（root、guest分组只能存在一个)',\n    create_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n    update_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),\n    delete_time datetime(3)               DEFAULT NULL,\n    PRIMARY KEY (id),\n    UNIQUE KEY name_del (name, delete_time)\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\n-- ----------------------------\n-- 分组-权限表\n-- ----------------------------\nDROP TABLE IF EXISTS lin_group_permission;\nCREATE TABLE lin_group_permission\n(\n    id            int(10) unsigned NOT NULL AUTO_INCREMENT,\n    group_id      int(10) unsigned NOT NULL COMMENT '分组id',\n    permission_id int(10) unsigned NOT NULL COMMENT '权限id',\n    PRIMARY KEY (id),\n    KEY group_id_permission_id (group_id, permission_id) USING BTREE COMMENT '联合索引'\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\n-- ----------------------------\n-- 用户基本信息表\n-- ----------------------------\nDROP TABLE IF EXISTS lin_user;\nCREATE TABLE lin_user\n(\n    id          int(10) unsigned NOT NULL AUTO_INCREMENT,\n    username    varchar(24)      NOT NULL COMMENT '用户名，唯一',\n    nickname    varchar(24)               DEFAULT NULL COMMENT '用户昵称',\n    avatar      varchar(500)              DEFAULT NULL COMMENT '头像url',\n    email       varchar(100)              DEFAULT NULL COMMENT '邮箱',\n    create_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n    update_time datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),\n    delete_time datetime(3)               DEFAULT NULL,\n    PRIMARY KEY (id),\n    UNIQUE KEY username_del (username, delete_time),\n    UNIQUE KEY email_del (email, delete_time)\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\n-- ----------------------------\n-- 用户授权信息表\n# id\n# user_id\n# identity_type 登录类型（手机号 邮箱 用户名）或第三方应用名称（微信 微博等）\n# identifier 标识（手机号 邮箱 用户名或第三方应用的唯一标识）\n# credential 密码凭证（站内的保存密码，站外的不保存或保存token）\n-- ----------------------------\nDROP TABLE IF EXISTS lin_user_identity;\nCREATE TABLE lin_user_identity\n(\n    id            int(10) unsigned NOT NULL AUTO_INCREMENT,\n    user_id       int(10) unsigned NOT NULL COMMENT '用户id',\n    identity_type varchar(100)     NOT NULL,\n    identifier    varchar(100),\n    credential    varchar(100),\n    create_time   datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n    update_time   datetime(3)      NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),\n    delete_time   datetime(3)               DEFAULT NULL,\n    PRIMARY KEY (id)\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\nDROP TABLE IF EXISTS book;\nCREATE TABLE book\n(\n    id          int(11)     NOT NULL AUTO_INCREMENT,\n    title       varchar(50) NOT NULL,\n    author      varchar(30)          DEFAULT NULL,\n    summary     varchar(1000)        DEFAULT NULL,\n    image       varchar(100)         DEFAULT NULL,\n    create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n    update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),\n    delete_time datetime(3)          DEFAULT NULL,\n    PRIMARY KEY (id)\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\n\n-- ----------------------------\n-- 用户-分组表\n-- ----------------------------\nDROP TABLE IF EXISTS lin_user_group;\nCREATE TABLE lin_user_group\n(\n    id       int(10) unsigned NOT NULL AUTO_INCREMENT,\n    user_id  int(10) unsigned NOT NULL COMMENT '用户id',\n    group_id int(10) unsigned NOT NULL COMMENT '分组id',\n    PRIMARY KEY (id),\n    KEY user_id_group_id (user_id, group_id) USING BTREE COMMENT '联合索引'\n) ENGINE = InnoDB\n  DEFAULT CHARSET = utf8mb4\n  COLLATE = utf8mb4_general_ci;\n\nSET FOREIGN_KEY_CHECKS = 1;\n\n-- ----------------------------\n-- 插入超级管理员\n-- 插入root分组\n-- ----------------------------\nBEGIN;\nINSERT INTO lin_user(id, username, nickname)\nVALUES (1, 'root', 'root');\n\nINSERT INTO lin_user_identity (id, user_id, identity_type, identifier, credential)\n\nVALUES (1, 1, 'USERNAME_PASSWORD', 'root',\n        'e10adc3949ba59abbe56e057f20f883e');\n\nINSERT INTO lin_group(id, name, info, level)\nVALUES (1, 'root', '超级用户组', 1);\n\nINSERT INTO lin_group(id, name, info, level)\nVALUES (2, 'guest', '游客组', 2);\n\nINSERT INTO lin_user_group(id, user_id, group_id)\nVALUES (1, 1, 1);\n\nCOMMIT;\n"
  },
  {
    "path": "think",
    "content": "#!/usr/bin/env php\n<?php\n// +----------------------------------------------------------------------\n// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n// +----------------------------------------------------------------------\n// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.\n// +----------------------------------------------------------------------\n// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n// +----------------------------------------------------------------------\n// | Author: yunwuxin <448901948@qq.com>\n// +----------------------------------------------------------------------\n\nnamespace think;\n\n// 加载基础文件\nrequire __DIR__ . '/thinkphp/base.php';\n\n// 应用初始化\nContainer::get('app')->path(__DIR__ . '/application/')->initialize();\n\n// 控制台初始化\nConsole::init();"
  }
]