├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── application ├── .htaccess ├── api │ ├── behavior │ │ └── Logger.php │ ├── controller │ │ ├── cms │ │ │ ├── Admin.php │ │ │ ├── File.php │ │ │ ├── Log.php │ │ │ └── User.php │ │ └── v1 │ │ │ └── Book.php │ ├── model │ │ ├── BaseModel.php │ │ ├── Book.php │ │ └── admin │ │ │ ├── LinFile.php │ │ │ ├── LinGroup.php │ │ │ ├── LinGroupPermission.php │ │ │ ├── LinLog.php │ │ │ ├── LinPermission.php │ │ │ ├── LinUser.php │ │ │ ├── LinUserGroup.php │ │ │ └── LinUserIdentity.php │ ├── service │ │ ├── admin │ │ │ ├── Admin.php │ │ │ ├── Log.php │ │ │ └── User.php │ │ └── token │ │ │ └── LoginToken.php │ └── validate │ │ └── user │ │ ├── ChangePasswordForm.php │ │ ├── LoginForm.php │ │ ├── RegisterForm.php │ │ ├── ResetPasswordValidator.php │ │ └── UpdateUserForm.php ├── command.php ├── common.php ├── http │ └── middleware │ │ └── Authentication.php ├── index │ └── controller │ │ └── Index.php ├── lib │ ├── authenticator │ │ ├── Authenticator.php │ │ ├── AuthenticatorExecutorFactory.php │ │ ├── PermissionScan.php │ │ ├── Scan.php │ │ └── executor │ │ │ ├── IExecutor.php │ │ │ └── impl │ │ │ ├── AdminRequireExecutorImpl.php │ │ │ ├── GroupRequireExecutorImpl.php │ │ │ └── LoginRequireExecutorImpl.php │ ├── enum │ │ ├── GroupLevelEnum.php │ │ ├── IdentityTypeEnum.php │ │ ├── MountTypeEnum.php │ │ └── PermissionLevelEnum.php │ ├── exception │ │ ├── AuthFailedException.php │ │ ├── NotFoundException.php │ │ ├── OperationException.php │ │ ├── RepeatException.php │ │ ├── file │ │ │ └── FileException.php │ │ └── token │ │ │ ├── DeployException.php │ │ │ ├── ForbiddenException.php │ │ │ └── TokenException.php │ └── file │ │ └── LocalUploader.php ├── provider.php └── tags.php ├── build.php ├── composer.json ├── config ├── app.php ├── cache.php ├── console.php ├── cookie.php ├── database.php ├── file.php ├── log.php ├── middleware.php ├── secure.php ├── session.php ├── template.php ├── token.php └── trace.php ├── error_msg.md ├── extend └── .gitignore ├── public ├── .htaccess ├── favicon.ico ├── index.php ├── robots.txt ├── router.php └── static │ └── .gitignore ├── route └── route.php ├── runtime └── .gitignore ├── schema.sql └── think /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | /vendor 4 | *.log 5 | thinkphp 6 | .env 7 | .DS_Store 8 | composer.lock 9 | /public/uploads 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | branches: 6 | only: 7 | - stable 8 | 9 | cache: 10 | directories: 11 | - $HOME/.composer/cache 12 | 13 | before_install: 14 | - composer self-update 15 | 16 | install: 17 | - composer install --no-dev --no-interaction --ignore-platform-reqs 18 | - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . 19 | - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" 20 | - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" 21 | - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" 22 | - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" 23 | - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" 24 | - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" 25 | - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" 26 | - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" 27 | - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" 28 | - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . 29 | 30 | script: 31 | - php think unit 32 | 33 | deploy: 34 | provider: releases 35 | api_key: 36 | secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= 37 | file: 38 | - ThinkPHP_Core.zip 39 | - ThinkPHP_Full.zip 40 | skip_cleanup: true 41 | on: 42 | tags: true 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 2 | 版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) 3 | All rights reserved。 4 | ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 5 | 6 | Apache Licence是著名的非盈利开源组织Apache采用的协议。 7 | 该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, 8 | 允许代码修改,再作为开源或商业软件发布。需要满足 9 | 的条件: 10 | 1. 需要给代码的用户一份Apache Licence ; 11 | 2. 如果你修改了代码,需要在被修改的文件中说明; 12 | 3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 13 | 带有原来代码中的协议,商标,专利声明和其他原来作者规 14 | 定需要包含的说明; 15 | 4. 如果再发布的产品中包含一个Notice文件,则在Notice文 16 | 件中需要带有本协议内容。你可以在Notice中增加自己的 17 | 许可,但不可以表现为对Apache Licence构成更改。 18 | 具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |
5 | Lin-CMS-TP5 6 |

7 | 8 |

9 | php version 10 | ThinkPHP version 11 | LISENCE 12 |

13 | 14 | # 简介 15 | 16 | ## 预防针 17 | 18 | * 本项目非官方团队出品,仅出于学习、研究目的丰富下官方项目的语言支持,目前已被收录至官方团队仓库,[点击查看](https://github.com/TaleLin) 19 | * 本项目采取后跟进官方团队功能的形式,即官方团队出什么功能,这边就跟进开发什么功能,开发者不必担心前端适配问题。 20 | * 在上一点的基础上,我们会尝试加入一些自己的想法并实现。 21 | * 局限于本人水平,有些地方还需重构,已经纳入了计划中,当然也会有我没考虑到的,希望有更多人参与进来一起完善,毕竟PHP作为世界上最好的语言不能缺席。 22 | 23 | ## 专栏教程 24 | 25 | * [《Lin CMS PHP&Vue教程》](https://course.7yue.pro/lin/lin-cms-php/)专栏教程连载更新中,通过实战开源前后端分离CMS——Lin CMS全家桶(lin-cms-vue & lin-cms-tp5)为一个前端应用实现内容管理系统。一套教程入门上手vue、ThinkPHP两大框架,自用、工作、私单一次打通。 26 | 27 | * 读者反馈:[《Lin CMS PHP&Vue教程》读者反馈贴](https://github.com/ChenJinchuang/lin-cms-tp5/issues/47) 28 | 29 | ## 线上文档地址(完善中) 30 | 31 | [http://chenjinchuang.gitee.io/lin-cms-book/](http://chenjinchuang.gitee.io/lin-cms-book/) 32 | 33 | ## 线上 Demo 34 | 35 | 可直接参考官方团队的线上Demo:[http://face.cms.7yue.pro/](http://face.cms.7yue.pro/),用户名:super,密码:123456 36 | 37 | ## 什么是 Lin CMS? 38 | 39 | > Lin-CMS 是林间有风团队经过大量项目实践所提炼出的一套**内容管理系统框架**。Lin-CMS 可以有效的帮助开发者提高 CMS 的开发效率。 40 | 41 | 本项目是基于ThinkPHP 5.1的 Lin CMS 后端实现。 42 | 43 | 官方团队产品了解请访问[TaleLin](https://github.com/TaleLin) 44 | 45 | ## Lin CMS 的特点 46 | 47 | Lin CMS 的构筑思想是有其自身特点的。下面我们阐述一些 Lin 的主要特点。 48 | 49 | **Lin CMS 是一个前后端分离的 CMS 解决方案** 50 | 51 | 这意味着,Lin 既提供后台的支撑,也有一套对应的前端系统,当然双端分离的好处不仅仅在于此,我们会在后续提供NodeJS和PHP版本的 Lin。如果你心仪 Lin,却又因为技术栈的原因无法即可使用,没关系,我们会在后续提供更多的语言版本。为什么 Lin 要选择前后端分离的单页面架构呢? 52 | 53 | 首先,传统的网站开发更多的是采用服务端渲染的方式,需用使用一种模板语言在服务端完成页面渲染:比如 JinJa2、Jade 等。 服务端渲染的好处在于可以比较好的支持 SEO,但作为内部使用的 CMS 管理系统,SEO 并不重要。 54 | 55 | 但一个不可忽视的事实是,服务器渲染的页面到底是由前端开发者来完成,还是由服务器开发者来完成?其实都不太合适。现在已经没有多少前端开发者是了解这些服务端模板语言的,而服务器开发者本身是不太擅长开发页面的。那还是分开吧,前端用最熟悉的 Vue 写 JS 和 CSS,而服务器只关注自己的 API 即可。 56 | 57 | 其次,单页面应用程序的体验本身就要好于传统网站。 58 | 59 | 更多关于Lin CMS的介绍请访问[Lin CMS线上文档](http://doc.cms.7yue.pro/) 60 | 61 | **框架本身已内置了 CMS 常用的功能** 62 | 63 | Lin 已经内置了 CMS 中最为常见的需求:用户管理、权限管理、日志系统等。开发者只需要集中精力开发自己的 CMS 业务即可 64 | 65 | ## Lin CMS TP5 的特点 66 | 67 | 在当前项目的版本`(0.0.1)`中,特点更多来自于`ThinkPHP 5.1`框架本身带来的特点。通过充分利用框架的特性,实现高效的后端使用、开发,也就是说,只要你熟悉`ThinkPHP`框架,那么对于理解使用和二次开发本项目是没有难度的,即便对于框架的某些功能存在疑问也完全可以通过ThinkPHP官方的开发手册找到答案。当然我们更欢迎你通过[Issues](https://github.com/ChenJinchuang/lin-cms-tp5/issues)来向我们提问:) 68 | 69 | 在下一个版本中`(>0.0.1)`,我们会在框架的基础上融入一些自己的东西来增强或者优化框架的使用、开发体验。 70 | 71 | ## 所需基础 72 | 73 | 由于 Lin 采用的是前后端分离的架构,所以你至少需要熟悉 PHP 和 Vue。 74 | 75 | Lin 的服务端框架是基于 ThinkPHP5.1的,所以如果你比较熟悉ThinkPHP的开发模式,那将可以更好的使用本项目。但如果你并不熟悉ThinkPHP,我们认为也没有太大的关系,因为框架本身已经提供了一套完整的开发机制,你只需要在框架下用 PHP 来编写自己的业务代码即可。照葫芦画瓢应该就是这种感觉。 76 | 77 | 但前端不同,前端还是需要开发者比较熟悉 Vue 的。但我想以 Vue 在国内的普及程度,绝大多数的开发者是没有问题的。这也正是我们选择 Vue 作为前端框架的原因。如果你喜欢 React Or Angular,那么加入我们,为 Lin 开发一个对应的版本吧。 78 | 79 | # 快速开始 80 | 81 | ## Server 端必备环境 82 | 83 | * 安装MySQL(version: 5.7+) 84 | 85 | * 安装PHP环境(version: 7.1+) 86 | 87 | ## 获取工程项目 88 | 89 | ```bash 90 | git clone https://github.com/ChenJinchuang/lin-cms-tp5.git 91 | ``` 92 | 93 | > 执行完毕后会生成lin-cms-tp5目录 94 | 95 | ## 安装依赖包 96 | 97 | 执行命令前请确保你已经安装了composer工具 98 | 99 | ```bash 100 | # 进入项目根目录 101 | cd lin-cms-tp5 102 | # 先执行以下命令,全局替换composer源,解决墙的问题 103 | composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 104 | # 接着执行以下命令安装依赖包 105 | composer install 106 | ``` 107 | 108 | ## 数据库配置 109 | 110 | Lin 需要你自己在 MySQL 中新建一个数据库,名字由你自己决定。例如,新建一个名为` lin-cms `的数据库。接着,我们需要在工程中进行一项简单的配置。使用编辑器打开 Lin 工程根目录下``/config/database.php``,找到如下配置项: 111 | 112 | ```php 113 | // 服务器地址 114 | 'hostname' => '', 115 | // 数据库名 116 | 'database' => 'lin-cms', 117 | // 用户名 118 | 'username' => 'root', 119 | // 密码 120 | 'password' => '', 121 | 122 | //省略后面一堆的配置项 123 | ``` 124 | 125 | **请务必根据自己的实际情况修改此配置项** 126 | 127 | ## 导入数据 128 | 129 | 接下来使用你本机上任意一款数据库可视化工具,为已经创建好的`lin-cms`数据库运行lin-cms-tp5根目录下的`schema.sql`文件,这个SQL脚本文件将为为你生成一些基础的数据库表和数据。 130 | 131 | ## 运行 132 | 133 | 如果前面的过程一切顺利,项目所需的准备工作就已经全部完成,这时候你就可以试着让工程运行起来了。在工程的根目录打开命令行,输入: 134 | 135 | ```bash 136 | php think run --port 5000 //启动thinkPHP内置的Web服务器 137 | ``` 138 | 139 | 启动成功后会看到如下提示: 140 | 141 | ```php 142 | ThinkPHP Development server is started On 143 | You can exit with `CTRL-C` 144 | ``` 145 | 146 | 打开浏览器,访问``http://127.0.0.1:5000``,你会看到一个欢迎界面,至此,Lin-cms-tp5部署完毕,可搭配[lin-cms-vue](https://github.com/TaleLin/lin-cms-vue)使用了。 147 | 148 | ## 更新日志 149 | 150 | [查看日志](http://chenjinchuang.gitee.io/lin-cms-book/log/) 151 | 152 | ## 常见问题 153 | 154 | [查看常见问题](http://chenjinchuang.gitee.io/lin-cms-book/qa/) 155 | 156 | ## 讨论交流 157 | 158 | ### QQ 交流群 159 | 160 | QQ 群号:643205479 161 | 162 | 163 | 164 | ### 微信公众号 165 | 166 | 微信搜索:林间有风 167 | 168 | 169 | -------------------------------------------------------------------------------- /application/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all -------------------------------------------------------------------------------- /application/api/behavior/Logger.php: -------------------------------------------------------------------------------- 1 | '日志信息不能为空' 31 | ]); 32 | } 33 | 34 | if (is_array($params)) { 35 | list('uid' => $uid, 'username' => $username, 'msg' => $message) = $params; 36 | } else { 37 | $tokenService = LoginToken::getInstance(); 38 | $uid = $tokenService->getCurrentUid(); 39 | $username = $tokenService->getCurrentUserName(); 40 | $message = $params; 41 | } 42 | 43 | $data = [ 44 | 'message' => $username . $message, 45 | 'user_id' => $uid, 46 | 'username' => $username, 47 | 'status_code' => Response::getCode(), 48 | 'method' => Request::method(), 49 | 'path' => '/' . Request::path(), 50 | 'permission' => null 51 | ]; 52 | 53 | LinLog::create($data); 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /application/api/controller/cms/Admin.php: -------------------------------------------------------------------------------- 1 | get('page/d', 0); 56 | $count = $request->get('count/d', 10); 57 | $groupId = $request->get('group_id/d'); 58 | 59 | return AdminService::getUsers($page, $count, $groupId); 60 | } 61 | 62 | /** 63 | * @adminRequired 64 | * @permission('修改用户密码','管理员','hidden') 65 | * @validate('ResetPasswordValidator') 66 | * @param Request $request 67 | * @param $id 68 | * @return Json 69 | * @throws NotFoundException 70 | */ 71 | public function changeUserPassword(Request $request, $id) 72 | { 73 | $newPassword = $request->put('new_password'); 74 | AdminService::changeUserPassword($id, $newPassword); 75 | Hook::listen('logger', "修改了用户ID为{$id}的密码"); 76 | 77 | return writeJson(200, null, '修改成功', 4); 78 | } 79 | 80 | /** 81 | * @adminRequired 82 | * @permission('删除用户','管理员','hidden') 83 | * @param int $id 84 | * @param('id','用户id','require|integer') 85 | * @return Json 86 | * @throws NotFoundException 87 | * @throws OperationException 88 | */ 89 | public function deleteUser(int $id) 90 | { 91 | AdminService::deleteUser($id); 92 | Hook::listen('logger', "删除了用户ID为:{$id}的用户"); 93 | return writeJson(201, $id, '删除用户成功', 5); 94 | } 95 | 96 | /** 97 | * @adminRequired 98 | * @permission('管理员更新用户信息','管理员','hidden') 99 | * @param Request $request 100 | * @param('id','用户id','require|integer') 101 | * @param('group_ids','分组id','require|array|min:1') 102 | * @return Json 103 | * @throws NotFoundException 104 | * @throws OperationException 105 | * @throws ForbiddenException 106 | * @throws DataNotFoundException 107 | * @throws ModelNotFoundException 108 | * @throws DbException 109 | */ 110 | public function updateUser(Request $request, $id) 111 | { 112 | $groupIds = $request->put('group_ids'); 113 | AdminService::updateUserInfo($id, $groupIds); 114 | 115 | Hook::listen('logger', "更新了用户:{$id}的所属分组"); 116 | return writeJson(201, $id, '更新用户成功', 6); 117 | } 118 | 119 | /** 120 | * @adminRequired 121 | * @permission('查询所有分组','管理员','hidden') 122 | * @return array|PDOStatement|string|\think\Collection|Collection 123 | * @throws DataNotFoundException 124 | * @throws DbException 125 | * @throws ModelNotFoundException 126 | * @throws NotFoundException 127 | */ 128 | public function getGroupAll() 129 | { 130 | return AdminService::getAllGroups(); 131 | } 132 | 133 | /** 134 | * @adminRequired 135 | * @permission('查询一个权限组及其权限','管理员','hidden') 136 | * @param int $id 137 | * @param('id','分组id','require|integer') 138 | * @return Query 139 | * @throws DbException 140 | * @throws NotFoundException 141 | */ 142 | public function getGroup(int $id) 143 | { 144 | return AdminService::getGroup($id); 145 | } 146 | 147 | /** 148 | * @adminRequired 149 | * @permission('新建一个权限组','管理员','hidden') 150 | * @param Request $request 151 | * @param('name','分组名字','require') 152 | * @param('permission_ids','权限id','require|array|min:1') 153 | * @return Json 154 | * @throws DataNotFoundException 155 | * @throws DbException 156 | * @throws ModelNotFoundException 157 | * @throws NotFoundException 158 | * @throws OperationException 159 | */ 160 | public function createGroup(Request $request) 161 | { 162 | $name = $request->post('name'); 163 | $info = $request->post('info'); 164 | $permissionIds = $request->post('permission_ids'); 165 | 166 | $groupId = AdminService::createGroup($name, $info, $permissionIds); 167 | 168 | Hook::listen('logger', "创建了分组:{$name}"); 169 | return writeJson(201, $groupId, '新增分组成功', 15); 170 | } 171 | 172 | /** 173 | * @adminRequired 174 | * @permission('更新一个权限组','管理员','hidden') 175 | * @param Request $request 176 | * @param int $id 177 | * @param('id','分组id','require|integer') 178 | * @param('info','分组信息','require') 179 | * @param('name','分组名字','require') 180 | * @return Json 181 | * @throws DataNotFoundException 182 | * @throws DbException 183 | * @throws ModelNotFoundException 184 | * @throws NotFoundException 185 | */ 186 | public function updateGroup(Request $request, int $id) 187 | { 188 | $name = $request->put('name'); 189 | $info = $request->put('info'); 190 | 191 | $res = AdminService::updateGroup($id, $name, $info); 192 | 193 | Hook::listen('logger', "更新了id为{$id}的分组"); 194 | return writeJson(200, $res, '更新分组信息成功', 7); 195 | } 196 | 197 | /** 198 | * @adminRequired 199 | * @permission('更新一个权限组','管理员','hidden') 200 | * @param int $id 201 | * @param('id','分组id','require|integer') 202 | * @return Json 203 | * @throws ForbiddenException 204 | * @throws NotFoundException 205 | * @throws OperationException 206 | */ 207 | public function deleteGroup(int $id) 208 | { 209 | AdminService::deleteGroup($id); 210 | 211 | Hook::listen('logger', "删除了id为{$id}的分组"); 212 | return writeJson(200, null, '删除分组成功', 8); 213 | } 214 | 215 | /** 216 | * @adminRequired 217 | * @permission('分配多个权限','管理员','hidden') 218 | * @param Request $request 219 | * @param('group_id','分组id','require|integer') 220 | * @param('permission_ids','权限id','require|array|min:1') 221 | * @return Json 222 | * @throws DbException 223 | * @throws NotFoundException 224 | * @throws OperationException 225 | */ 226 | public function dispatchPermissions(Request $request) 227 | { 228 | $groupId = $request->post('group_id'); 229 | $permissionIds = $request->post('permission_ids'); 230 | 231 | AdminService::dispatchPermissions($groupId, $permissionIds); 232 | 233 | Hook::listen('logger', "修改了分组ID为{$groupId}的权限"); 234 | return writeJson(200, null, '分配权限成功', 9); 235 | } 236 | 237 | /** 238 | * @adminRequired 239 | * @permission('删除多个权限','管理员','hidden') 240 | * @param Request $request 241 | * @param('group_id','分组id','require|integer') 242 | * @param('permission_ids','权限id','require|array|min:1') 243 | * @return Json 244 | * @throws DbException 245 | * @throws NotFoundException 246 | */ 247 | public function removePermissions(Request $request) 248 | { 249 | $groupId = $request->post('group_id'); 250 | $permissionIds = $request->post('permission_ids'); 251 | 252 | $deleted = AdminService::removePermissions($groupId, $permissionIds); 253 | 254 | Hook::listen('logger', "修改了分组ID为{$groupId}的权限"); 255 | return writeJson(200, $deleted, '删除权限成功', 10); 256 | } 257 | } -------------------------------------------------------------------------------- /application/api/controller/cms/File.php: -------------------------------------------------------------------------------- 1 | '字段中含有非法字符', 32 | ]); 33 | } 34 | $file = (new LocalUploader($request))->upload(); 35 | return $file; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /application/api/controller/cms/Log.php: -------------------------------------------------------------------------------- 1 | get('start'); 32 | $end = $request->get('end'); 33 | $name = $request->get('name'); 34 | $page = $request->get('page/d', 0); 35 | $count = $request->get('count/d', 10); 36 | 37 | return LogService::getLogs($page, $count, $start, $end, $name); 38 | } 39 | 40 | /** 41 | * @groupRequired 42 | * @permission('搜索日志','日志') 43 | * @param Request $request 44 | * @param('page','分页数','integer') 45 | * @param('count','分页值','integer') 46 | * @param('start','开始日期','date') 47 | * @param('end','结束日期','date') 48 | * @return array 49 | * @throws ParameterException 50 | */ 51 | public function getUserLogs(Request $request) 52 | { 53 | $start = $request->get('start'); 54 | $end = $request->get('end'); 55 | $name = $request->get('name'); 56 | $keyword = $request->get('keyword'); 57 | $page = $request->get('page/d', 0); 58 | $count = $request->get('count/d', 10); 59 | 60 | return LogService::searchLogs($page, $count, $start, $end, $name, $keyword); 61 | } 62 | 63 | /** 64 | * @groupRequired 65 | * @permission('查询日志记录的用户','日志') 66 | * @param Request $request 67 | * @param('page','分页数','integer') 68 | * @param('count','分页值','integer') 69 | * @return array 70 | * @throws ParameterException 71 | */ 72 | public function getUsers(Request $request) 73 | { 74 | $page = $request->get('page/d', 0); 75 | $count = $request->get('count/d', 10); 76 | 77 | return LogService::getUserNames($page, $count); 78 | } 79 | } -------------------------------------------------------------------------------- /application/api/controller/cms/User.php: -------------------------------------------------------------------------------- 1 | loginTokenService = LoginToken::getInstance(); 36 | } 37 | 38 | 39 | /** 40 | * @adminRequired 41 | * @permission('注册','管理员','hidden') 42 | * @param Request $request 43 | * @validate('RegisterForm') 44 | * @return Json 45 | * @throws NotFoundException 46 | * @throws OperationException 47 | * @throws RepeatException 48 | * @throws ForbiddenException 49 | * @throws DataNotFoundException 50 | * @throws ModelNotFoundException 51 | * @throws DbException 52 | */ 53 | public function register(Request $request) 54 | { 55 | $params = $request->post(); 56 | $user = UserService::createUser($params); 57 | 58 | Hook::listen('logger', "新建了用户:{$user['username']}"); 59 | return writeJson(201, $user['id'], '注册用户成功'); 60 | } 61 | 62 | /** 63 | * @param Request $request 64 | * @validate('LoginForm') 65 | * @return array 66 | * @throws DataNotFoundException 67 | * @throws DbException 68 | * @throws ModelNotFoundException 69 | * @throws NotFoundException 70 | * @throws AuthFailedException 71 | */ 72 | public function userLogin(Request $request) 73 | { 74 | $username = $request->post('username'); 75 | $password = $request->post('password'); 76 | $user = UserService::verify($username, $password); 77 | 78 | $tokenExtend = UserService::generateTokenExtend($user); 79 | 80 | $token = $this->loginTokenService->getToken($tokenExtend); 81 | 82 | Hook::listen('logger', array('uid' => $user->id, 'username' => $user->identifier, 'msg' => '登陆成功获取了令牌')); 83 | return [ 84 | 'access_token' => $token['accessToken'], 85 | 'refresh_token' => $token['refreshToken'] 86 | ]; 87 | } 88 | 89 | /** 90 | * @return array 91 | * @throws TokenException 92 | */ 93 | public function refreshToken() 94 | { 95 | $token = $this->loginTokenService->getTokenFromHeaders(); 96 | $token = $this->loginTokenService->refresh($token); 97 | return [ 98 | 'access_token' => $token['accessToken'] 99 | ]; 100 | } 101 | 102 | /** 103 | * @loginRequired 104 | */ 105 | public function getAllowedApis() 106 | { 107 | $uid = $this->loginTokenService->getCurrentUid(); 108 | return UserService::getPermissions($uid); 109 | } 110 | 111 | /** 112 | * @loginRequired 113 | * @return mixed 114 | */ 115 | public function getInformation() 116 | { 117 | $uid = $this->loginTokenService->getCurrentUid(); 118 | return UserService::getInformation($uid); 119 | } 120 | 121 | /** 122 | * @loginRequired 123 | * @param Request $request 124 | * @validate('UpdateUserForm') 125 | * @return Json 126 | * @throws RepeatException 127 | */ 128 | public function update(Request $request) 129 | { 130 | $params = $request->put(); 131 | $row = UserService::updateUser($params); 132 | return writeJson(200, $row, '用户信息更新成功'); 133 | } 134 | 135 | /** 136 | * @loginRequired 137 | * @validate('ChangePasswordForm') 138 | * @param Request $request 139 | * @return Json 140 | * @throws AuthFailedException 141 | * @throws NotFoundException 142 | */ 143 | public function changePassword(Request $request) 144 | { 145 | $oldPassword = $request->put('old_password'); 146 | $newPassword = $request->put('new_password'); 147 | 148 | $row = UserService::changePassword($oldPassword, $newPassword); 149 | 150 | Hook::listen('logger', '修改了自己的密码'); 151 | return writeJson(200, $row, '密码修改成功'); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /application/api/controller/v1/Book.php: -------------------------------------------------------------------------------- 1 | post(); 56 | BookModel::create($params); 57 | return writeJson(201, '', '新建图书成功'); 58 | } 59 | 60 | public function update(Request $request) 61 | { 62 | $params = $request->put(); 63 | $bookModel = new BookModel(); 64 | $bookModel->save($params, ['id' => $params['id']]); 65 | return writeJson(201, '', '更新图书成功'); 66 | } 67 | 68 | /** 69 | * @groupRequired 70 | * @permission('删除图书','图书') 71 | * @param $bid 72 | * @return Json 73 | */ 74 | public function delete($bid) 75 | { 76 | BookModel::destroy($bid); 77 | Hook::listen('logger', '删除了id为' . $bid . '的图书'); 78 | return writeJson(201, '', '删除图书成功'); 79 | } 80 | } -------------------------------------------------------------------------------- /application/api/model/BaseModel.php: -------------------------------------------------------------------------------- 1 | belongsToMany('LinUser', 'Lin_user_group', 'user_id', 'group_id'); 26 | } 27 | 28 | public function permissions() 29 | { 30 | return $this->belongsToMany('LinPermission', 'lin_group_permission', 'permission_id', 'group_id'); 31 | } 32 | } -------------------------------------------------------------------------------- /application/api/model/admin/LinGroupPermission.php: -------------------------------------------------------------------------------- 1 | count(); 27 | $logList = $logList->limit($start, $count) 28 | ->order('create_time desc') 29 | ->select(); 30 | return [ 31 | 'logList' => $logList, 32 | 'total' => $total 33 | ]; 34 | } 35 | 36 | public static function searchLogs(int $start, int $count, $params = []) 37 | { 38 | $logList = self::withSearch(['name', 'start', 'end', 'keyword'], $params); 39 | 40 | $total = $logList->count(); 41 | $logList = $logList->limit($start, $count) 42 | ->order('create_time desc') 43 | ->select(); 44 | return [ 45 | 'logList' => $logList, 46 | 'total' => $total 47 | ]; 48 | } 49 | 50 | 51 | public static function getUserNames(int $start, int $count) 52 | { 53 | $users = self::field('username'); 54 | 55 | $total = $users->count(); 56 | $users = $users->limit($start, $count) 57 | ->group('username') 58 | ->select(); 59 | 60 | return [ 61 | 'userList' => $users, 62 | 'total' => $total 63 | ]; 64 | } 65 | 66 | public function searchNameAttr($query, $value) 67 | { 68 | if ($value) { 69 | $query->where('username', $value); 70 | } 71 | } 72 | 73 | public 74 | function searchStartAttr($query, $value) 75 | { 76 | if ($value) { 77 | $query->where('create_time', '>= time', $value); 78 | } 79 | } 80 | 81 | public 82 | function searchEndAttr($query, $value) 83 | { 84 | if ($value) { 85 | $query->where('create_time', '<= time', $value); 86 | } 87 | } 88 | 89 | public 90 | function searchKeywordAttr($query, $value) 91 | { 92 | if ($value) { 93 | $query->whereLike('message', "%{$value}%"); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /application/api/model/admin/LinPermission.php: -------------------------------------------------------------------------------- 1 | where('username', '<>', 'root'); 27 | $total = $userList->count(); 28 | 29 | $userList = $userList 30 | ->limit($start, $count) 31 | ->with('groups') 32 | ->select(); 33 | 34 | return [ 35 | 'userList' => $userList, 36 | 'total' => $total 37 | ]; 38 | } 39 | 40 | public function groups() 41 | { 42 | return $this->belongsToMany('LinGroup', 'lin_user_group', 'group_id', 'user_id'); 43 | } 44 | 45 | public function identity() 46 | { 47 | return $this->hasMany('LinUserIdentity', 'user_id'); 48 | } 49 | 50 | public function searchGroupIdAttr($query, $value) 51 | { 52 | if ($value) { 53 | $query->join('lin_group g', 'g.id=' . $value)->where('g.id', '<>', 1); 54 | } 55 | } 56 | 57 | public function getAvatarAttr($value) 58 | { 59 | if ($value) { 60 | $host = Config::get('file.host') ?? "http://127.0.0.1:5000/"; 61 | $dir = Config::get('file.store_dir'); 62 | return $host . $dir . '/' . $value; 63 | } 64 | return $value; 65 | } 66 | } -------------------------------------------------------------------------------- /application/api/model/admin/LinUserGroup.php: -------------------------------------------------------------------------------- 1 | where('identifier', $currentUser->getAttr('username')) 28 | ->find(); 29 | 30 | if (!$user) { 31 | throw new NotFoundException(); 32 | } 33 | 34 | $user->credential = md5($newPassword); 35 | $user->save(); 36 | } 37 | 38 | public function checkPassword(string $password): bool 39 | { 40 | return $this->getAttr('credential') === md5($password); 41 | } 42 | } -------------------------------------------------------------------------------- /application/api/service/admin/Admin.php: -------------------------------------------------------------------------------- 1 | run(); 45 | foreach ($permissionList as $permission) { 46 | $model = LinPermissionModel::where('name', $permission['name']) 47 | ->where('module', $permission['module']) 48 | ->find(); 49 | if (!$model) { 50 | self::createPermission($permission['name'], $permission['module']); 51 | } 52 | } 53 | 54 | $permissions = LinPermissionModel::where('mount', MountTypeEnum::MOUNT) 55 | ->select()->toArray(); 56 | $result = []; 57 | foreach ($permissions as $permission) { 58 | $result[$permission['module']][] = $permission; 59 | } 60 | return $result; 61 | } 62 | 63 | /** 64 | * @param int $page 65 | * @param int $count 66 | * @param int $groupId 67 | * @return array 68 | * @throws ParameterException 69 | */ 70 | public static function getUsers(int $page, int $count, int $groupId = null): array 71 | { 72 | list($start, $count) = paginate($count, $page); 73 | $params = $groupId ? ['group_id' => $groupId] : []; 74 | $users = LinUserModel::getUsers($start, $count, $params); 75 | 76 | return [ 77 | 'items' => $users['userList'], 78 | 'count' => $count, 79 | 'page' => $page, 80 | 'total' => $users['total'] 81 | ]; 82 | } 83 | 84 | /** 85 | * @param int $uid 86 | * @param string $newPassword 87 | * @throws NotFoundException 88 | */ 89 | public static function changeUserPassword(int $uid, string $newPassword): void 90 | { 91 | $user = LinUserModel::get($uid); 92 | if (!$user) { 93 | throw new NotFoundException(); 94 | } 95 | 96 | LinUserIdentityModel::resetPassword($user, $newPassword); 97 | 98 | } 99 | 100 | /** 101 | * @param int $uid 102 | * @throws NotFoundException 103 | * @throws OperationException 104 | */ 105 | public static function deleteUser(int $uid): void 106 | { 107 | $user = LinUserModel::get($uid, 'identity'); 108 | if (!$user) { 109 | throw new NotFoundException(); 110 | } 111 | 112 | Db::startTrans(); 113 | try { 114 | $user->groups()->detach(); 115 | $user->together('identity')->delete(); 116 | Db::commit(); 117 | } catch (Exception $ex) { 118 | DB::rollback(); 119 | throw new OperationException(['msg' => "删除用户失败"]); 120 | } 121 | } 122 | 123 | /** 124 | * @param int $uid 125 | * @param array $groupIds 126 | * @throws ForbiddenException 127 | * @throws NotFoundException 128 | * @throws OperationException 129 | * @throws DataNotFoundException 130 | * @throws ModelNotFoundException 131 | * @throws DbException 132 | */ 133 | public static function updateUserInfo(int $uid, array $groupIds): void 134 | { 135 | $user = LinUserModel::get($uid); 136 | if (!$user) { 137 | throw new NotFoundException(); 138 | } 139 | 140 | $userGroupIds = LinUserGroupModel::where('user_id', $uid)->column('group_id'); 141 | $isAdmin = LinGroupModel::where('level', GroupLevelEnum::ROOT) 142 | ->whereIn('id', $userGroupIds) 143 | ->find(); 144 | if ($isAdmin) { 145 | throw new ForbiddenException(['code' => 10078, 'msg' => '不允许调整root分组信息']); 146 | } 147 | 148 | foreach ($userGroupIds as $groupId) { 149 | $group = LinGroupModel::get($groupId); 150 | if ($group['level'] === GroupLevelEnum::ROOT) { 151 | throw new ForbiddenException(['code' => 10073, 'msg' => '不允许添加用户到root分组']); 152 | } 153 | 154 | if (!$group) { 155 | throw new NotFoundException(['code' => 10077]); 156 | } 157 | } 158 | 159 | Db::startTrans(); 160 | try { 161 | $user->groups()->detach(); 162 | $user->groups()->attach($groupIds); 163 | Db::commit(); 164 | } catch (Exception $ex) { 165 | DB::rollback(); 166 | throw new OperationException(['msg' => "更新用户分组失败"]); 167 | } 168 | } 169 | 170 | /** 171 | * @return array|PDOStatement|string|\think\Collection|Collection 172 | * @throws DataNotFoundException 173 | * @throws DbException 174 | * @throws ModelNotFoundException 175 | * @throws NotFoundException 176 | */ 177 | public static function getAllGroups() 178 | { 179 | $groups = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT)->select(); 180 | if ($groups->isEmpty()) { 181 | throw new NotFoundException(); 182 | } 183 | return $groups; 184 | } 185 | 186 | /** 187 | * @param int $id 188 | * @return \think\db\Query 189 | * @throws DbException 190 | * @throws NotFoundException 191 | */ 192 | public static function getGroup(int $id) 193 | { 194 | $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT) 195 | ->get($id, 'permissions'); 196 | if (!$group) { 197 | throw new NotFoundException(); 198 | } 199 | 200 | return $group; 201 | } 202 | 203 | /** 204 | * @param string $name 205 | * @param string $info 206 | * @param array $permissionIds 207 | * @return int 208 | * @throws DataNotFoundException 209 | * @throws DbException 210 | * @throws ModelNotFoundException 211 | * @throws NotFoundException 212 | * @throws OperationException 213 | */ 214 | public static function createGroup(string $name, string $info, array $permissionIds): int 215 | { 216 | $isExist = LinGroupModel::where('name', $name)->find(); 217 | if ($isExist) { 218 | throw new OperationException(['msg' => '分组名已存在']); 219 | } 220 | 221 | foreach ($permissionIds as $permissionId) { 222 | $permission = LinPermissionModel::where('mount', MountTypeEnum::MOUNT) 223 | ->get($permissionId); 224 | if (!$permission) { 225 | throw new NotFoundException(['error_code' => 10231, 'msg' => '分配了不存在的权限']); 226 | } 227 | } 228 | 229 | Db::startTrans(); 230 | try { 231 | $group = LinGroupModel::create(['name' => $name, 'info' => $info], true); 232 | $group->permissions()->saveAll($permissionIds); 233 | Db::commit(); 234 | return $group->getAttr('id'); 235 | } catch (\Exception $ex) { 236 | Db::rollback(); 237 | throw new OperationException(['msg' => "新增分组失败:{$ex->getMessage()}"]); 238 | } 239 | 240 | } 241 | 242 | /** 243 | * @param int $id 244 | * @param string $name 245 | * @param string $info 246 | * @return int 247 | * @throws DataNotFoundException 248 | * @throws DbException 249 | * @throws ModelNotFoundException 250 | * @throws NotFoundException 251 | */ 252 | public static function updateGroup(int $id, string $name, string $info): int 253 | { 254 | $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT) 255 | ->find($id); 256 | 257 | if (!$group) { 258 | throw new NotFoundException(); 259 | } 260 | 261 | return $group->save(['name' => $name, 'info' => $info]); 262 | } 263 | 264 | /** 265 | * @param int $id 266 | * @throws ForbiddenException 267 | * @throws NotFoundException 268 | * @throws OperationException 269 | */ 270 | public static function deleteGroup(int $id): void 271 | { 272 | $group = LinGroupModel::find($id); 273 | 274 | if (!$group) { 275 | throw new NotFoundException(); 276 | } 277 | 278 | if ($group->getAttr('level') === GroupLevelEnum::ROOT) { 279 | throw new ForbiddenException(['msg' => '不允许删除root分组']); 280 | } 281 | 282 | if ($group->getAttr('level') === GroupLevelEnum::GUEST) { 283 | throw new ForbiddenException(['msg' => '不允许删除guest分组']); 284 | } 285 | 286 | Db::startTrans(); 287 | try { 288 | $group->permissions()->detach(); 289 | $group->users()->detach(); 290 | Db::commit(); 291 | } catch (Exception $ex) { 292 | Db::rollback(); 293 | throw new OperationException(['msg' => "删除分组失败:{$ex->getMessage()}"]); 294 | } 295 | } 296 | 297 | /** 298 | * @param int $id 299 | * @param array $permissionIds 300 | * @throws DbException 301 | * @throws NotFoundException 302 | * @throws OperationException 303 | */ 304 | public static function dispatchPermissions(int $id, array $permissionIds) 305 | { 306 | $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT) 307 | ->get($id); 308 | if (!$group) { 309 | throw new NotFoundException(); 310 | } 311 | 312 | foreach ($permissionIds as $permissionId) { 313 | $permission = LinPermissionModel::where('mount', MountTypeEnum::MOUNT) 314 | ->get($permissionId); 315 | if (!$permission) { 316 | throw new NotFoundException(['error_code' => 10231, 'msg' => '分配了不存在的权限']); 317 | } 318 | } 319 | 320 | try { 321 | $group->permissions()->attach($permissionIds); 322 | } catch (Exception $ex) { 323 | throw new OperationException(['msg' => '权限分配失败']); 324 | } 325 | } 326 | 327 | /** 328 | * @param int $id 329 | * @param array $permissionIds 330 | * @return int 331 | * @throws DbException 332 | * @throws NotFoundException 333 | */ 334 | public static function removePermissions(int $id, array $permissionIds): int 335 | { 336 | $group = LinGroupModel::where('level', '<>', GroupLevelEnum::ROOT) 337 | ->get($id); 338 | if (!$group) { 339 | throw new NotFoundException(); 340 | } 341 | 342 | foreach ($permissionIds as $permissionId) { 343 | $permission = LinPermissionModel::where('mount', MountTypeEnum::MOUNT) 344 | ->get($permissionId); 345 | if (!$permission) { 346 | throw new NotFoundException(['error_code' => 10231, 'msg' => '分配了不存在的权限']); 347 | } 348 | } 349 | 350 | return $group->permissions()->detach($permissionIds); 351 | } 352 | 353 | public static function createPermission(string $name, string $module): LinPermissionModel 354 | { 355 | return LinPermissionModel::create(['name' => $name, 'module' => $module, 'mount' => 1]); 356 | } 357 | } -------------------------------------------------------------------------------- /application/api/service/admin/Log.php: -------------------------------------------------------------------------------- 1 | $start, 'end' => $end, 'name' => $name]; 29 | $logsRes = LinLogModel::getLogs($offset, $count, $params); 30 | 31 | return [ 32 | 'items' => $logsRes['logList'], 33 | 'count' => $count, 34 | 'page' => $page, 35 | 'total' => $logsRes['total'] 36 | ]; 37 | } 38 | 39 | /** 40 | * @param int $page 41 | * @param int $count 42 | * @param string|null $start 43 | * @param string|null $end 44 | * @param string|null $name 45 | * @param string|null $keyword 46 | * @return array 47 | * @throws ParameterException 48 | */ 49 | public static function searchLogs(int $page, int $count, string $start = null, 50 | string $end = null, string $name = null, string $keyword = null) 51 | { 52 | list($offset, $count) = paginate($count, $page); 53 | $params = ['start' => $start, 'end' => $end, 'name' => $name, 'keyword' => $keyword]; 54 | 55 | $logsRes = LinLogModel::searchLogs($offset, $count, $params); 56 | 57 | return [ 58 | 'items' => $logsRes['logList'], 59 | 'count' => $count, 60 | 'page' => $page, 61 | 'total' => $logsRes['total'] 62 | ]; 63 | } 64 | 65 | /** 66 | * @param int $page 67 | * @param int $count 68 | * @return array 69 | * @throws ParameterException 70 | */ 71 | public static function getUserNames(int $page, int $count) 72 | { 73 | list($start, $count) = paginate($count, $page); 74 | $usersRes = LinLogModel::getUserNames($start, $count); 75 | $items = array_map(function ($item) { 76 | return $item['username']; 77 | }, $usersRes['userList']->toArray()); 78 | 79 | return [ 80 | 'items' => $items, 81 | 'count' => $count, 82 | 'page' => $page, 83 | 'total' => $usersRes['total'] 84 | ]; 85 | } 86 | } -------------------------------------------------------------------------------- /application/api/service/admin/User.php: -------------------------------------------------------------------------------- 1 | find(); 50 | if ($user) { 51 | throw new RepeatException(['msg' => '用户名已存在']); 52 | } 53 | 54 | if (isset($params['email'])) { 55 | $user = LinUserModel::where('email', $params['email'])->find(); 56 | if ($user) { 57 | throw new RepeatException(['msg' => '邮箱地址已存在']); 58 | } 59 | } 60 | 61 | if (isset($params['group_ids'])) { 62 | $groups = LinGroupModel::select($params['group_ids']); 63 | foreach ($groups as $group) { 64 | if ($group['level'] === GroupLevelEnum::ROOT) { 65 | throw new ForbiddenException(['msg' => '不允许分配用户到root分组']); 66 | } 67 | } 68 | 69 | if ($groups->isEmpty()) { 70 | throw new NotFoundException(); 71 | } 72 | } 73 | 74 | return self::registerUser($params); 75 | } 76 | 77 | /** 78 | * @param string $username 79 | * @param string $password 80 | * @return Model 81 | * @throws AuthFailedException 82 | * @throws DataNotFoundException 83 | * @throws DbException 84 | * @throws ModelNotFoundException 85 | * @throws NotFoundException 86 | */ 87 | public static function verify(string $username, string $password): Model 88 | { 89 | $user = new LinUserIdentityModel(); 90 | 91 | $user = $user->where('identifier', $username) 92 | ->where('identity_type', IdentityTypeEnum::PASSWORD) 93 | ->find(); 94 | 95 | if (!$user) { 96 | throw new NotFoundException(['msg' => '用户不存在']); 97 | } 98 | 99 | if (!$user->checkPassword($password)) { 100 | throw new AuthFailedException(); 101 | } 102 | return $user; 103 | } 104 | 105 | public static function generateTokenExtend(Model $linUserIdentityModel) 106 | { 107 | $user = LinUserModel::get($linUserIdentityModel['user_id']); 108 | $userPermissions = self::getPermissions($user->getAttr('id')); 109 | return [ 110 | 'id' => $user->getAttr('id'), 111 | 'identifier' => $linUserIdentityModel->getAttr('identifier'), 112 | 'email' => $user->getAttr('email'), 113 | 'admin' => $userPermissions['admin'], 114 | 'permissions' => $userPermissions['permissions'], 115 | ]; 116 | } 117 | 118 | public static function getPermissions(int $uid): array 119 | { 120 | $user = LinUserModel::get($uid); 121 | 122 | $groupIds = LinUserGroupModel::where('user_id', $uid) 123 | ->column('group_id'); 124 | 125 | $root = LinGroupModel::where('level', GroupLevelEnum::ROOT) 126 | ->whereIn('id', $groupIds)->find(); 127 | 128 | $user = $user->hidden(['username'])->toArray(); 129 | $user['admin'] = $root ? true : false; 130 | 131 | if ($root) { 132 | $permissions = LinPermissionModel::where('mount', MountTypeEnum::MOUNT) 133 | ->select() 134 | ->toArray(); 135 | $user['permissions'] = formatPermissions($permissions); 136 | } else { 137 | $permissionIds = LinGroupPermissionModel::whereIn('group_id', $groupIds) 138 | ->column('permission_id'); 139 | $permissions = LinPermissionModel::where('mount', MountTypeEnum::MOUNT) 140 | ->select($permissionIds)->toArray(); 141 | 142 | $user['permissions'] = formatPermissions($permissions); 143 | 144 | } 145 | 146 | return $user; 147 | } 148 | 149 | public static function getInformation(int $uid) 150 | { 151 | return LinUser::get($uid, 'groups'); 152 | } 153 | 154 | public static function updateUser(array $params): int 155 | { 156 | $user = LoginToken::getInstance()->getTokenExtend(); 157 | if (isset($params['username']) && $params['username'] !== $user['username']) { 158 | $isExit = LinUserModel::where('username', $params['username']) 159 | ->find(); 160 | if ($isExit) { 161 | throw new RepeatException(['msg' => "用户名已被占用"]); 162 | } 163 | } 164 | 165 | if (isset($params['email']) && $params['email'] !== $user['email']) { 166 | $isExit = LinUserModel::where('email', $params['email']) 167 | ->find(); 168 | if ($isExit) { 169 | throw new RepeatException(['msg' => "邮箱已被占用"]); 170 | } 171 | } 172 | 173 | $user = LinUserModel::get($user['id']); 174 | return $user->allowField(true)->save($params); 175 | } 176 | 177 | public static function changePassword(string $oldPassword, string $newPassword): int 178 | { 179 | $currentUser = LoginToken::getInstance()->getTokenExtend(); 180 | $user = new LinUserIdentityModel(); 181 | 182 | $user = $user::where('identity_type', IdentityTypeEnum::PASSWORD) 183 | ->where('identifier', $currentUser['identifier']) 184 | ->find(); 185 | 186 | if (!$user) { 187 | throw new NotFoundException(); 188 | } 189 | 190 | if (!$user->checkPassword($oldPassword)) { 191 | throw new AuthFailedException(); 192 | } 193 | 194 | $user->credential = md5($newPassword); 195 | return $user->save(); 196 | } 197 | 198 | /** 199 | * @param array $params 200 | * @return LinUserModel 201 | * @throws OperationException 202 | */ 203 | private static function registerUser(array $params): LinUserModel 204 | { 205 | Db::startTrans(); 206 | try { 207 | $user = LinUserModel::create($params, true); 208 | $user->identity()->save([ 209 | 'identity_type' => IdentityTypeEnum::PASSWORD, 210 | 'identifier' => $user['username'], 211 | 'credential' => md5($params['password']) 212 | ]); 213 | 214 | // 判断是否同时分配了分组 215 | if (isset($params['group_ids']) && count($params['group_ids']) > 0) { 216 | $user->groups()->attach($params['group_ids']); 217 | } else { 218 | // 没有分配分组,添加到游客分组 219 | $group = LinGroupModel::where('level', GroupLevelEnum::GUEST)->find(); 220 | $user->groups()->attach([$group['id']]); 221 | } 222 | Db::commit(); 223 | return $user; 224 | } catch (Exception $ex) { 225 | Db::rollback(); 226 | throw new OperationException(['msg' => "注册用户失败:{$ex->getMessage()}"]); 227 | } 228 | 229 | } 230 | 231 | // private static function formatPermissions(array $permissions) 232 | // { 233 | // $groupPermission = []; 234 | // foreach ($permissions as $permission) { 235 | // $item = [ 236 | // 'name' => $permission['name'], 237 | // 'module' => $permission['module'] 238 | // ]; 239 | // $groupPermission[$permission['module']][] = $item; 240 | // } 241 | // 242 | // $result[] = array_map(function ($item) { 243 | // return $item; 244 | // }, $groupPermission); 245 | // return $result; 246 | // } 247 | } -------------------------------------------------------------------------------- /application/api/service/token/LoginToken.php: -------------------------------------------------------------------------------- 1 | tokenConfig = (new TokenConfig()) 42 | ->dualToken($config['enable_dual_token']) 43 | ->setAlgorithms($config['algorithms']) 44 | ->setIat(time()) 45 | ->setIss($config['issuer']) 46 | ->setAccessSecretKey($config['access_secret_key']) 47 | ->setAccessExp($config['access_expire_time']) 48 | ->setRefreshSecretKey($config['refresh_secret_key']) 49 | ->setRefreshExp($config['refresh_expire_time']); 50 | } 51 | 52 | public static function getInstance(): LoginToken 53 | { 54 | if (!self::$instance instanceof self) { 55 | self::$instance = new self(); 56 | } 57 | return self::$instance; 58 | } 59 | 60 | /** 61 | * 获取令牌 62 | * @param array $extend 要插入到令牌扩展字段中的信息 63 | * @return array 64 | * @throws \Exception 65 | */ 66 | public function getToken(array $extend): array 67 | { 68 | $this->tokenConfig->setExtend($extend); 69 | return TokenUtils::makeToken($this->tokenConfig); 70 | } 71 | 72 | /** 73 | * 令牌刷新 74 | * @param string $refreshToken 当开启了双令牌后颁发的refreshToken,用于刷新accessToken 75 | * @return array 76 | * @throws TokenException 77 | */ 78 | public function refresh(string $refreshToken): array 79 | { 80 | try { 81 | return TokenUtils::refresh($refreshToken, $this->tokenConfig); 82 | } catch (SignatureInvalidException $signatureInvalidException) { 83 | throw new TokenException(['msg' => '令牌签名错误']); 84 | } catch (BeforeValidException $beforeValidException) { 85 | throw new TokenException(); 86 | } catch (ExpiredException $expiredException) { 87 | throw new TokenException(['error_code' => 10042, 'msg' => '令牌已过期,请重新登录']); 88 | } catch (UnexpectedValueException $unexpectedValueException) { 89 | throw new TokenException(); 90 | } 91 | } 92 | 93 | /** 94 | * @param string|null $token 95 | * @param string $tokenType 96 | * @return array 97 | * @throws TokenException 98 | */ 99 | public function verify(string $token = null, string $tokenType = 'access') 100 | { 101 | $token = $token ?: $this->getTokenFromHeaders(); 102 | try { 103 | return TokenUtils::verifyToken($token, $tokenType, $this->tokenConfig); 104 | } catch (SignatureInvalidException $signatureInvalidException) { 105 | throw new TokenException(['msg' => '令牌签名错误']); 106 | } catch (BeforeValidException $beforeValidException) { 107 | throw new TokenException(); 108 | } catch (ExpiredException $expiredException) { 109 | throw new TokenException(['error_code' => 10041, 'msg' => '令牌已过期']); 110 | } catch (UnexpectedValueException $unexpectedValueException) { 111 | throw new TokenException(); 112 | } 113 | } 114 | 115 | /** 116 | * 获取令牌扩展字段内容 117 | * @param string|null $token 118 | * @param string $tokenType 119 | * @return array 120 | * @throws TokenException 121 | */ 122 | public function getTokenExtend(string $token = null, string $tokenType = 'access'): array 123 | { 124 | return (array)$this->verify($token, $tokenType)['extend']; 125 | } 126 | 127 | /** 128 | * 获取指定令牌扩展内容字段的值 129 | * @param string $val 130 | * @return mixed 131 | * @throws TokenException 132 | */ 133 | public function getExtendVal(string $val) 134 | { 135 | return $this->getTokenExtend()[$val]; 136 | } 137 | 138 | public function getCurrentUid() 139 | { 140 | return $this->getExtendVal('id'); 141 | } 142 | 143 | public function getCurrentUserName() 144 | { 145 | return $this->getExtendVal('identifier'); 146 | } 147 | 148 | public function getTokenFromHeaders(): string 149 | { 150 | $authorization = Request::header('authorization'); 151 | 152 | if (!$authorization) { 153 | throw new TokenException(['msg' => '请求未携带Authorization信息']); 154 | } 155 | 156 | list($type, $token) = explode(' ', $authorization); 157 | 158 | if ($type !== 'Bearer') throw new TokenException(['msg' => '接口认证方式需为Bearer']); 159 | 160 | if (!$token || $token === 'undefined') { 161 | throw new TokenException(['msg' => '尝试获取的Authorization信息不存在']); 162 | } 163 | 164 | return $token; 165 | } 166 | } -------------------------------------------------------------------------------- /application/api/validate/user/ChangePasswordForm.php: -------------------------------------------------------------------------------- 1 | 'require', 13 | 'new_password|新密码' => 'require|confirm:confirm_password', 14 | 'confirm_password|确认密码' => 'require', 15 | ]; 16 | } -------------------------------------------------------------------------------- /application/api/validate/user/LoginForm.php: -------------------------------------------------------------------------------- 1 | 'require', 18 | 'password' => 'require', 19 | ]; 20 | 21 | protected $message = [ 22 | 'username' => '用户名不能为空', 23 | 'password' => '密码不能为空' 24 | ]; 25 | } -------------------------------------------------------------------------------- /application/api/validate/user/RegisterForm.php: -------------------------------------------------------------------------------- 1 | 'require|confirm:confirm_password', 18 | 'confirm_password' => 'require', 19 | 'username' => 'require|length:2,10', 20 | 'group_ids' => 'array', 21 | 'email' => 'email' 22 | ]; 23 | } -------------------------------------------------------------------------------- /application/api/validate/user/ResetPasswordValidator.php: -------------------------------------------------------------------------------- 1 | 'require|integer', 18 | 'new_password|新密码' => 'require|confirm:confirm_password', 19 | 'confirm_password|确认密码' => 'require', 20 | ]; 21 | } -------------------------------------------------------------------------------- /application/api/validate/user/UpdateUserForm.php: -------------------------------------------------------------------------------- 1 | 'length:2,10', 18 | 'email' => 'email', 19 | 'nickname' => 'length:2,10', 20 | ]; 21 | } -------------------------------------------------------------------------------- /application/command.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | return []; 13 | -------------------------------------------------------------------------------- /application/common.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // 应用公共文件 13 | use LinCmsTp5\exception\ParameterException; 14 | use think\response\Json; 15 | 16 | /** 17 | * 统一响应包装函数 18 | * @param $code 19 | * @param $errorCode 20 | * @param $data 21 | * @param $msg 22 | * @return Json 23 | */ 24 | function writeJson($code, $data, $msg = 'ok', $errorCode = 0) 25 | { 26 | $data = [ 27 | 'code' => $errorCode, 28 | 'result' => $data, 29 | 'message' => $msg 30 | ]; 31 | return json($data, $code); 32 | } 33 | 34 | /** 35 | * 分页参数处理函数 36 | * @param int $count 37 | * @param int $page 38 | * @return array 39 | * @throws ParameterException 40 | */ 41 | function paginate(int $count = 10, int $page = 0) 42 | { 43 | // $count = intval(Request::get('count', $count)); 44 | // $start = intval(Request::get('page', $page)); 45 | // $page = $start; 46 | $count = $count >= 15 ? 15 : $count; 47 | $start = $page * $count; 48 | 49 | if ($start < 0 || $count < 0) throw new ParameterException(); 50 | 51 | return [$start, $count]; 52 | } 53 | 54 | /** 55 | * 权限数组格式化函数 56 | * @param array $permissions 57 | * @return array 58 | */ 59 | function formatPermissions(array $permissions) 60 | { 61 | $groupPermission = []; 62 | foreach ($permissions as $permission) { 63 | $item = [ 64 | 'permission' => $permission['name'], 65 | 'module' => $permission['module'] 66 | ]; 67 | $groupPermission[$permission['module']][] = $item; 68 | } 69 | $result = []; 70 | foreach ($groupPermission as $key => $item) { 71 | array_push($result, [$key => $item]); 72 | } 73 | 74 | return $result; 75 | } -------------------------------------------------------------------------------- /application/http/middleware/Authentication.php: -------------------------------------------------------------------------------- 1 | check(); 23 | 24 | if (!$auth) { 25 | throw new ForbiddenException(); 26 | } 27 | 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /application/index/controller/Index.php: -------------------------------------------------------------------------------- 1 | *{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: 21 | pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: 22 | "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; 23 | margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

24 | Lin
心上无垢,林间有风。

'); 25 | } 26 | } -------------------------------------------------------------------------------- /application/lib/authenticator/Authenticator.php: -------------------------------------------------------------------------------- 1 | controller(); 29 | // 控制层下有二级目录,需要解析下。如controller/cms/Admin,获取到的是Cms.Admin 30 | $controllerPath = explode('.', $controller); 31 | // 获取当前请求的方法 32 | $action = $request->action(); 33 | // 反射获取当前请求的控制器类 34 | $class = new ReflectionClass('app\\api\\controller\\' . strtolower($controllerPath[0]) . '\\' . $controllerPath[1]); 35 | $this->parsedClass = (new Reflex($class->newInstance()))->setMethod($action); 36 | } 37 | 38 | /** 39 | * 入口方法 40 | * @return bool 41 | * @throws DeployException 42 | * @throws ReflectionException 43 | */ 44 | public function check(): bool 45 | { 46 | //判断是否开启加载文件函数注释 47 | if (ini_get('opcache.save_comments') === '0' || ini_get('opcache.save_comments') === '') { 48 | throw new DeployException(); 49 | } 50 | // 获取方法权限控制等级 51 | $actionPermissionLevel = $this->actionAuthorityLevel(); 52 | // 没有等级标识,直接通过 53 | if (!$actionPermissionLevel) { 54 | return true; 55 | } 56 | 57 | // 执行校验并返回校验结果 58 | return $this->execute($actionPermissionLevel); 59 | 60 | } 61 | 62 | /** 63 | * 执行各权限等级校验 64 | * @param string $actionPermissionLevel 65 | * @return bool 66 | * @throws ReflectionException 67 | */ 68 | public function execute(string $actionPermissionLevel): bool 69 | { 70 | // 账户信息,包含所拥有的权限列表 71 | $userInfo = $this->getUserInfo(); 72 | //账户属于超级管理员,直接通过 73 | if ($userInfo['admin'] === true) return true; 74 | $actionPermissionName = $this->actionPermission(); 75 | 76 | return AuthenticatorExecutorFactory::getInstance($actionPermissionLevel)->handle($userInfo, $actionPermissionName); 77 | 78 | } 79 | 80 | /** 81 | * 获取接口权限等级注解 82 | * @return string 83 | * @throws Exception 84 | */ 85 | protected function actionAuthorityLevel(): string 86 | { 87 | $permissionLevel = null; 88 | 89 | if ($this->parsedClass->isExist(PermissionLevelEnum::LOGIN_REQUIRED)) { 90 | $permissionLevel = PermissionLevelEnum::LOGIN_REQUIRED; 91 | return $permissionLevel; 92 | } 93 | 94 | if ($this->parsedClass->isExist(PermissionLevelEnum::GROUP_REQUIRED)) { 95 | $permissionLevel = PermissionLevelEnum::GROUP_REQUIRED; 96 | return $permissionLevel; 97 | } 98 | if ($this->parsedClass->isExist(PermissionLevelEnum::ADMIN_REQUIRED)) { 99 | $permissionLevel = PermissionLevelEnum::ADMIN_REQUIRED; 100 | return $permissionLevel; 101 | } 102 | return ''; 103 | } 104 | 105 | protected function getUserInfo(): array 106 | { 107 | return LoginToken::getInstance()->getTokenExtend(); 108 | } 109 | 110 | /** 111 | * 获取接口权限注解内容 112 | * @return string 113 | * @throws Exception 114 | */ 115 | protected function actionPermission(): string 116 | { 117 | $actionAuthContent = $this->parsedClass->get('permission'); 118 | 119 | $actionAuthContent = empty($actionAuthContent) ? '' : $actionAuthContent[0] . '/' . $actionAuthContent[1]; 120 | return $actionAuthContent; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /application/lib/authenticator/AuthenticatorExecutorFactory.php: -------------------------------------------------------------------------------- 1 | namespaceList = (new Scan())->scanController(); 25 | } 26 | 27 | /** 28 | * @throws ReflectionException 29 | */ 30 | public function run() 31 | { 32 | return $this->getPermissionList(); 33 | } 34 | 35 | /** 36 | * @return array 37 | * @throws ReflectionException 38 | */ 39 | private function getPermissionList() 40 | { 41 | $permissionList = []; 42 | // 遍历需要解析@permission注解的控制器类 43 | foreach ($this->namespaceList as $value) { 44 | // 反射控制器类 45 | $class = new ReflectionClass($value); 46 | // 类下面的所有方法的数组 47 | $methods = $class->getMethods(); 48 | // 类下面所有含有@permission注解的方法的注解内容数组 49 | $methodPermissionList = $this->getPermissionByMethods($class->newInstance(), $methods); 50 | 51 | if (!empty($methodPermissionList)) { 52 | // 插入类权限数组 53 | if (empty($permissionList)) { 54 | $permissionList = $methodPermissionList; 55 | } else { 56 | $permissionList = array_merge($permissionList, $methodPermissionList); 57 | } 58 | } 59 | } 60 | return $permissionList; 61 | } 62 | 63 | /** 64 | * @param $class 65 | * @param $methods 66 | * @param string $annotationField 67 | * @return array 68 | * @throws Exception 69 | */ 70 | private function getPermissionByMethods($class, $methods, $annotationField = 'permission') 71 | { 72 | $data = []; 73 | $re = new Reflex($class); 74 | foreach ($methods as $value) { 75 | $re->setMethod($value->name); 76 | $permissionAnnotationArray = $re->get($annotationField); 77 | 78 | if (!empty($permissionAnnotationArray) && !in_array('hidden', $permissionAnnotationArray)) { 79 | $permission = $this->handleAnnotation($permissionAnnotationArray); 80 | array_push($data, $permission); 81 | } 82 | } 83 | 84 | return $data; 85 | } 86 | 87 | public function handleAnnotation(array $annotation) 88 | { 89 | return [ 90 | 'name' => $annotation[0], 91 | 'module' => $annotation[1], 92 | 'mount' => MountTypeEnum::MOUNT 93 | ]; 94 | } 95 | } -------------------------------------------------------------------------------- /application/lib/authenticator/Scan.php: -------------------------------------------------------------------------------- 1 | controller_namespace = 'app\\api\\controller\\'; 22 | // 拼接出当前应用模块下的控制器层目录在服务器上的绝对路径 23 | $this->controller_path = Env::get('module_path') . 'controller'; 24 | // 初始化需权限扫描的命名空间列表 25 | $this->authScanNamespaceList = []; 26 | } 27 | 28 | /** 29 | * 入口方法,调用scanControllerLayerDir()扫描控制器层 30 | * @return array 控制器层下所有类的完整命名空间数组 31 | */ 32 | public function scanController() 33 | { 34 | return $this->scanControllerLayerDir($this->controller_path); 35 | } 36 | 37 | /** 38 | * 递归扫描控制器目录,扫描到类文件的时候push命名空间到$this->authScanNamespaceList 39 | * @param string $path 扫描的目标目录 40 | * @param string $subModule 可空,目标目录的子目录 41 | * @return array 控制器层下所有类的完整命名空间数组 42 | */ 43 | private function scanControllerLayerDir(string $path, string $subModule = '') 44 | { 45 | $files = scandir($path); 46 | foreach ($files as $file) { 47 | if ($file !== '.' && $file !== '..') { 48 | if (strpos($file, '.php')) { 49 | $classFileName = substr($file, 0, -4); 50 | $module = $subModule ? $subModule . '\\' : ''; 51 | $completeNamespace = $this->controller_namespace . $module . $classFileName; 52 | array_push($this->authScanNamespaceList, $completeNamespace); 53 | } else { 54 | $ds = PHP_OS === 'WINNT' ? '\\' : DIRECTORY_SEPARATOR; 55 | $subDir = $path . $ds . $file; 56 | $this->scanControllerLayerDir($subDir, $file); 57 | } 58 | } 59 | } 60 | return $this->authScanNamespaceList; 61 | } 62 | } -------------------------------------------------------------------------------- /application/lib/authenticator/executor/IExecutor.php: -------------------------------------------------------------------------------- 1 | verify(); 21 | return true; 22 | } 23 | } -------------------------------------------------------------------------------- /application/lib/enum/GroupLevelEnum.php: -------------------------------------------------------------------------------- 1 | files as $key => $file) { 31 | $md5 = $this->generateMd5($file); 32 | $exists = LinFile::get(['md5' => $md5]); 33 | if ($exists) { 34 | array_push($ret, [ 35 | 'id' => $exists['id'], 36 | 'key' => $key, 37 | 'path' => $exists['path'], 38 | 'url' => $host . '/' . $this->storeDir . '/' . $exists['path'] 39 | ]); 40 | } else { 41 | $size = $this->getSize($file); 42 | $info = $file->move(Env::get('root_path') . '/' . 'public' . '/' . $this->storeDir); 43 | if ($info) { 44 | $extension = '.' . $info->getExtension(); 45 | $path = str_replace('\\', '/', $info->getSaveName()); 46 | $name = $info->getFilename(); 47 | } else { 48 | throw new FileException([ 49 | 'msg' => "存储本地文件失败", 50 | 'error_code' => 60001 51 | ]); 52 | } 53 | $linFile = LinFile::create([ 54 | 'name' => $name, 55 | 'path' => $path, 56 | 'size' => $size, 57 | 'extension' => $extension, 58 | 'md5' => $md5, 59 | 'type' => 1 60 | ]); 61 | array_push($ret, [ 62 | 'id' => $linFile->id, 63 | 'key' => $key, 64 | 'path' => $path, 65 | 'url' => $host . '/' . $this->storeDir . '/' . $path 66 | ]); 67 | 68 | } 69 | 70 | } 71 | return $ret; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /application/provider.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // 应用容器绑定定义 13 | return [ 14 | ]; 15 | -------------------------------------------------------------------------------- /application/tags.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // 应用行为扩展定义文件 13 | return [ 14 | // 应用初始化 15 | 'app_init' => [ 16 | ], 17 | // 应用开始 18 | 'app_begin' => [], 19 | // 模块初始化 20 | 'module_init' => [], 21 | // 操作开始执行 22 | 'action_begin' => [], 23 | // 视图内容过滤 24 | 'view_filter' => [], 25 | // 日志写入 26 | 'log_write' => [], 27 | // 应用结束 28 | 'app_end' => [], 29 | // api日志 30 | 'logger' => [ 31 | 'app\\api\\behavior\\Logger', 32 | ] 33 | ]; 34 | -------------------------------------------------------------------------------- /build.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | return [ 13 | // 生成应用公共文件 14 | '__file__' => ['common.php'], 15 | 16 | // 定义demo模块的自动生成 (按照实际定义的文件名生成) 17 | 'demo' => [ 18 | '__file__' => ['common.php'], 19 | '__dir__' => ['behavior', 'controller', 'model', 'view'], 20 | 'controller' => ['Index', 'Test', 'UserType'], 21 | 'model' => ['User', 'UserType'], 22 | 'view' => ['index/index'], 23 | ], 24 | 25 | // 其他更多的模块定义 26 | ]; 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "topthink/think", 3 | "description": "the new thinkphp framework", 4 | "type": "project", 5 | "keywords": [ 6 | "framework", 7 | "thinkphp", 8 | "ORM" 9 | ], 10 | "homepage": "http://thinkphp.cn/", 11 | "license": "Apache-2.0", 12 | "authors": [ 13 | { 14 | "name": "liu21st", 15 | "email": "liu21st@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.1.0", 20 | "topthink/framework": "5.1.36", 21 | "topthink/think-migration": "2.*", 22 | "lin-cms-tp5/base-core": "dev-master", 23 | "lin-cms-tp/validate-core": "dev-master", 24 | "lin-cms-tp/utils-core": "dev-master", 25 | "qinchen/web-utils": ">0.0.1" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "app\\": "application" 30 | } 31 | }, 32 | "extra": { 33 | "think-path": "thinkphp" 34 | }, 35 | "config": { 36 | "preferred-install": "dist" 37 | }, 38 | "repositories": { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | 应用设置 14 | // +---------------------------------------------------------------------- 15 | 16 | return [ 17 | // 应用名称 18 | 'app_name' => '', 19 | // 应用地址 20 | 'app_host' => '', 21 | // 应用调试模式 22 | 'app_debug' => true, 23 | // 应用Trace 24 | 'app_trace' => false, 25 | // 是否支持多模块 26 | 'app_multi_module' => true, 27 | // 入口自动绑定模块 28 | 'auto_bind_module' => false, 29 | // 注册的根命名空间 30 | 'root_namespace' => [], 31 | // 默认输出类型 32 | 'default_return_type' => 'json', 33 | // 默认AJAX 数据返回格式,可选json xml ... 34 | 'default_ajax_return' => 'json', 35 | // 默认JSONP格式返回的处理方法 36 | 'default_jsonp_handler' => 'jsonpReturn', 37 | // 默认JSONP处理方法 38 | 'var_jsonp_handler' => 'callback', 39 | // 默认时区 40 | 'default_timezone' => 'Asia/Shanghai', 41 | // 是否开启多语言 42 | 'lang_switch_on' => false, 43 | // 默认全局过滤方法 用逗号分隔多个 44 | 'default_filter' => '', 45 | // 默认语言 46 | 'default_lang' => 'zh-cn', 47 | // 应用类库后缀 48 | 'class_suffix' => false, 49 | // 控制器类后缀 50 | 'controller_suffix' => false, 51 | 52 | // +---------------------------------------------------------------------- 53 | // | 模块设置 54 | // +---------------------------------------------------------------------- 55 | 56 | // 默认模块名 57 | 'default_module' => 'index', 58 | // 禁止访问模块 59 | 'deny_module_list' => ['common'], 60 | // 默认控制器名 61 | 'default_controller' => 'Index', 62 | // 默认操作名 63 | 'default_action' => 'index', 64 | // 默认验证器 65 | 'default_validate' => '', 66 | // 默认的空模块名 67 | 'empty_module' => '', 68 | // 默认的空控制器名 69 | 'empty_controller' => 'Error', 70 | // 操作方法前缀 71 | 'use_action_prefix' => false, 72 | // 操作方法后缀 73 | 'action_suffix' => '', 74 | // 自动搜索控制器 75 | 'controller_auto_search' => false, 76 | 77 | // +---------------------------------------------------------------------- 78 | // | URL设置 79 | // +---------------------------------------------------------------------- 80 | 81 | // PATHINFO变量名 用于兼容模式 82 | 'var_pathinfo' => 's', 83 | // 兼容PATH_INFO获取 84 | 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], 85 | // pathinfo分隔符 86 | 'pathinfo_depr' => '/', 87 | // HTTPS代理标识 88 | 'https_agent_name' => '', 89 | // IP代理获取标识 90 | 'http_agent_ip' => 'X-REAL-IP', 91 | // URL伪静态后缀 92 | 'url_html_suffix' => 'html', 93 | // URL普通方式参数 用于自动生成 94 | 'url_common_param' => false, 95 | // URL参数方式 0 按名称成对解析 1 按顺序解析 96 | 'url_param_type' => 0, 97 | // 是否开启路由延迟解析 98 | 'url_lazy_route' => false, 99 | // 是否强制使用路由 100 | 'url_route_must' => false, 101 | // 合并路由规则 102 | 'route_rule_merge' => false, 103 | // 路由是否完全匹配 104 | 'route_complete_match' => true, 105 | // 使用注解路由 106 | 'route_annotation' => false, 107 | // 域名根,如thinkphp.cn 108 | 'url_domain_root' => '', 109 | // 是否自动转换URL中的控制器和操作名 110 | 'url_convert' => true, 111 | // 默认的访问控制器层 112 | 'url_controller_layer' => 'controller', 113 | // 表单请求类型伪装变量 114 | 'var_method' => '_method', 115 | // 表单ajax伪装变量 116 | 'var_ajax' => '_ajax', 117 | // 表单pjax伪装变量 118 | 'var_pjax' => '_pjax', 119 | // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 120 | 'request_cache' => false, 121 | // 请求缓存有效期 122 | 'request_cache_expire' => null, 123 | // 全局请求缓存排除规则 124 | 'request_cache_except' => [], 125 | // 是否开启路由缓存 126 | 'route_check_cache' => false, 127 | // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5 128 | 'route_check_cache_key' => '', 129 | // 路由缓存类型及参数 130 | 'route_cache_option' => [], 131 | 132 | // 默认跳转页面对应的模板文件 133 | 'dispatch_success_tmpl' => Env::get('think_path') . 'tpl/dispatch_jump.tpl', 134 | 'dispatch_error_tmpl' => Env::get('think_path') . 'tpl/dispatch_jump.tpl', 135 | 136 | // 异常页面的模板文件 137 | 'exception_tmpl' => Env::get('think_path') . 'tpl/think_exception.tpl', 138 | 139 | // 错误显示信息,非调试模式有效 140 | 'error_message' => '页面错误!请稍后再试~', 141 | // 显示错误信息 142 | 'show_error_msg' => false, 143 | // 异常处理handle类 留空使用 \think\exception\Handle 144 | 'exception_handle' => 'LinCmsTp5\exception\ExceptionHandler', 145 | 146 | ]; 147 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | 缓存设置 14 | // +---------------------------------------------------------------------- 15 | 16 | return [ 17 | // 驱动方式 18 | 'type' => 'File', 19 | // 缓存保存目录 20 | 'path' => '', 21 | // 缓存前缀 22 | 'prefix' => '', 23 | // 缓存有效期 0表示永久缓存 24 | 'expire' => 0, 25 | ]; 26 | -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | 控制台配置 14 | // +---------------------------------------------------------------------- 15 | return [ 16 | 'name' => 'Think Console', 17 | 'version' => '0.1', 18 | 'user' => null, 19 | 'auto_path' => env('app_path') . 'command' . DIRECTORY_SEPARATOR, 20 | ]; 21 | -------------------------------------------------------------------------------- /config/cookie.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | Cookie设置 14 | // +---------------------------------------------------------------------- 15 | return [ 16 | // cookie 名称前缀 17 | 'prefix' => '', 18 | // cookie 保存时间 19 | 'expire' => 0, 20 | // cookie 保存路径 21 | 'path' => '/', 22 | // cookie 有效域名 23 | 'domain' => '', 24 | // cookie 启用安全传输 25 | 'secure' => false, 26 | // httponly设置 27 | 'httponly' => '', 28 | // 是否使用 setcookie 29 | 'setcookie' => true, 30 | ]; 31 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | return [ 13 | // 数据库类型 14 | 'type' => 'mysql', 15 | // 服务器地址 16 | 'hostname' => '127.0.0.1', 17 | // 数据库名 18 | 'database' => 'lin_cms', 19 | // 用户名 20 | 'username' => 'root', 21 | // 密码 22 | 'password' => '', 23 | // 端口 24 | 'hostport' => '', 25 | // 连接dsn 26 | 'dsn' => '', 27 | // 数据库连接参数 28 | 'params' => [], 29 | // 数据库编码默认采用utf8 30 | 'charset' => 'utf8', 31 | // 数据库表前缀 32 | 'prefix' => '', 33 | // 数据库调试模式 34 | 'debug' => true, 35 | // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) 36 | 'deploy' => 0, 37 | // 数据库读写是否分离 主从式有效 38 | 'rw_separate' => false, 39 | // 读写分离后 主服务器数量 40 | 'master_num' => 1, 41 | // 指定从服务器序号 42 | 'slave_no' => '', 43 | // 自动读取主库数据 44 | 'read_master' => false, 45 | // 是否严格检查字段是否存在 46 | 'fields_strict' => true, 47 | // 数据集返回类型 48 | 'resultset_type' => 'collection', 49 | // 自动写入时间戳字段 50 | 'auto_timestamp' => false, 51 | // 时间字段取出后的默认时间格式 52 | 'datetime_format' => 'Y-m-d H:i:s', 53 | // 是否需要进行SQL性能分析 54 | 'sql_explain' => false, 55 | // Builder类 56 | 'builder' => '', 57 | // Query类 58 | 'query' => '\\think\\db\\Query', 59 | // 是否需要断线重连 60 | 'break_reconnect' => false, 61 | // 断线标识字符串 62 | 'break_match_str' => [] 63 | ]; 64 | -------------------------------------------------------------------------------- /config/file.php: -------------------------------------------------------------------------------- 1 | 'uploads', # 文件的存储路径 5 | "single_limit" => 1024 * 1024 * 2, # 单个文件的大小限制,默认2M 6 | "total_limit"=> 1024 * 1024 * 20, # 所有文件的大小限制,默认20M 7 | "nums" => 10, # 文件数量限制,默认10 8 | "include" => [], # 文件后缀名的排除项,默认排除[],即允许所有类型的文件上传 9 | "exclude" => [] # 文件后缀名的包括项 10 | ]; 11 | -------------------------------------------------------------------------------- /config/log.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | 日志设置 14 | // +---------------------------------------------------------------------- 15 | return [ 16 | // 日志记录方式,内置 file socket 支持扩展 17 | 'type' => 'File', 18 | // 日志保存目录 19 | 'path' => '', 20 | // 日志记录级别 21 | 'level' => [], 22 | // 单文件日志写入 23 | 'single' => false, 24 | // 独立日志级别 25 | 'apart_level' => [], 26 | // 最大日志文件数量 27 | 'max_files' => 0, 28 | // 是否关闭日志写入 29 | 'close' => true, 30 | 31 | ]; 32 | -------------------------------------------------------------------------------- /config/middleware.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | 中间件配置 14 | // +---------------------------------------------------------------------- 15 | return [ 16 | // 默认中间件命名空间 17 | 'default_namespace' => 'app\\http\\middleware\\', 18 | 'ReflexValidate' => LinCmsTp\Param::class // 开启注释验证器,需要的中间件配置,请勿胡乱关闭 19 | ]; 20 | -------------------------------------------------------------------------------- /config/secure.php: -------------------------------------------------------------------------------- 1 | 'SDJxjkxc9o', 10 | 'refresh_token_salt' => 'SKTxigxc9o', 11 | ]; -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | 会话设置 14 | // +---------------------------------------------------------------------- 15 | 16 | return [ 17 | 'id' => '', 18 | // SESSION_ID的提交变量,解决flash上传跨域 19 | 'var_session_id' => '', 20 | // SESSION 前缀 21 | 'prefix' => 'think', 22 | // 驱动方式 支持redis memcache memcached 23 | 'type' => '', 24 | // 是否自动开启 SESSION 25 | 'auto_start' => true, 26 | ]; 27 | -------------------------------------------------------------------------------- /config/template.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | 模板设置 14 | // +---------------------------------------------------------------------- 15 | 16 | return [ 17 | // 模板引擎类型 支持 php think 支持扩展 18 | 'type' => 'Think', 19 | // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 20 | 'auto_rule' => 1, 21 | // 模板路径 22 | 'view_path' => '', 23 | // 模板后缀 24 | 'view_suffix' => 'html', 25 | // 模板文件名分隔符 26 | 'view_depr' => DIRECTORY_SEPARATOR, 27 | // 模板引擎普通标签开始标记 28 | 'tpl_begin' => '{', 29 | // 模板引擎普通标签结束标记 30 | 'tpl_end' => '}', 31 | // 标签库标签开始标记 32 | 'taglib_begin' => '{', 33 | // 标签库标签结束标记 34 | 'taglib_end' => '}', 35 | ]; 36 | -------------------------------------------------------------------------------- /config/token.php: -------------------------------------------------------------------------------- 1 | true, 11 | # 令牌算法类型 12 | 'algorithms' => 'HS256', 13 | # 令牌签发者 14 | 'issuer' => 'lin-cms-tp5', 15 | # accessToken秘钥 16 | 'access_secret_key' => 'w.kx(c82jkA', 17 | # accessToken过期时间,单位秒 18 | 'access_expire_time' => 7200, 19 | # refreshToken秘钥 20 | 'refresh_secret_key' => 'xUh.@3s8A8', 21 | # refreshToken过期时间,建议设置较长时间 22 | # 在有效期内可用于刷新accessToken,单位秒 23 | 'refresh_expire_time' => 604800, 24 | ]; -------------------------------------------------------------------------------- /config/trace.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // +---------------------------------------------------------------------- 13 | // | Trace设置 开启 app_trace 后 有效 14 | // +---------------------------------------------------------------------- 15 | return [ 16 | // 内置Html Console 支持扩展 17 | 'type' => 'Html', 18 | ]; 19 | -------------------------------------------------------------------------------- /error_msg.md: -------------------------------------------------------------------------------- 1 | # 错误码字典 2 | 3 | ## 参数类错误 4 | 99999 参数校验不通过 5 | 6 | ## 通用错误 7 | 10001 资源操作失败 8 | 10021 资源不存在 9 | 10071 资源已存在 10 | 11 | ## 令牌类错误 12 | 10000 令牌解析失败 13 | 10041 access令牌过期 14 | 10042 refresh令牌过期 15 | 10002 权限不足 16 | 10073 不允许添加用户到root分组 17 | 10078 不允许调整root分组信息 18 | 19 | ## 用户类错误 20 | 21 | 10030 密码错误 22 | 23 | ## 分组类错误 24 | ### 3000x 25 | 30001 分组创建失败 26 | 30002 分组权限编辑失败 27 | 30003 分组不存在 28 | 30004 分组已存在 29 | 30 | ## 服务端类错误 31 | ### 5000x 32 | 50000 服务环境配置错误 33 | 34 | ## 上传类错误 35 | ### 6000x 36 | 60000 不符合上传条件 37 | 60001 上传文件失败 38 | -------------------------------------------------------------------------------- /extend/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Options +FollowSymlinks -Multiviews 3 | RewriteEngine On 4 | 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] 8 | 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TaleLin/lin-cms-tp5/27f7429abc1f0e1f44ac9ca4a8a3450ea52ee18d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | // [ 应用入口文件 ] 13 | namespace think; 14 | 15 | // 加载基础文件 16 | require __DIR__ . '/../thinkphp/base.php'; 17 | 18 | // 支持事先使用静态方法设置Request对象和Config对象 19 | 20 | // 执行应用并响应 21 | Container::get('app')->run()->send(); 22 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/router.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | // $Id$ 12 | 13 | if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { 14 | return false; 15 | } else { 16 | require __DIR__ . "/index.php"; 17 | } 18 | -------------------------------------------------------------------------------- /public/static/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /route/route.php: -------------------------------------------------------------------------------- 1 | 10 | // +---------------------------------------------------------------------- 11 | 12 | use think\facade\Route; 13 | 14 | Route::get('v1/test', 'api/v1.Test/index'); 15 | 16 | Route::group('', function () { 17 | Route::group('cms', function () { 18 | // 账户相关接口分组 19 | Route::group('user', function () { 20 | // 登陆接口 21 | Route::post('login', 'api/cms.User/userLogin'); 22 | // 刷新令牌 23 | Route::get('refresh', 'api/cms.User/refreshToken'); 24 | // 查询自己拥有的权限 25 | Route::get('permissions', 'api/cms.User/getAllowedApis'); 26 | // 注册一个用户 27 | Route::post('register', 'api/cms.User/register'); 28 | // 查询自己信息 29 | Route::get('information', 'api/cms.User/getInformation'); 30 | // 用户更新信息 31 | Route::put('', 'api/cms.User/update'); 32 | // 修改自己密码 33 | Route::put('change_password', 'api/cms.User/changePassword'); 34 | }); 35 | // 管理类接口 36 | Route::group('admin', function () { 37 | // 查询所有可分配的权限 38 | Route::get('permission', 'api/cms.Admin/getAllPermissions'); 39 | // 查询所有用户 40 | Route::get('users', 'api/cms.Admin/getAdminUsers'); 41 | // 修改用户密码 42 | Route::put('user/:id/password', 'api/cms.Admin/changeUserPassword'); 43 | // 删除用户 44 | Route::delete('user/:id', 'api/cms.Admin/deleteUser'); 45 | // 更新用户信息 46 | Route::put('user/:id', 'api/cms.Admin/updateUser'); 47 | // 查询所有权限组 48 | Route::get('group/all', 'api/cms.Admin/getGroupAll'); 49 | // 新增权限组 50 | Route::post('group', 'api/cms.Admin/createGroup'); 51 | // 查询指定分组及其权限 52 | Route::get('group/:id', 'api/cms.Admin/getGroup'); 53 | // 更新一个权限组 54 | Route::put('group/:id', 'api/cms.Admin/updateGroup'); 55 | // 删除一个分组 56 | Route::delete('group/:id', 'api/cms.Admin/deleteGroup'); 57 | // 删除多个权限 58 | Route::post('permission/remove', 'api/cms.Admin/removePermissions'); 59 | // 分配多个权限 60 | Route::post('permission/dispatch/batch', 'api/cms.Admin/dispatchPermissions'); 61 | 62 | }); 63 | // 日志类接口 64 | Route::group('log', function () { 65 | Route::get('', 'api/cms.Log/getLogs'); 66 | Route::get('users', 'api/cms.Log/getUsers'); 67 | Route::get('search', 'api/cms.Log/getUserLogs'); 68 | }); 69 | //上传文件类接口 70 | Route::post('file', 'api/cms.File/postFile'); 71 | }); 72 | Route::group('v1', function () { 73 | Route::group('book', function () { 74 | // 查询所有图书 75 | Route::get('', 'api/v1.Book/getBooks'); 76 | // 新建图书 77 | Route::post('', 'api/v1.Book/create'); 78 | // 查询指定bid的图书 79 | Route::get(':bid', 'api/v1.Book/getBook'); 80 | // 搜索图书 81 | 82 | // 更新图书 83 | Route::put(':bid', 'api/v1.Book/update'); 84 | // 删除图书 85 | Route::delete(':bid', 'api/v1.Book/delete'); 86 | }); 87 | 88 | }); 89 | })->middleware(['Authentication', 'ReflexValidate'])->allowCrossDomain(); 90 | 91 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | SET NAMES utf8mb4; 2 | SET FOREIGN_KEY_CHECKS = 0; 3 | 4 | -- ---------------------------- 5 | -- 文件表 6 | -- ---------------------------- 7 | DROP TABLE IF EXISTS lin_file; 8 | CREATE TABLE lin_file 9 | ( 10 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 11 | path varchar(500) NOT NULL, 12 | type varchar(10) NOT NULL DEFAULT 'LOCAL' COMMENT 'LOCAL 本地,REMOTE 远程', 13 | name varchar(100) NOT NULL, 14 | extension varchar(50) DEFAULT NULL, 15 | size int(11) DEFAULT NULL, 16 | md5 varchar(40) DEFAULT NULL COMMENT 'md5值,防止上传重复文件', 17 | create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 18 | update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), 19 | delete_time datetime(3) DEFAULT NULL, 20 | PRIMARY KEY (id), 21 | UNIQUE KEY md5_del (md5, delete_time) 22 | ) ENGINE = InnoDB 23 | DEFAULT CHARSET = utf8mb4 24 | COLLATE = utf8mb4_general_ci; 25 | 26 | -- ---------------------------- 27 | -- 日志表 28 | -- ---------------------------- 29 | DROP TABLE IF EXISTS lin_log; 30 | CREATE TABLE lin_log 31 | ( 32 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 33 | message varchar(450) DEFAULT NULL, 34 | user_id int(10) unsigned NOT NULL, 35 | username varchar(24) DEFAULT NULL, 36 | status_code int(11) DEFAULT NULL, 37 | method varchar(20) DEFAULT NULL, 38 | path varchar(50) DEFAULT NULL, 39 | permission varchar(100) DEFAULT NULL, 40 | create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 41 | update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), 42 | delete_time datetime(3) DEFAULT NULL, 43 | PRIMARY KEY (id) 44 | ) ENGINE = InnoDB 45 | DEFAULT CHARSET = utf8mb4 46 | COLLATE = utf8mb4_general_ci; 47 | 48 | -- ---------------------------- 49 | -- 权限表 50 | -- ---------------------------- 51 | DROP TABLE IF EXISTS lin_permission; 52 | CREATE TABLE lin_permission 53 | ( 54 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 55 | name varchar(60) NOT NULL COMMENT '权限名称,例如:访问首页', 56 | module varchar(50) NOT NULL COMMENT '权限所属模块,例如:人员管理', 57 | mount tinyint(1) NOT NULL DEFAULT 1 COMMENT '0:关闭 1:开启', 58 | create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 59 | update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), 60 | delete_time datetime(3) DEFAULT NULL, 61 | PRIMARY KEY (id) 62 | ) ENGINE = InnoDB 63 | DEFAULT CHARSET = utf8mb4 64 | COLLATE = utf8mb4_general_ci; 65 | 66 | -- ---------------------------- 67 | -- 分组表 68 | -- ---------------------------- 69 | DROP TABLE IF EXISTS lin_group; 70 | CREATE TABLE lin_group 71 | ( 72 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 73 | name varchar(60) NOT NULL COMMENT '分组名称,例如:搬砖者', 74 | info varchar(255) DEFAULT NULL COMMENT '分组信息:例如:搬砖的人', 75 | level tinyint(2) NOT NULL DEFAULT 3 COMMENT '分组级别 1:root 2:guest 3:user(root、guest分组只能存在一个)', 76 | create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 77 | update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), 78 | delete_time datetime(3) DEFAULT NULL, 79 | PRIMARY KEY (id), 80 | UNIQUE KEY name_del (name, delete_time) 81 | ) ENGINE = InnoDB 82 | DEFAULT CHARSET = utf8mb4 83 | COLLATE = utf8mb4_general_ci; 84 | 85 | -- ---------------------------- 86 | -- 分组-权限表 87 | -- ---------------------------- 88 | DROP TABLE IF EXISTS lin_group_permission; 89 | CREATE TABLE lin_group_permission 90 | ( 91 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 92 | group_id int(10) unsigned NOT NULL COMMENT '分组id', 93 | permission_id int(10) unsigned NOT NULL COMMENT '权限id', 94 | PRIMARY KEY (id), 95 | KEY group_id_permission_id (group_id, permission_id) USING BTREE COMMENT '联合索引' 96 | ) ENGINE = InnoDB 97 | DEFAULT CHARSET = utf8mb4 98 | COLLATE = utf8mb4_general_ci; 99 | 100 | -- ---------------------------- 101 | -- 用户基本信息表 102 | -- ---------------------------- 103 | DROP TABLE IF EXISTS lin_user; 104 | CREATE TABLE lin_user 105 | ( 106 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 107 | username varchar(24) NOT NULL COMMENT '用户名,唯一', 108 | nickname varchar(24) DEFAULT NULL COMMENT '用户昵称', 109 | avatar varchar(500) DEFAULT NULL COMMENT '头像url', 110 | email varchar(100) DEFAULT NULL COMMENT '邮箱', 111 | create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 112 | update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), 113 | delete_time datetime(3) DEFAULT NULL, 114 | PRIMARY KEY (id), 115 | UNIQUE KEY username_del (username, delete_time), 116 | UNIQUE KEY email_del (email, delete_time) 117 | ) ENGINE = InnoDB 118 | DEFAULT CHARSET = utf8mb4 119 | COLLATE = utf8mb4_general_ci; 120 | 121 | -- ---------------------------- 122 | -- 用户授权信息表 123 | # id 124 | # user_id 125 | # identity_type 登录类型(手机号 邮箱 用户名)或第三方应用名称(微信 微博等) 126 | # identifier 标识(手机号 邮箱 用户名或第三方应用的唯一标识) 127 | # credential 密码凭证(站内的保存密码,站外的不保存或保存token) 128 | -- ---------------------------- 129 | DROP TABLE IF EXISTS lin_user_identity; 130 | CREATE TABLE lin_user_identity 131 | ( 132 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 133 | user_id int(10) unsigned NOT NULL COMMENT '用户id', 134 | identity_type varchar(100) NOT NULL, 135 | identifier varchar(100), 136 | credential varchar(100), 137 | create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 138 | update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), 139 | delete_time datetime(3) DEFAULT NULL, 140 | PRIMARY KEY (id) 141 | ) ENGINE = InnoDB 142 | DEFAULT CHARSET = utf8mb4 143 | COLLATE = utf8mb4_general_ci; 144 | 145 | DROP TABLE IF EXISTS book; 146 | CREATE TABLE book 147 | ( 148 | id int(11) NOT NULL AUTO_INCREMENT, 149 | title varchar(50) NOT NULL, 150 | author varchar(30) DEFAULT NULL, 151 | summary varchar(1000) DEFAULT NULL, 152 | image varchar(100) DEFAULT NULL, 153 | create_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 154 | update_time datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), 155 | delete_time datetime(3) DEFAULT NULL, 156 | PRIMARY KEY (id) 157 | ) ENGINE = InnoDB 158 | DEFAULT CHARSET = utf8mb4 159 | COLLATE = utf8mb4_general_ci; 160 | 161 | 162 | -- ---------------------------- 163 | -- 用户-分组表 164 | -- ---------------------------- 165 | DROP TABLE IF EXISTS lin_user_group; 166 | CREATE TABLE lin_user_group 167 | ( 168 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 169 | user_id int(10) unsigned NOT NULL COMMENT '用户id', 170 | group_id int(10) unsigned NOT NULL COMMENT '分组id', 171 | PRIMARY KEY (id), 172 | KEY user_id_group_id (user_id, group_id) USING BTREE COMMENT '联合索引' 173 | ) ENGINE = InnoDB 174 | DEFAULT CHARSET = utf8mb4 175 | COLLATE = utf8mb4_general_ci; 176 | 177 | SET FOREIGN_KEY_CHECKS = 1; 178 | 179 | -- ---------------------------- 180 | -- 插入超级管理员 181 | -- 插入root分组 182 | -- ---------------------------- 183 | BEGIN; 184 | INSERT INTO lin_user(id, username, nickname) 185 | VALUES (1, 'root', 'root'); 186 | 187 | INSERT INTO lin_user_identity (id, user_id, identity_type, identifier, credential) 188 | 189 | VALUES (1, 1, 'USERNAME_PASSWORD', 'root', 190 | 'e10adc3949ba59abbe56e057f20f883e'); 191 | 192 | INSERT INTO lin_group(id, name, info, level) 193 | VALUES (1, 'root', '超级用户组', 1); 194 | 195 | INSERT INTO lin_group(id, name, info, level) 196 | VALUES (2, 'guest', '游客组', 2); 197 | 198 | INSERT INTO lin_user_group(id, user_id, group_id) 199 | VALUES (1, 1, 1); 200 | 201 | COMMIT; 202 | -------------------------------------------------------------------------------- /think: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 11 | // +---------------------------------------------------------------------- 12 | 13 | namespace think; 14 | 15 | // 加载基础文件 16 | require __DIR__ . '/thinkphp/base.php'; 17 | 18 | // 应用初始化 19 | Container::get('app')->path(__DIR__ . '/application/')->initialize(); 20 | 21 | // 控制台初始化 22 | Console::init(); --------------------------------------------------------------------------------