This commit is contained in:
Wenbin.Wang 2021-12-24 16:40:05 +08:00
commit d49c9fde59
2828 changed files with 621149 additions and 0 deletions

838
CHANGELOG.md Normal file
View File

@ -0,0 +1,838 @@
## V5.1.34 LTS2019-1-30
本次更新为常规更新,修正了一些反馈的问题。
* 改进Request类的`has`方法,支持`patch`
* 改进`unique`验证的多条件支持
* 修复自定义上传验证,检测文件大小
* 改进`in`查询支持表达式
* 改进路由的`getBind`方法
* 改进验证类的错误信息获取
* 改进`response`助手函数默认值
* 修正mysql的`regexp`查询
* 改进模型类型强制转换写入对`Expression`对象的支持
## V5.1.33 LTS2019-1-16
* 修复路由中存在多个相同替换的正则BUG
* 修正whereLike查询
* join方法支持参数绑定
* 改进union方法
* 修正多对多关联的attach方法
* 改进验证类的正则规则自定义
* 改进Request类method方法
* 改进File日志类型的CLI日志写入
* 改进文件日志time_format配置对JSON格式的支持
## V5.1.32 LTS2018-12-24
本次主要为常规更新,修正了一些反馈的问题。
* 改进多对多关联的`attach`方法
* 改进聚合查询的`field`处理
* 改进关联的`save`方法
* 修正模型`exists`方法返回值
* 改进时间字段写入和输出
* 改进控制器中间件的调用
* 改进路由变量替换的性能
* 改进缓存标签的处理机制
## V5.1.31 LTS (2018-12-9)
本次版本包含一个安全更新,建议升级。
* 改进`field`方法
* 改进`count`方法返回类型
* `download`函数增加在浏览器中显示文件功能
* 修正多对多模型的中间表数据写入
* 改进`sqlsrv`驱动支持多个Schemas模式查询
* 统一助手函数与\think\response\Download函数文件过期时间
* 完善关联模型的`save`方法 增加`make`方法仅创建对象不保存
* 修改条件表达式对静态变量的支持
* 修正控制器名获取
* 改进view方法的`field`解析
## V5.1.30 LTS2018-11-30
该版本为常规更新,修正了一些社区反馈的问题。
主要更新如下:
* 改进查询类的`execute`方法
* 判断路由规则定义添加对请求类型的判断
* 修复`orderRaw`异常
* 修正 `optimize:autoload`指令
* 改进软删除的`destroy`方法造成重复执行事件的问题
* 改进验证类对扩展验证规则 始终验证 不管是否`require`
* 修复自定义验证`remove`所有规则的异常
* 改进时间字段的自动写入支持微秒数据
* 改进`Connection`类的`getrealsql`方法
* 修正`https`地址的URL生成
* 修复 `array_walk_recursive` 在低于PHP7.1消耗内部指针问题
* 改进手动参数绑定使用
* 改进聚合查询方法的`field`参数支持`Expression`
## V5.1.29 LTS2018-11-11
该版本主要改进了参数绑定的解析问题和提升性能,并修正了一些反馈的问题。
* 改进手动参数绑定
* 修正MISS路由的分组参数无效问题
* 行为支持对象的方法
* 修正全局查询范围
* 改进`belongsto`关联的`has`方法
* 改进`hasMany`关联
* 改进模型观察者多次注册的问题
* 改进`query`类的默认查询参数处理
* 修正`parseBetween`解析方法
* 改进路由地址生成的本地域名支持
* 改进参数绑定的实际URL解析性能
* 改进`Env`类的`getEnv`和`get`方法
* 改进模板缓存的生成优化
* 修复验证类的多语言支持
* 修复自定义场景验证`remove`规则异常
* File类添加是否自动补全扩展名的选项
* 改进`strpos`对子串是否存在的判断
* 修复`choice`无法用值选择第一个选项问题
* 验证器支持多维数组取值验证
* 改进解析`extend`和`block`标签的正则
## V5.1.28 LTS2018-10-29
该版本主要修正了上一个版本存在的一些问题,并改进了关联查询
* 改进聚合查询方法的字段支持DISTINCT
* 改进定义路由后url函数的端口生成
* 改进控制器中间件对`swoole`等的支持
* 改进Log类`save`方法
* 改进验证类的闭包验证参数
* 多对多关联支持指定中间表数据的名称
* 关联聚合查询支持闭包方式指定聚合字段
* 改进Lang类`get`方法
* 多对多关联增加判断关联数据是否存在的方法
* 改进关联查询使用`fetchsql`的情况
* 改进修改器的是否已经执行判断
* 增加`afterWith`和`beforeWith`验证规则 用于比较日期字段
## V5.1.27 LTS2018-10-22
该版本主要修正了路由绑定的参数改进了修改器的执行多次问题并正式宣布为LTS版本
* 修正路由绑定的参数丢失问题
* 修正路由别名的参数获取
* 改进修改器会执行多次的问题
## V5.1.262018-10-12
该版本主要修正了上一个版本的一些问题,并改进了全局查询范围的支持,同时包含了一个安全更新。
* 修正单一模块下注解路由无效的问题
* 改进数据库的聚合查询的字段处理
* 模型类增加`globalScope`属性定义 用于指定全局的查询范围
* 模型的`useGlobalScope`方法支持传入数组 用于指定当前查询需要使用的全局查询范围
* 改进数据集的`order`方法对数字类型的支持
* 修正上一个版本`order`方法解析的一处BUG
* 排序字段不合法或者错误的时候抛出异常
* 改进`Request`类的`file`方法对上传文件的错误判断
## V5.1.252018-9-21
该版本主要改进了查询参数绑定的性能和对浮点型的支持,以及一些细节的完善。
* 修正一处命令行问题
* 改进`Socketlog`日志驱动,支持自定义默认展开日志类别
* 修正`MorphMany`一处bug
* 跳转到上次记住的url并支持默认值
* 改进模型的异常提示
* 改进参数绑定对浮点型的支持
* 改进`order`方法解析
* 改进`json`字段数据的自动编码
* 改进日志`log_write`可能造成的日志写入死循环
* Log类增加`log_level`行为标签位置,用于对某个类型的日志进行处理
* Route类增加`clear`方法清空路由规则
* 分布式数据库配置支持使用数组
* 单日志文件也支持`max_files`参数
* 改进查询参数绑定的性能
* 改进别名路由的URL后缀参数检测
* 控制器前置方法和控制器中间件的`only`和`except`定义不区分大小写
## V5.1.242018-9-5
该版本主要增加了命令行的表格输出功能,并增加了查看路由定义的指令,以及修正了社区的一些反馈问题。
* 修正`Request`类的`file`方法
* 修正路由的`cache`方法
* 修正路由缓存的一处问题
* 改进上传文件获取的异常处理
* 改进`fetchCollection`方法支持传入数据集类名
* 修正多级控制器的注解路由生成
* 改进`Middleware`类`clear`方法
* 增加`route:list`指令用于[查看定义的路由](752690) 并支持排序
* 命令行增加`Table`输出类
* `Command`类增加`table`方法用于输出表格
* 改进搜索器查询方法支持别名定义
* 命令行配置增加`auto_path`参数用于定义自动载入的命令类路径
* 增加`make:command`指令用于[快速生成指令](354146)
* 改进`make:controller`指令对操作方法后缀的支持
* 改进命令行的定义文件支持索引数组 用于指令对象的惰性加载
* 改进`value`和`column`方法对后续查询结果的影响
* 改进`RuleName`类的`setRule`方法
## V5.1.232018-8-23
该版本主要改进了数据集对象的处理,增加了`findOrEmpty`方法并且修正了一些社区反馈的BUG。
* 数据集类增加`diff`/`intersect`方法用于获取差集和交集(默认根据主键值比较)
* 数据集类增加`order`方法支持指定字段排序
* 数据集类增加`map`方法使用回调函数处理数据并返回新的数据集对象
* Db增加`allowEmpty`方法允许`find`方法在没有数据的时候返回空数组或者空模型对象而不是null
* Db增加`findOrEmpty`方法
* Db增加`fetchCollection`方法用于指定查询返回数据集对象
* 改进`order`方法的数组方式解析,增强安全性
* 改进`withSearch`方法,支持第三个参数传入字段前缀标识,用于多表查询字段搜索
* 修正`optimize:route`指令开启类库后缀后的注解路由生成
* 修正redis缓存及session驱动
* 支持指定`Yaconf`的独立配置文件
* 增加`yaconf`助手函数用于配置文件
## V5.1.222018-8-9
该版本主要增加了模型搜索器和`withJoin`方法,完善了模型输出和对`Yaconf`的支持修正了一些社区反馈的BUG。
* 改进一对一关联的`table`识别问题
* 改进内置`Facade`类
* 增加`withJoin`方法支持`join`方式的[一对一关联](一对一关联.md)查询
* 改进`join`预载入查询的空数据问题
* 改进`Config`类的`load`方法支持快速加载配置文件
* 改进`execute`方法和事务的断线重连
* 改进`memcache`驱动的`has`方法
* 模型类支持定义[搜索器](搜索器.md)方法
* 完善`Config`类对`Yaconf`的支持
* 改进模型的`hidden/visible/append/withAttr`方法,支持在[查询前后调用](数组访问.md),以及支持数据集对象
* 数据集对象增加`where`方法根据字段或者关联数据[过滤数据](模型数据集.md)
* 改进AJAX请求的`204`判断
## V5.1.212018-8-2
该版本主要增加了下载响应对象和数组查询对象的支持,并修正了一些社区反馈的问题。
* 改进核心对象的无用信息调试输出
* 改进模型的`isRelationAttr`方法判断
* 模型类的`get`和`all`方法并入Db类
* 增加[下载响应对象](文件下载.md)和`download`助手函数
* 修正别名路由配置定义读取
* 改进`resultToModel`方法
* 修正开启类库后缀后的注解路由生成
* `Response`类增加`noCache`快捷方法
* 改进路由对象在`Swoole`/`Workerman`下面参数多次合并问题
* 修正路由`ajax`/`pjax`参数后路由变量无法正确获取的问题
* 增加清除中间件的方法
* 改进依赖注入的参数规范自动识别(便于对接前端小写+下划线规范)
* 改进`hasWhere`的数组条件的字段判断
* 增加[数组查询对象](高级查询.md)`Where`支持(喜欢数组查询的福音)
* 改进多对多关联的闭包支持
## V5.1.202018-7-25
该版本主要增加了Db和模型的动态获取器的支持并修正了一些已知问题。
* Db类添加[获取器支持](703981)
* 支持模型及关联模型字段[动态定义获取器](354046)
* 动态获取器支持`JSON`字段
* 改进路由的`before`行为执行(匹配后执行)
* `Config`类支持`Yaconf`
* 改进Url生成的端口问题
* Request类增加`setUrl`和`setBaseUrl`方法
* 改进页面trace的信息显示
* 修正`MorphOne`关联
* 命令行添加[查看版本指令](703994)
## V5.1.19 2018-7-13
该版本是一个小幅改进版本,针对`Swoole`和`Workerman`的`Cookie`支持做了一些改进,并修正了一些已知的问题。
* 改进query类`delete`方法对软删除条件判断
* 修正分表查询的软删除问题
* 模型查询的时候同时传入`table`和`name`属性
* 容器类增加`IteratorAggregate`和`Countable`接口支持
* 路由分组支持对下面的资源路由统一设置`only/except/vars`参数
* 改进Cookie类更好支持扩展
* 改进Request类`post`方法
* 改进模型自关联的自动识别
* 改进Request类对`php://input`数据的处理
## V5.1.18 2018-6-30
该版本主要完善了对`Swoole`和`Workerman`的`HttpServer`运行支持,改进`Request`类,并修正了一些已知的问题。
* 改进关联`append`方法的处理
* 路由初始化和检测方法分离
* 修正`destroy`方法强制删除
* `app_init`钩子位置移入`run`方法
* `think-swoole`扩展更新到2.0版本
* `think-worker`扩展更新到2.0版本
* 改进Url生成的域名自动识别
* `Request`类增加`setPathinfo`方法和`setHost`方法
* `Request`类增加`withGet`/`withPost`/`withHeader`/`withServer`/`withCookie`/`withEnv`方法进行赋值操作
* Route类改进`host`属性的获取
* 解决注解路由配置不生效的问题
* 取消Test日志驱动改为使用`close`设置关闭全局日志写入
* 修正路由的`response`参数
* 修正204响应输出的判断
## V5.1.17 2018-6-18
该版本主要增加了控制器中间件的支持,改进了路由功能,并且修正了社区反馈的一些问题。
* 修正软删除的`delete`方法
* 修正Query类`Count`方法
* 改进多对多`detach`方法
* 改进Request类`Session`方法
* 增加控制器中间件支持
* 模型类增加`jsonAssoc`属性用于定义json数据是否返回数组
* 修正Request类`method`方法的请求伪装
* 改进静态路由的匹配
* 分组首页路由自动完整匹配
* 改进sqlsrv的`column`方法
* 日志类的`apart_level`配置支持true自动生成对应类型的日志文件
* 改进`204`输出判断
* 修正cli下页面输出的BUG
* 验证类使用更高效的`ctype`验证机制
* 改进Request类`cookie`方法
* 修正软删除的`withTrashed`方法
* 改进多态一对多的预载入查询
* 改进Query类`column`方法的缓存读取
* Query类增加`whereBetweenTimeField`方法
* 改进分组下多个相同路由规则的合并匹配问题
* 路由类增加`getRule`/`getRuleList`方法获取定义的路由
## V5.1.16 2018-6-7
该版本主要修正了社区反馈的一些问题并对Request类做了进一步规范和优化。
* 改进Session类的`boot`方法
* App类的初始化方法可以单独执行
* 改进Request类的`param`方法
* 改进资源路由的变量替换
* Request类增加`__isset`方法
* 改进`useGlobalScope`方法对软删除的影响
* 修正命令行调用
* 改进Cookie类`init`方法
* 改进多对多关联删除的返回值
* 一对多关联写入支持`replace`
* 路由增加`filter`检测方法,用于通过请求参数检测路由是否匹配
* 取消Request类`session/env/server`方法的`filter`参数
* 改进关联的指定属性输出
* 模型删除操作删除后不清空对象数据仅作标记
* 调整模型的`save`方法返回值为布尔值
* 修正Request类`isAjax`方法
* 修正中间件的模块配置读取
* 取消Request类的请求变量的设置功能
* 取消请求变量获取的默认修饰符
* Request类增加`setAction/setModule/setController`方法
* 关联模型的`delete`方法调用Query类
* 改进URL生成的域名识别
* 改进URL检测对已定义路由的域名判断
* 模型类增加`isExists`和`isForce`方法
* 软删除的`destroy`和`restore`方法返回值调整为布尔值
## V5.1.15 2018-6-1
该版本主要改进了路由缓存的性能和缓存方式设置增加了JSON格式文件日志的支持并修正了社区反馈的一些问题。
* 容器类增加`exists`方法 仅判断是否存在对象实例
* 取消配置类的`autoload`方法
* 改进路由缓存大小提高性能
* 改进Dispatch类`init`方法
* 增加`make:validate`指令生成验证器类
* Config类`get`方法支持默认值参数
* 修正字段缓存指令
* 改进App类对`null`数据的返回
* 改进模型类的`__isset`方法判断
* 修正`Query`类的`withAggregate`方法
* 改进`RuleItem`类的`setRuleName`方法
* 修正依赖注入和参数的冲突问题
* 修正Db类对第三方驱动的支持
* 修正模型类查询对象问题
* 修正File缓存驱动的`has`方法
* 修正资源路由嵌套
* 改进Request类对`$_SERVER`变量的读取
* 改进请求缓存处理
* 路由缓存支持指定单独的缓存方式和参数
* 修正资源路由的中间件多次执行问题
* 修正`optimize:config`指令
* 文件日志支持`JSON`格式日志保存
* 修正Db类`connect`方法
* 改进Log类`write`方法不会自动写入之前日志
* 模型的关联操作默认启用事务
* 改进软删除的事件响应
## V5.1.14 2018-5-18
该版本主要对底层容器进行了一些优化改进,并增加了路由缓存功能,可以进一步提升路由性能。
* 依赖注入的对象参数传入改进
* 改进核心类的容器实例化
* 改进日期字段的读取
* 改进验证类的`getScene`方法
* 模型的`create`方法和`save`方法支持`replace`操作
* 改进`Db`类的调用机制
* App类调整为容器类
* 改进容器默认绑定
* `Loader`类增加工厂类的实例化方法
* 增加路由变量默认规则配置参数
* 增加路由缓存设计
* 错误处理机制改进
* 增加清空路由缓存指令
## V5.1.13 2018-5-11
该版本主要增加了MySQL的XA事务支持模型事件支持观察者以及对Facade类的改进。
* 改进自动缓存
* 改进Url生成
* 修正数据缓存
* 修正`value`方法的缓存
* `join`方法和`view`方法的条件支持使用`Expression`对象
* 改进驱动的`parseKey`方法
* 改进Request类`host`方法和`domain`方法对端口的处理
* 模型增加`withEvent`方法用于控制当前操作是否需要执行模型事件
* 模型`setInc/setDec`方法支持更新事件
* 模型添加`before_restore/after_restore`事件
* 增加模型事件观察者
* 路由增加`mobile`方法设置是否允许手机访问
* 数据库XA事务支持
* 改进索引数组查询对`IN`查询的支持
* 修正`invokeMethod`方法
* 修正空数据写入返回值的BUG
* redis驱动支持`predis`
* 改进`parseData`方法
* 改进模块加载
* App类初始化方法调整
* 改进数组查询对表达式`Expression`对象支持
* 改进闭包的依赖注入调用
* 改进多对多关联的中间表模型更新
* 增加容器中对象的自定义实例化
## V5.1.12 2018-4-25
该版本主要改进了主从查询的及时性,并支持动态设置请求数据。
* 支持动态设置请求数据
* 改进`comment`方法解析
* 修正App类`__unset`方法
* 改进url生成的域名绑定
* 改进主从查询的及时性
* 修正`value`的数据缓存功能
* 改进分页类的集合对象方法调用
* 改进Db类的代码提示
* SQL日志增加主从标记
## V5.1.11 2018-4-19
该版本为安全和修正版本改进了JSON查询的参数绑定问题和容器类对象实例获取并包含一处可能的安全隐患建议更新。
* 支持指定JSON数据查询的字段类型
* 修正`selectInsert`方法
* `whereColumn`方法支持数组方式
* 改进容器类`make`方法
* 容器类`delete`方法支持数组
* 改进`composer`自动加载
* 改进模板引擎
* 修正`like`查询的一处安全隐患
## V5.1.10 2018-4-16
该版本为修正版本修正上一个版本的一些BUG并增强了`think clear`指令。
* 改进`orderField`方法
* 改进`exists`查询
* 修改cli模式入口文件位置计算
* 修正`null`查询
* 改进`parseTime`方法
* 修正关联预载入查询
* 改进`mysql`驱动
* 改进`think clear`指令 支持 `-c -l -r `选项
* 改进路由规则对`/`结尾的支持
## V5.1.9 2018-4-12
该版本主要是一些改进和修正,并包含一个安全更新,是一个推荐更新版本。
* 默认模板渲染规则支持配置保持操作方法名
* 改进`Request`类的`ip`方法
* 支持模型软删除字段的默认值定义
* 改进路由变量规则对中文的支持
* 使用闭包查询的时候使用`cache(true)` 抛出异常提示
* 改进`Loader`类`loadComposerAutoloadFiles`方法
* 改进查询方法安全性
* 修正路由地址中控制器名驼峰问题
* 调整上一个版本的`module_init`和`app_begin`的钩子顺序问题
* 改进CLI命令行执行的问题
* 修正社区反馈的其它问题
## V5.1.8 2018-4-5
该版本主要改进了中间件的域名和模块支持,并同时修正了几个已知问题。
* 增加`template.auto_rule` 参数设置默认模板渲染的操作名自动转换规则
* 默认模板渲染规则改由视图驱动实现
* 修正路由标识定义
* 修正控制器路由方法
* 改进Request类`ip`方法支持自定义代理IP参数
* 路由注册中间件支持数组方式别名
* 改进命令行执行下的`composer`自动加载
* 添加域名中间件注册支持
* 全局中间件支持模块定义文件
* Log日志配置支持`close`参数可以全局关闭日志写入
* 中间件方法中捕获`HttpResponseException`异常
* 改进中间件的闭包参数传入
* 改进分组路由的延迟解析
* 改进URL生成对域名绑定的支持
* 改进文件缓存和文件日志驱动的并发支持
## V5.1.7 2018-3-28
该版本主要修正了路由的一些问题,并改进了查询的安全性。
* 支持`middleware`配置文件预先定义中间件别名方便路由调用
* 修正资源路由
* 改进`field`方法 自动识别`fieldRaw`
* 增加`Expression`类
* Query类增加`raw`方法
* Query类的`field`/ `order` 和` where`方法都支持使用`raw`表达式查询
* 改进`inc/dec`查询 支持批量更新
* 改进路由分组
* 改进Response类`create`方法
* 改进composer自动加载
* 修正域名路由的`append`方法
* 修正操作方法的初始化方法获取不到问题
## V5.1.6 2018-3-26
该版本主要改进了路由规则的匹配算法,大幅提升了路由性能。并正式引入了中间件的支持,可以在路由中定义或者全局定义。另外包含了一个安全更新,是一个建议更新版本。
* 改进URL生成对路由`ext`方法的支持
* 改进查询缓存对不同数据库相同表名的支持
* 改进composer自动加载的性能
* 改进空路由变量对默认参数的影响
* mysql的`json`字段查询支持多级
* Query类增加`option`方法
* 优化路由匹配
* 修复验证规则数字键名丢失问题
* 改进路由Url生成
* 改进一对一关联预载入查询
* Request类增加`rootDomain`方法
* 支持API资源控制器生成 `make:controller --api`
* 优化Template类的标签解析
* 容器类增加删除和清除对象实例的方法
* 修正MorphMany关联的`eagerlyMorphToMany`方法一处错误
* Container类的异常捕获改进
* Domain对象支持`bind`方法
* 修正分页参数
* 默认模板的输出规则不受URL影响
* 注解路由支持多级控制器
* Query类增加`getNumRows`方法获取前次操作影响的记录数
* 改进查询条件的性能
* 改进模型类`readTransform`方法对序列化类型的处理
* Log类增加`close`方法可以临时关闭当前请求的日志写入
* 文件日志方式增加自动清理功能(设置`max_files`参数)
* 修正Query类的`getPk`方法
* 修正模板缓存的布局开关问题
* 修正Query类`select`方法的缓存
* 改进input助手函数
* 改进断线重连的信息判断
* 改进正则验证方法
* 调整语言包的加载顺序 放到`app_init`之前
* controller类`fetch`方法改为`final`
* 路由地址中的变量支持使用`<var>`方式
* 改进XMLResponse 支持传入编码过的xml内容
* 修正Query类`view`方法的数组表名支持
* 改进路由的模型闭包绑定
* 改进分组变量规则的继承
* 改进`cli-server`模式下的`composer`自动加载
* 路由变量规则异常捕获
* 引入中间件支持
* 路由定义增加`middleware`方法
* 增加生成中间件指令`make:middleware`
* 增加全局中间件定义支持
* 改进`optimize:config`指令对全局中间件的支持
* 改进config类`has`方法
* 改进时间查询的参数绑定
* 改进`inc/dec/exp`查询的安全性
## V5.1.5 2018-1-31
该版本主要增强了数据库的JSON查询并支持JSON字段的聚合查询改进了一些性能问题修正了路由的一些BUG主要更新如下
* 改进数据集查询对`JSON`数据的支持
* 改进聚合查询对`JSON`字段的支持
* 模型类增加`getOrFail`方法
* 改进数据库驱动的`parseKey`方法
* 改进Query类`join`方法的自关联查询
* 改进数据查询不存在不生成查询缓存
* 增加`run`命令行指令启动内置服务器
* `Request`类`pathinfo`方法改进对`cli-server`支持
* `Session`类增加`use_lock`配置参数设置是否启用锁机制
* 优化`File`缓存自动生成空目录的问题
* 域名及分组路由支持`append`方法传递隐式参数
* 改进日志的并发写入问题
* 改进`Query`类的`where`方法支持传入`Query`对象
* 支持设置单个日志文件的文件名
* 修正路由规则的域名条件约束
* `Request`类增加`subDomain`方法用于获取当前子域名
* `Response`类增加`allowCache`方法控制是否允许请求缓存
* `Request`类增加`sendData`方法便于扩展
* 改进`Env`类不依赖`putenv`方法
* 改进控制台`trace`显示错误
* 改进`MorphTo`关联
* 改进完整路由匹配后带斜线访问出错的情况
* 改进路由的多级分组问题
* 路由url地址生成支持多级分组
* 改进路由Url生成的`url_convert`参数的影响
* 改进`miss`和`auto`路由内部解析
* 取消预载入关联查询缓存功能
## V5.1.4 2018-1-19
该版本主要增强了数据库和模型操作,主要更新如下:
* 支持设置 `deleteTime`属性为`false` 关闭软删除
* 模型增加`getError`方法
* 改进Query类的`getTableFields`/`getFieldsType`方法 支持表名自动获取
* 模型类`toCollection`方法增加参数指定数据集类
* 改进`union`查询
* 关联预载入`with`方法增加缓存参数
* 改进模型类的`get`和`all`方法的缓存 支持关联缓存
* 支持`order by field`操作
* 改进`insertAll`分批写入
* 改进`json`字段数据支持
* 增加JSON数据的模型对象化操作
* 改进路由`ext`参数检测
* 修正`rule`方法的`method`参数使用 `get|post` 方式注册路由的问题
## V5.1.3 2018-1-12
该版本主要改进了路由及调整函数加载顺序,主要更新如下:
* 增加`env`助手函数;
* 增加`route`助手函数;
* 增加视图路由方法;
* 增加路由重定向方法;
* 路由默认区分最后的目录斜杆(支持设置不区分);
* 调整公共文件和配置文件的加载顺序(可以在配置文件中直接使用助手函数);
* 视图类增加`filter`方法设置输出过滤;
* `view`助手函数增加`filter`参数;
* 改进缓存生成指令;
* Session类的`get`方法支持获取多级;
* Request类`only`方法支持指定默认值;
* 改进路由分组;
* 修正使用闭包查询的时候自动数据缓存出错的情况;
* 废除`view_filter`钩子位置;
* 修正分组下面的资源路由;
* 改进session驱动;
## V5.1.2 2018-1-8
该版本改进了配置类及数据库类,主要更新如下:
* 修正嵌套路由分组;
* 修正自定义模板标签界定符后表达式语法出错的情况;
* 修正自关联的多次调用问题;
* 修正数组查询的`null`条件查询;
* 修正Query类的`order`及`field`的一处可能的BUG
* 配置参数设置支持三级;
* 配置对象支持`ArrayAccess`
* App类增加`path`方法用于设置应用目录;
* 关联定义增加`selfRelation`方法用于设置是否为自关联;
## V5.1.1 2018-1-3
修正一些反馈的BUG包括
* 修正Cookie类存取数组的问题
* 修正Controller的`fetch`方法
* 改进跨域请求
* 修正`insertAll`方法
* 修正`chunk`方法
## V5.1.0 2018-1-1
主要更新如下:
* 增加注解路由支持
* 路由支持跨域请求设置
* 增加`app_dispatch`钩子位置
* 修正多对多关联的`detach`方法
* 修正软删除的`destroy`方法
* Cookie类`httponly`参数默认为false
* 日志File驱动增加`single`参数配置记录同一个文件(不按日期生成)
* 路由的`ext`和`denyExt`方法支持不传任何参数
* 改进模型的`save`方法对`oracle`的支持
* Query类的`insertall`方法支持配合`data`和`limit`方法
* 增加`whereOr`动态查询支持
* 日志的ip地址记录改进
* 模型`saveAll`方法支持`isUpdate`方法
* 改进`Pivot`模型的实例化操作
* 改进Model类的`data`方法
* 改进多对多中间表模型类
* 模型增加`force`方法强制更新所有数据
* Hook类支持设置入口方法名称
* 改进验证类
* 改进`hasWhere`查询的数据重复问题
* 模型的`saveall`方法返回数据集对象
* 改进File缓存的`clear`方法
* 缓存添加统一的序列化机制
* 改进泛三级域名的绑定
* 改进泛域名的传值和取值
* Request类增加`panDomain`方法
* 改进废弃字段判断
* App类增加`create`方法用于实例化应用类库
* 容器类增加`has`方法
* 改进多数据库切换连接
* 改进断线重连的异常捕获
* 改进模型类`buildQuery`方法
* Query类增加`unionAll`方法
* 关联统计功能增强支持Sum/Max/Min/Avg
* 修正延迟写入
* chunk方法支持复合主键
* 改进JSON类型的写入
* 改进Mysql的insertAll方法
* Model类`save`方法改进复合主键包含自增的情况
* 改进Query类`inc`和`dec`方法的关键字处理
* File缓存inc和dec方法保持原来的有效期
* 改进redis缓存的有效期判断
* 增加checkRule方法用于单独数据的多个验证规则
* 修正setDec方法的延迟写入
* max和min方法增加force参数
* 二级配置参数区分大小写
* 改进join方法自关联的问题
* 修正关联模型自定义表名的情况
* Query类增加getFieldsType和getTableFields方法
* 取消视图替换功能及view_replace_str配置参数
* 改进域名绑定模块后的额外路由规则问题
* 改进mysql的insertAll方法
* 改进insertAll方法写入json字段数据的支持
* 改进redis长连接多编号库的情况
## RC3版本2017-11-6
主要更新如下:
* 改进redis驱动的`get`方法
* 修正Query类的`alias`方法
* `File`类错误信息支持多语言
* 修正路由的额外参数解析
* 改进`whereTime`方法
* 改进Model类`getAttr`方法
* 改进App类的`controller`和`validate`方法支持多层
* 改进`HasManyThrough`类
* 修正软删除的`restore`方法
* 改进`MorpthTo`关联
* 改进数据库驱动类的`parseKey`方法
* 增加`whereField`动态查询方法
* 模型增加废弃字段功能
* 改进路由的`after`行为检查和`before`行为机制
* 改进路由分组的检查
* 修正mysql的`json`字段查询
* 取消Connection类的`quote`方法
* 改进命令行的支持
* 验证信息支持多语言
* 修正路由模型绑定
* 改进参数绑定类型对枚举类型的支持
* 修正模板的`{$Think.version} `输出
* 改进模板`date`函数解析
* 改进`insertAll`方法支持分批执行
* Request类`host`方法支持反向代理
* 改进`JumpResponse`支持区分成功和错误模板
* 改进开启类库后缀后的关联外键自动识别问题
* 修正一对一关联的JOIN方式预载入查询问题
* Query类增加`hidden`方法
## RC2版本2017-10-17
主要更新如下:
* 修正视图查询
* 修正资源路由
* 修正`HasMany`关联 修正`where`方法的闭包查询
* 一对一关联绑定属性到父模型后 关联属性不再保留
* 修正应用的命令行配置文件读取
* 改进`Connection`类的`getCacheKey`方法
* 改进文件上传的非法图像异常
* 改进验证类的`unique`规则
* Config类`get`方法支持获取一级配置
* 修正count方法对`fetchSql`的支持
* 修正mysql驱动对`socket`支持
* 改进Connection类的`getRealSql`方法
* 修正`view`助手函数
* Query类增加`leftJoin` `rightJoin``fullJoin`方法
* 改进app_namespace的获取
* 改进`append`方法对一对一`bind`属性的支持
* 改进关联的`saveall`方法的返回值
* 路由标识设置异常修复
* 改进Route类`rule`方法
* 改进模型的`table`属性设置
* 改进composer autofile的加载顺序
* 改进`exception_handle`配置对闭包的支持
* 改进app助手函数增加参数
* 改进composer的加载路径判断
* 修正路由组合变量的URL生成
* 修正路由URL生成
* 改进`whereTime`查询并支持扩展规则
* File类的`move`方法第二个参数支持`false`
* 改进Config类
* 改进缓存类`remember`方法
* 惯例配置文件调整 Url类当普通模式参数的时候不做`urlencode`处理
* 取消`ROOT_PATH`和`APP_PATH`常量定义 如需更改应用目录 自己重新定义入口文件
* 增加`app_debug`的`Env`获取
* 修正泛域名绑定
* 改进查询表达式的解析机制
* mysql增加`regexp`查询表达式 支持正则查询
* 改进查询表达式的异常判断
* 改进model类的`destroy`方法
* 改进Builder类 取消`parseValue`方法
* 修正like查询的参数绑定问题
* console和start文件移出核心纳入应用库
* 改进Db类主键删除方法
* 改进泛域名绑定模块
* 取消`BIND_MODULE`常量 改为在入口文件使用`bind`方法设置
* 改进数组查询
* 改进模板渲染的异常处理
* 改进控制器基类的架构方法参数
* 改进Controller类的`success`和`error`方法
* 改进对浏览器`JSON-Handle`插件的支持
* 优化跳转模板的移动端显示
* 修正模型查询的`chunk`方法对时间字段的支持
* 改进trace驱动
* Collection类增加`push`方法
* 改进Redis Session驱动
* 增加JumpResponse驱动
## RC12017-9-8
主要新特性为:
* 引入容器和Facade支持
* 依赖注入完善和支持更多场景
* 重构的(对象化)路由
* 配置和路由目录独立
* 取消系统常量
* 助手函数增强
* 类库别名机制
* 模型和数据库增强
* 验证类增强
* 模板引擎改进
* 支持PSR-3日志规范
* RC1版本取消了5.0多个字段批量数组查询的方式

32
README.md Normal file
View File

@ -0,0 +1,32 @@
DolphinPHP
===============
DophinPHP海豚PHP是一个基于ThinkPHP5.1.34 LTS开发的一套开源PHP快速开发框架DophinPHP秉承极简、极速、极致的开发理念为开发集成了基于数据-角色的权限管理机制,集成多种灵活快速构建工具,可方便快速扩展的模块、插件、钩子、数据包。统一了模块、插件、钩子、数据包之间的版本和依赖关系,进一步降低了代码和数据的沉余,以方便开发者快速构建自己的应用。
## 功能特性
### 万事俱备-ZBuilder构建神器
使用DophinPHP海豚PHP自主开发的ZBuilder类您可以轻松的应对复杂多变的表单、数据列表。数据列表集成类似EXCEL的快速筛选、排序、模糊搜索、AJAX编辑等功能表单页集成常用的文本、下拉框、单选、多选、关键词、编辑器、文件上传、图片上传、图片裁切等控件除此之外您还可以灵活的扩展自己的控件以便在自己的项目中重复使用。ZBuilder让您更加专注业务逻辑。
### 相得益彰-模块化组合
千变万化的事物之间总是有着千丝万缕的关系,在应用开发的时候,不同的功能、模块甚至数据之间也存在各种依赖关系。在保证模块、插件独立的同时,为了降低代码、数据的沉余,我们将每个模块、插件、数据包加上了唯一的标示和版本号以及彼此之间的依赖关系,保证了应用程序的高内聚低耦合。
### 一举多得-夸平台支持
DophinPHP海豚PHP集成BootStrap、JQuery、Xeditable、Select2等优秀的前端开源框架基于ThinkPHP5出色的性能和REST支持、远程调试和更好的支持API开发。让您可以一次开发即可直接兼容PC、移动设备和微信界面通过简单的配置即可快速构建属于自己的Android、IOS的APP。
## 鸣谢
感谢[ThinkPHP](http://www.thinkphp.cn)、[JQuery](http://jquery.com/)、[Bootstrap](http://getbootstrap.com/)、[Xeditable](http://vitalets.github.io/x-editable)、[Select2](https://github.com/select2/select2)等优秀开源项目。
## 官方网站
[www.dolphinphp.com](http://www.dolphinphp.com)
## 版权信息
DolphinPHP提供个人非商业用途免费使用商业需授权。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2016-2019 广东卓锐软件有限公司 (http://www.zrthink.com)
All rights reserved。
更多细节参阅 [LICENSE.txt](LICENSE.txt)

1
application/.htaccess Normal file
View File

@ -0,0 +1 @@
deny from all

View File

@ -0,0 +1,65 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Action as ActionModel;
use app\admin\model\Module as ModuleModel;
/**
* 行为管理控制器
* @package app\admin\controller
*/
class Action extends Admin
{
/**
* 首页
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\DbException
*/
public function index()
{
// 查询
$map = $this->getMap();
// 数据列表
$data_list = ActionModel::where($map)->order('id desc')->paginate();
// 所有模块的名称和标题
$list_module = ModuleModel::getModule();
// 新增或编辑页面的字段
$fields = [
['hidden', 'id'],
['select', 'module', '所属模块', '', $list_module],
['text', 'name', '行为标识', '由英文字母和下划线组成'],
['text', 'title', '行为名称', ''],
['textarea', 'remark', '行为描述'],
['textarea', 'rule', '行为规则', '不写则只记录日志'],
['textarea', 'log', '日志规则', '记录日志备注时按此规则来生成,支持[变量|函数]。目前变量有user,time,model,record,data,details'],
['radio', 'status', '立即启用', '', ['否', '是'], 1]
];
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setPageTitle('行为管理') // 设置页面标题
->setSearch(['name' => '标识', 'title' => '名称']) // 设置搜索框
->addColumns([ // 批量添加数据列
['id', 'ID'],
['name', '标识'],
['title', '名称'],
['remark', '描述'],
['module', '所属模块', 'callback', function($module, $list_module){
return isset($list_module[$module]) ? $list_module[$module] : '未知';
}, $list_module],
['status', '状态', 'switch'],
['right_button', '操作', 'btn']
])
->autoAdd($fields, '', true, true) // 添加自动新增按钮
->autoEdit($fields, '', true, true) // 添加自动编辑按钮
->addTopButtons('enable,disable,delete') // 批量添加顶部按钮
->addRightButtons('delete') // 批量添加右侧按钮
->addFilter('module', $list_module)
->setRowList($data_list) // 设置表格数据
->fetch(); // 渲染模板
}
}

View File

@ -0,0 +1,499 @@
<?php
namespace app\admin\controller;
use app\common\controller\Common;
use app\common\builder\ZBuilder;
use app\admin\model\Menu as MenuModel;
use app\admin\model\Module as ModuleModel;
use app\admin\model\Icon as IconModel;
use app\user\model\Role as RoleModel;
use app\user\model\Message as MessageModel;
use think\facade\Cache;
use think\Db;
use think\facade\App;
use think\helper\Hash;
/**
* 后台公共控制器
* @package app\admin\controller
*/
class Admin extends Common
{
/**
* 初始化
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
*/
protected function initialize()
{
parent::initialize();
// 是否拒绝ie浏览器访问
if (config('system.deny_ie') && get_browser_type() == 'ie') {
$this->redirect('admin/ie/index');
}
// 判断是否登录并定义用户ID常量
defined('UID') or define('UID', $this->isLogin());
// 设置当前角色菜单节点权限
role_auth();
// 检查权限
if (!RoleModel::checkAuth()) $this->error('权限不足!');
// 设置分页参数
$this->setPageParam();
// 如果不是ajax请求则读取菜单
if (!$this->request->isAjax()) {
// 读取顶部菜单
$this->assign('_top_menus', MenuModel::getTopMenu(config('top_menu_max'), '_top_menus'));
// 读取全部顶级菜单
$this->assign('_top_menus_all', MenuModel::getTopMenu('', '_top_menus_all'));
// 获取侧边栏菜单
$this->assign('_sidebar_menus', MenuModel::getSidebarMenu());
// 获取面包屑导航
$this->assign('_location', MenuModel::getLocation('', true));
// 获取当前用户未读消息数量
$this->assign('_message', MessageModel::getMessageCount());
// 获取自定义图标
$this->assign('_icons', IconModel::getUrls());
// 构建侧栏
$data = [
'table' => 'admin_config', // 表名或模型名
'prefix' => 1,
'module' => 'admin',
'controller' => 'system',
'action' => 'quickedit',
];
$table_token = substr(sha1('_aside'), 0, 8);
session($table_token, $data);
$settings = [
[
'title' => '站点开关',
'tips' => '站点关闭后将不能访问',
'checked' => Db::name('admin_config')->where('id', 1)->value('value'),
'table' => $table_token,
'id' => 1,
'field' => 'value'
]
];
ZBuilder::make('aside')
->addBlock('switch', '系统设置', $settings);
}
}
/**
* 获取当前操作模型
* @author 蔡伟明 <314013107@qq.com>
* @return object|\think\db\Query
*/
final protected function getCurrModel()
{
$table_token = input('param._t', '');
$module = $this->request->module();
$controller = parse_name($this->request->controller());
$table_token == '' && $this->error('缺少参数');
!session('?'.$table_token) && $this->error('参数错误');
$table_data = session($table_token);
$table = $table_data['table'];
$Model = null;
if ($table_data['prefix'] == 2) {
// 使用模型
try {
$Model = App::model($table);
} catch (\Exception $e) {
$this->error('找不到模型:'.$table);
}
} else {
// 使用DB类
$table == '' && $this->error('缺少表名');
if ($table_data['module'] != $module || $table_data['controller'] != $controller) {
$this->error('非法操作');
}
$Model = $table_data['prefix'] == 0 ? Db::table($table) : Db::name($table);
}
return $Model;
}
/**
* 设置分页参数
* @author 蔡伟明 <314013107@qq.com>
*/
final protected function setPageParam()
{
_system_check();
$list_rows = input('?param.list_rows') ? input('param.list_rows') : config('list_rows');
config('paginate.list_rows', $list_rows);
config('paginate.query', input('get.'));
}
/**
* 检查是否登录,没有登录则跳转到登录页面
* @author 蔡伟明 <314013107@qq.com>
* @return int
*/
final protected function isLogin()
{
// 判断是否登录
if ($uid = is_signin()) {
// 已登录
return $uid;
} else {
// 未登录
$this->redirect('user/publics/signin');
}
}
/**
* 禁用
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function disable($record = [])
{
return $this->setStatus('disable', $record);
}
/**
* 启用
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function enable($record = [])
{
return $this->setStatus('enable', $record);
}
/**
* 启用
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function delete($record = [])
{
return $this->setStatus('delete', $record);
}
/**
* 快速编辑
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
*/
public function quickEdit($record = [])
{
$field = input('post.name', '');
$value = input('post.value', '');
$type = input('post.type', '');
$id = input('post.pk', '');
$validate = input('post.validate', '');
$validate_fields = input('post.validate_fields', '');
$field == '' && $this->error('缺少字段名');
$id == '' && $this->error('缺少主键值');
$Model = $this->getCurrModel();
$protect_table = [
'__ADMIN_USER__',
'__ADMIN_ROLE__',
config('database.prefix').'admin_user',
config('database.prefix').'admin_role',
];
// 验证是否操作管理员
if (in_array($Model->getTable(), $protect_table) && $id == 1) {
$this->error('禁止操作超级管理员');
}
// 验证器
if ($validate != '') {
$validate_fields = array_flip(explode(',', $validate_fields));
if (isset($validate_fields[$field])) {
$result = $this->validate([$field => $value], $validate.'.'.$field);
if (true !== $result) $this->error($result);
}
}
switch ($type) {
// 日期时间需要转为时间戳
case 'combodate':
$value = strtotime($value);
break;
// 开关
case 'switch':
$value = $value == 'true' ? 1 : 0;
break;
// 开关
case 'password':
$value = Hash::make((string)$value);
break;
}
// 主键名
$pk = $Model->getPk();
$result = $Model->where($pk, $id)->setField($field, $value);
cache('hook_plugins', null);
cache('system_config', null);
cache('access_menus', null);
if (false !== $result) {
// 记录行为日志
if (!empty($record)) {
call_user_func_array('action_log', $record);
}
$this->success('操作成功');
} else {
$this->error('操作失败');
}
}
/**
* 自动创建添加页面
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function add()
{
// 获取表单项
$cache_name = $this->request->module().'/'.parse_name($this->request->controller()).'/add';
$cache_name = strtolower($cache_name);
$form = Cache::get($cache_name, []);
if (!$form) {
$this->error('自动新增数据不存在,请重新打开此页面');
}
// 保存数据
if ($this->request->isPost()) {
// 表单数据
$data = $this->request->post();
$_pop = $this->request->get('_pop');
// 验证
if ($form['validate'] != '') {
$result = $this->validate($data, $form['validate']);
if(true !== $result) $this->error($result);
}
// 是否需要自动插入时间
if ($form['auto_time'] != '') {
$now_time = $this->request->time();
foreach ($form['auto_time'] as $item) {
if (strpos($item, '|')) {
list($item, $format) = explode('|', $item);
$data[$item] = date($format, $now_time);
} else {
$data[$item] = $form['format'] != '' ? date($form['format'], $now_time) : $now_time;
}
}
}
// 插入数据
if (Db::name($form['table'])->insert($data)) {
if ($_pop == 1) {
$this->success('新增成功', null, '_parent_reload');
} else {
$this->success('新增成功', $form['go_back']);
}
} else {
$this->error('新增失败');
}
}
// 显示添加页面
return ZBuilder::make('form')
->addFormItems($form['items'])
->fetch();
}
/**
* 自动创建编辑页面
* @param string $id 主键值
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @throws \think\exception\PDOException
*/
public function edit($id = '')
{
if ($id === '') $this->error('参数错误');
// 获取表单项
$cache_name = $this->request->module().'/'.parse_name($this->request->controller()).'/edit';
$cache_name = strtolower($cache_name);
$form = Cache::get($cache_name, []);
if (!$form) {
$this->error('自动编辑数据不存在,请重新打开此页面');
}
// 保存数据
if ($this->request->isPost()) {
// 表单数据
$data = $this->request->post();
$_pop = $this->request->get('_pop');
// 验证
if ($form['validate'] != '') {
$result = $this->validate($data, $form['validate']);
if(true !== $result) $this->error($result);
}
// 是否需要自动插入时间
if ($form['auto_time'] != '') {
$now_time = $this->request->time();
foreach ($form['auto_time'] as $item) {
if (strpos($item, '|')) {
list($item, $format) = explode('|', $item);
$data[$item] = date($format, $now_time);
} else {
$data[$item] = $form['format'] != '' ? date($form['format'], $now_time) : $now_time;
}
}
}
// 更新数据
if (false !== Db::name($form['table'])->update($data)) {
if ($_pop == 1) {
$this->success('编辑成功', null, '_parent_reload');
} else {
$this->success('编辑成功', $form['go_back']);
}
} else {
$this->error('编辑失败');
}
}
// 获取数据
$info = Db::name($form['table'])->find($id);
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('编辑')
->addFormItems($form['items'])
->setFormData($info)
->fetch();
}
/**
* 设置状态
* 禁用、启用、删除都是调用这个内部方法
* @param string $type 操作类型enable,disable,delete
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function setStatus($type = '', $record = [])
{
$ids = $this->request->isPost() ? input('post.ids/a') : input('param.ids');
$ids = (array)$ids;
$field = input('param.field', 'status');
empty($ids) && $this->error('缺少主键');
$Model = $this->getCurrModel();
$protect_table = [
'__ADMIN_USER__',
'__ADMIN_ROLE__',
'__ADMIN_MODULE__',
config('database.prefix').'admin_user',
config('database.prefix').'admin_role',
config('database.prefix').'admin_module',
];
// 禁止操作核心表的主要数据
if (in_array($Model->getTable(), $protect_table) && in_array('1', $ids)) {
$this->error('禁止操作');
}
// 主键名称
$pk = $Model->getPk();
$map = [
[$pk, 'in', $ids]
];
$result = false;
switch ($type) {
case 'disable': // 禁用
$result = $Model->where($map)->setField($field, 0);
break;
case 'enable': // 启用
$result = $Model->where($map)->setField($field, 1);
break;
case 'delete': // 删除
$result = $Model->where($map)->delete();
break;
default:
$this->error('非法操作');
break;
}
if (false !== $result) {
Cache::clear();
// 记录行为日志
if (!empty($record)) {
call_user_func_array('action_log', $record);
}
$this->success('操作成功');
} else {
$this->error('操作失败');
}
}
/**
* 模块设置
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function moduleConfig()
{
// 当前模块名
$module = $this->request->module();
// 保存
if ($this->request->isPost()) {
$data = $this->request->post();
$data = json_encode($data);
if (false !== ModuleModel::where('name', $module)->update(['config' => $data])) {
cache('module_config_'.$module, null);
$this->success('更新成功');
} else {
$this->error('更新失败');
}
}
// 模块配置信息
$module_info = ModuleModel::getInfoFromFile($module);
$config = $module_info['config'];
$trigger = isset($module_info['trigger']) ? $module_info['trigger'] : [];
// 数据库内的模块信息
$db_config = ModuleModel::where('name', $module)->value('config');
$db_config = json_decode($db_config, true);
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('模块设置')
->addFormItems($config)
->setFormdata($db_config) // 设置表格数据
->setTrigger($trigger) // 设置触发
->fetch();
}
}

View File

@ -0,0 +1,235 @@
<?php
namespace app\admin\controller;
use app\common\controller\Common;
use app\admin\model\Menu as MenuModel;
use app\admin\model\Attachment as AttachmentModel;
use think\facade\Cache;
use think\Db;
/**
* 用于处理ajax请求的控制器
* @package app\admin\controller
*/
class Ajax extends Common
{
/**
* 获取联动数据
* @param string $token token
* @param int $pid 父级ID
* @param string $pidkey 父级id字段名
* @author 蔡伟明 <314013107@qq.com>
* @return \think\response\Json
*/
public function getLevelData($token = '', $pid = 0, $pidkey = 'pid')
{
if ($token == '') {
return json(['code' => 0, 'msg' => '缺少Token']);
}
$token_data = session($token);
$table = $token_data['table'];
$option = $token_data['option'];
$key = $token_data['key'];
$data_list = Db::name($table)->where($pidkey, $pid)->column($option, $key);
if ($data_list === false) {
return json(['code' => 0, 'msg' => '查询失败']);
}
if ($data_list) {
$result = [
'code' => 1,
'msg' => '请求成功',
'list' => format_linkage($data_list)
];
return json($result);
} else {
return json(['code' => 0, 'msg' => '查询不到数据']);
}
}
/**
* 获取筛选数据
* @param string $token
* @param array $map 查询条件
* @param string $options 选项,用于显示转换
* @param string $list 选项缓存列表名称
* @author 蔡伟明 <314013107@qq.com>
* @return \think\response\Json
*/
public function getFilterList($token = '', $map = [], $options = '', $list = '')
{
if ($list != '') {
$result = [
'code' => 1,
'msg' => '请求成功',
'list' => Cache::get($list)
];
return json($result);
}
if ($token == '') {
return json(['code' => 0, 'msg' => '缺少Token']);
}
$token_data = session($token);
$table = $token_data['table'];
$field = $token_data['field'];
if ($field == '') {
return json(['code' => 0, 'msg' => '缺少字段']);
}
if (!empty($map) && is_array($map)) {
foreach ($map as &$item) {
if (is_array($item)) {
foreach ($item as &$value) {
$value = trim($value);
}
} else {
$item = trim($item);
}
}
}
if (strpos($table, '/')) {
$data_list = model($table)->where($map)->group($field)->column($field);
} else {
$data_list = Db::name($table)->where($map)->group($field)->column($field);
}
if ($data_list === false) {
return json(['code' => 0, 'msg' => '查询失败']);
}
if ($data_list) {
if ($options != '') {
// 从缓存获取选项数据
$options = cache($options);
if ($options) {
$temp_data_list = [];
foreach ($data_list as $item) {
$temp_data_list[$item] = isset($options[$item]) ? $options[$item] : '';
}
$data_list = $temp_data_list;
} else {
$data_list = parse_array($data_list);
}
} else {
$data_list = parse_array($data_list);
}
$result = [
'code' => 1,
'msg' => '请求成功',
'list' => $data_list
];
return json($result);
} else {
return json(['code' => 0, 'msg' => '查询不到数据']);
}
}
/**
* 获取指定模块的菜单
* @param string $module 模块名
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function getModuleMenus($module = '')
{
if (!is_signin()) {
$this->error('请先登录');
}
$menus = MenuModel::getMenuTree(0, '', $module);
$result = [
'code' => 1,
'msg' => '请求成功',
'list' => format_linkage($menus)
];
return json($result);
}
/**
* 设置配色方案
* @param string $theme 配色名称
* @author 蔡伟明 <314013107@qq.com>
*/
public function setTheme($theme = '') {
if (!is_signin()) {
$this->error('请先登录');
}
$themes = ['default', 'amethyst', 'city', 'flat', 'modern', 'smooth'];
if (!in_array($theme, $themes)) {
$this->error('非法操作');
}
$map['name'] = 'system_color';
$map['group'] = 'system';
if (Db::name('admin_config')->where($map)->setField('value', $theme)) {
$this->success('设置成功');
} else {
$this->error('设置失败,请重试');
}
}
/**
* 获取侧栏菜单
* @param string $module_id 模块id
* @param string $module 模型名
* @param string $controller 控制器名
* @author 蔡伟明 <314013107@qq.com>
* @return string
*/
public function getSidebarMenu($module_id = '', $module = '', $controller = '')
{
if (!is_signin()) {
$this->error('登录已失效,请重新登录', 'user/publics/signin');
}
role_auth();
$menus = MenuModel::getSidebarMenu($module_id, $module, $controller);
$output = '';
foreach ($menus as $key => $menu) {
if (!empty($menu['url_value'])) {
$output = $menu['url_value'];
break;
}
if (!empty($menu['child'])) {
$output = $menu['child'][0]['url_value'];
break;
}
}
$this->success('获取成功', null, $output);
}
/**
* 检查附件是否存在
* @param string $md5 文件md5
* @author 蔡伟明 <314013107@qq.com>
* @return \think\response\Json
*/
public function check($md5 = '')
{
$md5 == '' && $this->error('参数错误');
// 判断附件是否已存在
if ($file_exists = AttachmentModel::get(['md5' => $md5])) {
if ($file_exists['driver'] == 'local') {
$file_path = PUBLIC_PATH.$file_exists['path'];
} else {
$file_path = $file_exists['path'];
}
return json([
'code' => 1,
'info' => '上传成功',
'class' => 'success',
'id' => $file_exists['id'],
'path' => $file_path
]);
} else {
$this->error('文件不存在');
}
}
}

View File

@ -0,0 +1,796 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Attachment as AttachmentModel;
use think\Image;
use think\File;
use think\facade\Hook;
use think\Db;
use think\facade\Env;
/**
* 附件控制器
* @package app\admin\controller
*/
class Attachment extends Admin
{
/**
* 附件列表
* @author 蔡伟明 <314013107@qq.com>
*/
public function index()
{
// 查询
$map = $this->getMap();
// 数据列表
$data_list = AttachmentModel::where($map)->order('sort asc,id desc')->paginate();
foreach ($data_list as $key => &$value) {
if (in_array(strtolower($value['ext']), ['jpg', 'jpeg', 'png', 'gif', 'bmp'])) {
if ($value['driver'] == 'local') {
$thumb = $value['thumb'] != '' ? $value['thumb'] : $value['path'];
$value['type'] = '<div class="js-gallery"><img class="image" title="点击查看大图" data-original="'. PUBLIC_PATH . $value['path'].'" src="'. PUBLIC_PATH . $thumb.'"></div>';
} else {
$value['type'] = '<div class="js-gallery"><img class="image" title="点击查看大图" data-original="'. $value['path'].'" src="'. $value['path'].'"></div>';
}
} else {
if ($value['driver'] == 'local') {
$path = PUBLIC_PATH. $value['path'];
} else {
$path = $value['path'];
}
if (is_file('.'.config('public_static_path').'admin/img/files/'.$value['ext'].'.png')) {
$value['type'] = '<a href="'. $path.'"
data-toggle="tooltip" title="点击下载">
<img class="image" src="'.config('public_static_path').'admin/img/files/'.$value['ext'].'.png"></a>';
} else {
$value['type'] = '<a href="'. $path.'"
data-toggle="tooltip" title="点击下载">
<img class="image" src="'.config('public_static_path').'admin/img/files/file.png"></a>';
}
}
}
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setSearch(['name' => '名称']) // 设置搜索框
->addColumns([ // 批量添加数据列
['id', 'ID'],
['type', '类型'],
['name', '名称'],
['size', '大小', 'byte'],
['driver', '上传驱动', parse_attr(Db::name('admin_config')->where('name', 'upload_driver')->value('options'))],
['create_time', '上传时间', 'datetime'],
['status', '状态', 'switch'],
['right_button', '操作', 'btn']
])
->addTopButtons('enable,disable,delete') // 批量添加顶部按钮
->addRightButtons('delete') // 批量添加右侧按钮
->setRowList($data_list) // 设置表格数据
->fetch(); // 渲染模板
}
/**
* 上传附件
* @param string $dir 保存的目录:images,files,videos,voices
* @param string $from 来源wangeditorwangEditor编辑器, ueditor:ueditor编辑器, editormd:editormd编辑器等
* @param string $module 来自哪个模块
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function upload($dir = '', $from = '', $module = '')
{
// 临时取消执行时间限制
set_time_limit(0);
if ($dir == '') $this->error('没有指定上传目录');
if ($from == 'ueditor') return $this->ueditor();
if ($from == 'jcrop') return $this->jcrop();
return $this->saveFile($dir, $from, $module);
}
/**
* 保存附件
* @param string $dir 附件存放的目录
* @param string $from 来源
* @param string $module 来自哪个模块
* @author 蔡伟明 <314013107@qq.com>
* @return string|\think\response\Json
*/
private function saveFile($dir = '', $from = '', $module = '')
{
// 附件大小限制
$size_limit = $dir == 'images' ? config('upload_image_size') : config('upload_file_size');
$size_limit = $size_limit * 1024;
// 附件类型限制
$ext_limit = $dir == 'images' ? config('upload_image_ext') : config('upload_file_ext');
$ext_limit = $ext_limit != '' ? parse_attr($ext_limit) : '';
// 缩略图参数
$thumb = $this->request->post('thumb', '');
// 水印参数
$watermark = $this->request->post('watermark', '');
// 获取附件数据
$callback = '';
switch ($from) {
case 'editormd':
$file_input_name = 'editormd-image-file';
break;
case 'ckeditor':
$file_input_name = 'upload';
$callback = $this->request->get('CKEditorFuncNum');
break;
case 'ueditor_scrawl':
return $this->saveScrawl();
break;
default:
$file_input_name = 'file';
}
$file = $this->request->file($file_input_name);
// 判断附件是否已存在
if ($file_exists = AttachmentModel::get(['md5' => $file->hash('md5')])) {
if ($file_exists['driver'] == 'local') {
$file_path = PUBLIC_PATH. $file_exists['path'];
} else {
$file_path = $file_exists['path'];
}
// 附件已存在
return $this->uploadSuccess($from, $file_path, $file_exists['name'], $file_exists['id'], $callback);
}
// 判断附件大小是否超过限制
if ($size_limit > 0 && ($file->getInfo('size') > $size_limit)) {
return $this->uploadError($from, '附件过大', $callback);
}
// 判断附件格式是否符合
$file_name = $file->getInfo('name');
$file_ext = strtolower(substr($file_name, strrpos($file_name, '.')+1));
$error_msg = '';
if ($ext_limit == '') {
$error_msg = '获取文件信息失败!';
}
if ($file->getMime() == 'text/x-php' || $file->getMime() == 'text/html') {
$error_msg = '禁止上传非法文件!';
}
if (preg_grep("/php/i", $ext_limit)) {
$error_msg = '禁止上传非法文件!';
}
if (!preg_grep("/$file_ext/i", $ext_limit)) {
$error_msg = '附件类型不正确!';
}
if ($error_msg != '') {
// 上传错误
return $this->uploadError($from, $error_msg, $callback);
}
// 附件上传钩子,用于第三方文件上传扩展
if (config('upload_driver') != 'local') {
$hook_result = Hook::listen('upload_attachment', ['file' => $file, 'from' => $from, 'module' => $module], true);
if (false !== $hook_result) {
return $hook_result;
}
}
// 移动到框架应用根目录/uploads/ 目录下
$info = $file->move(config('upload_path') . DIRECTORY_SEPARATOR . $dir);
if($info){
// 缩略图路径
$thumb_path_name = '';
// 图片宽度
$img_width = '';
// 图片高度
$img_height = '';
if ($dir == 'images') {
$img = Image::open($info);
$img_width = $img->width();
$img_height = $img->height();
// 水印功能
if ($watermark == '') {
if (config('upload_thumb_water') == 1 && config('upload_thumb_water_pic') > 0) {
$this->create_water($info->getRealPath(), config('upload_thumb_water_pic'));
}
} else {
if (strtolower($watermark) != 'close') {
list($watermark_img, $watermark_pos, $watermark_alpha) = explode('|', $watermark);
$this->create_water($info->getRealPath(), $watermark_img, $watermark_pos, $watermark_alpha);
}
}
// 生成缩略图
if ($thumb == '') {
if (config('upload_image_thumb') != '') {
$thumb_path_name = $this->create_thumb($info, $info->getPathInfo()->getfileName(), $info->getFilename());
}
} else {
if (strtolower($thumb) != 'close') {
list($thumb_size, $thumb_type) = explode('|', $thumb);
$thumb_path_name = $this->create_thumb($info, $info->getPathInfo()->getfileName(), $info->getFilename(), $thumb_size, $thumb_type);
}
}
}
// 获取附件信息
$file_info = [
'uid' => session('user_auth.uid'),
'name' => $file->getInfo('name'),
'mime' => $file->getInfo('type'),
'path' => 'uploads/' . $dir . '/' . str_replace('\\', '/', $info->getSaveName()),
'ext' => $info->getExtension(),
'size' => $info->getSize(),
'md5' => $info->hash('md5'),
'sha1' => $info->hash('sha1'),
'thumb' => $thumb_path_name,
'module' => $module,
'width' => $img_width,
'height' => $img_height,
];
// 写入数据库
if ($file_add = AttachmentModel::create($file_info)) {
$file_path = PUBLIC_PATH. $file_info['path'];
return $this->uploadSuccess($from, $file_path, $file_info['name'], $file_add['id'], $callback);
} else {
return $this->uploadError($from, '上传失败', $callback);
}
}else{
return $this->uploadError($from, $file->getError(), $callback);
}
}
/**
* 处理ueditor上传
* @author 蔡伟明 <314013107@qq.com>
* @return string|\think\response\Json
*/
private function ueditor(){
$action = $this->request->get('action');
$config_file = './static/libs/ueditor/php/config.json';
$config = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", "", file_get_contents($config_file)), true);
switch ($action) {
/* 获取配置信息 */
case 'config':
$result = $config;
break;
/* 上传图片 */
case 'uploadimage':
return $this->saveFile('images', 'ueditor');
break;
/* 上传涂鸦 */
case 'uploadscrawl':
return $this->saveFile('images', 'ueditor_scrawl');
break;
/* 上传视频 */
case 'uploadvideo':
return $this->saveFile('videos', 'ueditor');
break;
/* 上传附件 */
case 'uploadfile':
return $this->saveFile('files', 'ueditor');
break;
/* 列出图片 */
case 'listimage':
return $this->showFile('listimage', $config);
break;
/* 列出附件 */
case 'listfile':
return $this->showFile('listfile', $config);
break;
/* 抓取远程附件 */
// case 'catchimage':
// $result = include("action_crawler.php");
// break;
default:
$result = ['state' => '请求地址出错'];
break;
}
/* 输出结果 */
if (isset($_GET["callback"])) {
if (preg_match("/^[\w_]+$/", $_GET["callback"])) {
return htmlspecialchars($_GET["callback"]) . '(' . $result . ')';
} else {
return json(['state' => 'callback参数不合法']);
}
} else {
return json($result);
}
}
/**
* 保存涂鸦ueditor
* @author 蔡伟明 <314013107@qq.com>
* @return \think\response\Json
*/
private function saveScrawl()
{
$file = $this->request->post('file');
$file_content = base64_decode($file);
$file_name = md5($file) . '.jpg';
$dir = config('upload_path') . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . date('Ymd', $this->request->time());
$file_path = $dir . DIRECTORY_SEPARATOR . $file_name;
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
if (false === file_put_contents($file_path, $file_content)) {
return json(['state' => '涂鸦上传出错']);
}
$file = new File($file_path);
$img = Image::open($file);
$file_info = [
'uid' => session('user_auth.uid'),
'name' => $file_name,
'mime' => 'image/png',
'path' => 'uploads/images/' . date('Ymd', $this->request->time()) . '/' . $file_name,
'ext' => 'png',
'size' => $file->getSize(),
'md5' => $file->hash('md5'),
'sha1' => $file->hash('sha1'),
'module' => $this->request->module(),
'width' => $img->width(),
'height' => $img->height()
];
if ($file_add = AttachmentModel::create($file_info)) {
// 返回成功信息
return json([
"state" => "SUCCESS", // 上传状态,上传成功时必须返回"SUCCESS"
"url" => PUBLIC_PATH. $file_info['path'], // 返回的地址
"title" => $file_info['name'], // 附件名
]);
} else {
return json(['state' => '涂鸦上传出错']);
}
}
/**
* 显示附件列表ueditor
* @param string $type 类型
* @param $config
* @author 蔡伟明 <314013107@qq.com>
* @return \think\response\Json
*/
private function showFile($type, $config){
/* 判断类型 */
switch ($type) {
/* 列出附件 */
case 'listfile':
$allowFiles = $config['fileManagerAllowFiles'];
$listSize = $config['fileManagerListSize'];
$path = realpath(config('upload_path') .'/files/');
break;
/* 列出图片 */
case 'listimage':
default:
$allowFiles = $config['imageManagerAllowFiles'];
$listSize = $config['imageManagerListSize'];
$path = realpath(config('upload_path') .'/images/');
}
$allowFiles = substr(str_replace(".", "|", join("", $allowFiles)), 1);
/* 获取参数 */
$size = isset($_GET['size']) ? htmlspecialchars($_GET['size']) : $listSize;
$start = isset($_GET['start']) ? htmlspecialchars($_GET['start']) : 0;
$end = $start + $size;
/* 获取附件列表 */
$files = $this->getfiles($path, $allowFiles);
if (!count($files)) {
return json(array(
"state" => "no match file",
"list" => array(),
"start" => $start,
"total" => count($files)
));
}
/* 获取指定范围的列表 */
$len = count($files);
for ($i = min($end, $len) - 1, $list = array(); $i < $len && $i >= 0 && $i >= $start; $i--){
$list[] = $files[$i];
}
//倒序
//for ($i = $end, $list = array(); $i < $len && $i < $end; $i++){
// $list[] = $files[$i];
//}
/* 返回数据 */
$result = array(
"state" => "SUCCESS",
"list" => $list,
"start" => $start,
"total" => count($files)
);
return json($result);
}
/**
* 处理Jcrop图片裁剪
* @author 蔡伟明 <314013107@qq.com>
*/
private function jcrop()
{
$file_path = $this->request->post('path', '');
$cut_info = $this->request->post('cut', '');
$thumb = $this->request->post('thumb', '');
$watermark = $this->request->post('watermark', '');
$module = $this->request->param('module', '');
// 上传图片
if ($file_path == '') {
$file = $this->request->file('file');
// 附件类型限制
$ext_limit = config('upload_image_ext');
$ext_limit = $ext_limit != '' ? parse_attr($ext_limit) : '';
// 判断附件格式是否符合
$file_name = $file->getInfo('name');
$file_ext = strtolower(substr($file_name, strrpos($file_name, '.')+1));
if ($ext_limit == '') {
$this->error('获取文件信息失败!');
}
if (strtolower($file_ext) == 'php') {
$this->error('禁止上传非法文件!');
}
if ($file->getMime() == 'text/x-php' || $file->getMime() == 'text/html') {
$this->error('禁止上传非法文件!');
}
if (preg_grep("/php/i", $ext_limit)) {
$this->error('禁止上传非法文件!');
}
if (!preg_grep("/$file_ext/i", $ext_limit)) {
$this->error('附件类型不正确!');
}
if (!is_dir(config('upload_temp_path'))) {
mkdir(config('upload_temp_path'), 0766, true);
}
$info = $file->move(config('upload_temp_path'), $file->hash('md5'));
if ($info) {
return json(['code' => 1, 'src' => PUBLIC_PATH. 'uploads/temp/'. $info->getFilename()]);
} else {
$this->error('上传失败');
}
}
$file_path = config('upload_temp_path') . str_replace(PUBLIC_PATH. 'uploads/temp/', '', $file_path);
if (is_file($file_path)) {
// 获取裁剪信息
$cut_info = explode(',', $cut_info);
// 读取图片
$image = Image::open($file_path);
$dir_name = date('Ymd');
$file_dir = config('upload_path') . DIRECTORY_SEPARATOR . 'images/' . $dir_name . '/';
if (!is_dir($file_dir)) {
mkdir($file_dir, 0766, true);
}
$file_name = md5(microtime(true)) . '.' . $image->type();
$new_file_path = $file_dir . $file_name;
// 裁剪图片
$image->crop($cut_info[0], $cut_info[1], $cut_info[2], $cut_info[3], $cut_info[4], $cut_info[5])->save($new_file_path);
// 水印功能
if ($watermark == '') {
if (config('upload_thumb_water') == 1 && config('upload_thumb_water_pic') > 0) {
$this->create_water($new_file_path, config('upload_thumb_water_pic'));
}
} else {
if (strtolower($watermark) != 'close') {
list($watermark_img, $watermark_pos, $watermark_alpha) = explode('|', $watermark);
$this->create_water($new_file_path, $watermark_img, $watermark_pos, $watermark_alpha);
}
}
// 是否创建缩略图
$thumb_path_name = '';
if ($thumb == '') {
if (config('upload_image_thumb') != '') {
$thumb_path_name = $this->create_thumb($new_file_path, $dir_name, $file_name);
}
} else {
if (strtolower($thumb) != 'close') {
list($thumb_size, $thumb_type) = explode('|', $thumb);
$thumb_path_name = $this->create_thumb($new_file_path, $dir_name, $file_name, $thumb_size, $thumb_type);
}
}
// 保存图片
$file = new File($new_file_path);
$file_info = [
'uid' => session('user_auth.uid'),
'name' => $file_name,
'mime' => $image->mime(),
'path' => 'uploads/images/' . $dir_name . '/' . $file_name,
'ext' => $image->type(),
'size' => $file->getSize(),
'md5' => $file->hash('md5'),
'sha1' => $file->hash('sha1'),
'thumb' => $thumb_path_name,
'module' => $module,
'width' => $image->width(),
'height' => $image->height()
];
if ($file_add = AttachmentModel::create($file_info)) {
// 删除临时图片
unlink($file_path);
// 返回成功信息
return json([
'code' => 1,
'id' => $file_add['id'],
'src' => PUBLIC_PATH . $file_info['path'],
'thumb' => $thumb_path_name == '' ? '' : PUBLIC_PATH . $thumb_path_name,
]);
} else {
$this->error('上传失败');
}
}
$this->error('文件不存在');
}
/**
* 创建缩略图
* @param string $file 目标文件,可以是文件对象或文件路径
* @param string $dir 保存目录,即目标文件所在的目录名
* @param string $save_name 缩略图名
* @param string $thumb_size 尺寸
* @param string $thumb_type 裁剪类型
* @author 蔡伟明 <314013107@qq.com>
* @return string 缩略图路径
*/
private function create_thumb($file = '', $dir = '', $save_name = '', $thumb_size = '', $thumb_type = '')
{
// 获取要生成的缩略图最大宽度和高度
$thumb_size = $thumb_size == '' ? config('upload_image_thumb') : $thumb_size;
list($thumb_max_width, $thumb_max_height) = explode(',', $thumb_size);
// 读取图片
$image = Image::open($file);
// 生成缩略图
$thumb_type = $thumb_type == '' ? config('upload_image_thumb_type') : $thumb_type;
$image->thumb($thumb_max_width, $thumb_max_height, $thumb_type);
// 保存缩略图
$thumb_path = config('upload_path') . DIRECTORY_SEPARATOR . 'images/' . $dir . '/thumb/';
if (!is_dir($thumb_path)) {
mkdir($thumb_path, 0766, true);
}
$thumb_path_name = $thumb_path. $save_name;
$image->save($thumb_path_name);
$thumb_path_name = 'uploads/images/' . $dir . '/thumb/' . $save_name;
return $thumb_path_name;
}
/**
* 添加水印
* @param string $file 要添加水印的文件路径
* @param string $watermark_img 水印图片id
* @param string $watermark_pos 水印位置
* @param string $watermark_alpha 水印透明度
* @author 蔡伟明 <314013107@qq.com>
*/
private function create_water($file = '', $watermark_img = '', $watermark_pos = '', $watermark_alpha = '')
{
$path = model('admin/attachment')->getFilePath($watermark_img, 1);
$thumb_water_pic = realpath(Env::get('root_path') . 'public/' . $path);
if (is_file($thumb_water_pic)) {
// 读取图片
$image = Image::open($file);
// 添加水印
$watermark_pos = $watermark_pos == '' ? config('upload_thumb_water_position') : $watermark_pos;
$watermark_alpha = $watermark_alpha == '' ? config('upload_thumb_water_alpha') : $watermark_alpha;
$image->water($thumb_water_pic, $watermark_pos, $watermark_alpha);
// 保存水印图片,覆盖原图
$image->save($file);
}
}
/**
* 上传成功信息
* @param $from
* @param string $file_path
* @param string $file_name
* @param string $file_id
* @param string $callback
* @return string|\think\response\Json
* @author 蔡伟明 <314013107@qq.com>
*/
private function uploadSuccess($from, $file_path = '', $file_name = '', $file_id = '', $callback = '')
{
switch ($from) {
case 'wangeditor':
return $file_path;
break;
case 'ueditor':
return json([
"state" => "SUCCESS", // 上传状态,上传成功时必须返回"SUCCESS"
"url" => $file_path, // 返回的地址
"title" => $file_name, // 附件名
]);
break;
case 'editormd':
return json([
"success" => 1,
"message" => '上传成功',
"url" => $file_path,
]);
break;
case 'ckeditor':
return ck_js($callback, $file_path);
break;
default:
return json([
'code' => 1,
'info' => '上传成功',
'class' => 'success',
'id' => $file_id,
'path' => $file_path
]);
}
}
/**
* 上传错误信息
* @param $from
* @param string $msg
* @param string $callback
* @return string|\think\response\Json
* @author 蔡伟明 <314013107@qq.com>
*/
private function uploadError($from, $msg = '', $callback = '')
{
switch ($from) {
case 'wangeditor':
return "error|".$msg;
break;
case 'ueditor':
return json(['state' => $msg]);
break;
case 'editormd':
return json(["success" => 0, "message" => $msg]);
break;
case 'ckeditor':
return ck_js($callback, '', $msg);
break;
default:
return json([
'code' => 0,
'class' => 'danger',
'info' => $msg
]);
}
}
/**
* 遍历获取目录下的指定类型的附件
* @param string $path 路径
* @param string $allowFiles 允许查看的类型
* @param array $files 文件列表
* @author 蔡伟明 <314013107@qq.com>
* @return array|null
*/
public function getfiles($path = '', $allowFiles = '', &$files = array())
{
if (!is_dir($path)) return null;
if(substr($path, strlen($path) - 1) != '/') $path .= '/';
$handle = opendir($path);
while (false !== ($file = readdir($handle))) {
if ($file != '.' && $file != '..') {
$path2 = $path . $file;
if (is_dir($path2)) {
$this->getfiles($path2, $allowFiles, $files);
} else {
if (preg_match("/\.(".$allowFiles.")$/i", $file)) {
$files[] = array(
'url'=> str_replace("\\", "/", substr($path2, strlen($_SERVER['DOCUMENT_ROOT']))),
'mtime'=> filemtime($path2)
);
}
}
}
}
return $files;
}
/**
* 启用附件
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function enable($record = [])
{
return $this->setStatus('enable');
}
/**
* 禁用附件
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function disable($record = [])
{
return $this->setStatus('disable');
}
/**
* 设置附件状态:删除、禁用、启用
* @param string $type 类型delete/enable/disable
* @param array $record
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function setStatus($type = '', $record = [])
{
$ids = $this->request->isPost() ? input('post.ids/a') : input('param.ids');
$ids = is_array($ids) ? implode(',', $ids) : $ids;
return parent::setStatus($type, ['attachment_'.$type, 'admin_attachment', 0, UID, $ids]);
}
/**
* 删除附件
* @param string $ids 附件id
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function delete($ids = '')
{
$ids = $this->request->isPost() ? input('post.ids/a') : input('param.ids');
if (empty($ids)) $this->error('缺少主键');
$files_path = AttachmentModel::where('id', 'in', $ids)->column('path,thumb', 'id');
foreach ($files_path as $value) {
$real_path = realpath(config('upload_path').'/../'.$value['path']);
$real_path_thumb = realpath(config('upload_path').'/../'.$value['thumb']);
if (is_file($real_path) && !unlink($real_path)) {
$this->error('删除失败');
}
if (is_file($real_path_thumb) && !unlink($real_path_thumb)) {
$this->error('删除缩略图失败');
}
}
if (AttachmentModel::where('id', 'in', $ids)->delete()) {
// 记录行为
$ids = is_array($ids) ? implode(',', $ids) : $ids;
action_log('attachment_delete', 'admin_attachment', 0, UID, $ids);
$this->success('删除成功');
} else {
$this->error('删除失败');
}
}
/**
* 快速编辑
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function quickEdit($record = [])
{
$id = input('post.pk', '');
return parent::quickEdit(['attachment_edit', 'admin_attachment', 0, UID, $id]);
}
}

View File

@ -0,0 +1,281 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Config as ConfigModel;
/**
* 系统配置控制器
* @package app\admin\controller
*/
class Config extends Admin
{
/**
* 配置首页
* @param string $group 分组
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\DbException
*/
public function index($group = 'base')
{
cookie('__forward__', $_SERVER['REQUEST_URI']);
// 配置分组信息
$list_group = config('config_group');
$tab_list = [];
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
// 查询
$map = $this->getMap();
$map[] = ['group', '=', $group];
$map[] = ['status', 'egt', 0];
// 排序
$order = $this->getOrder('sort asc,id asc');
// 数据列表
$data_list = ConfigModel::where($map)->order($order)->paginate();
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setPageTitle('配置管理') // 设置页面标题
->setTabNav($tab_list, $group) // 设置tab分页
->setSearch(['name' => '名称', 'title' => '标题']) // 设置搜索框
->addColumns([ // 批量添加数据列
['name', '名称', 'text.edit'],
['title', '标题', 'text.edit'],
['type', '类型', 'select', config('form_item_type')],
['status', '状态', 'switch'],
['sort', '排序', 'text.edit'],
['right_button', '操作', 'btn']
])
->addValidate('Config', 'name,title') // 添加快捷编辑的验证器
->addOrder('name,title,status') // 添加标题字段排序
->addFilter('name,title') // 添加标题字段筛选
->addFilter('type', config('form_item_type')) // 添加标题字段筛选
->addFilterMap('name,title', ['group' => $group]) // 添加标题字段筛选条件
->addTopButton('add', ['href' => url('add', ['group' => $group])], true) // 添加单个顶部按钮
->addTopButtons('enable,disable,delete') // 批量添加顶部按钮
->addRightButton('edit', [], true)
->addRightButton('delete') // 批量添加右侧按钮
->setRowList($data_list) // 设置表格数据
->fetch(); // 渲染模板
}
/**
* 新增配置项
* @param string $group 分组
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function add($group = '')
{
// 保存数据
if ($this->request->isPost()) {
// 表单数据
$data = $this->request->post();
// 验证
$result = $this->validate($data, 'Config');
if(true !== $result) $this->error($result);
// 如果是快速联动
if ($data['type'] == 'linkages') {
$data['key'] = $data['key'] == '' ? 'id' : $data['key'];
$data['pid'] = $data['pid'] == '' ? 'pid' : $data['pid'];
$data['level'] = $data['level'] == '' ? '2' : $data['level'];
$data['option'] = $data['option'] == '' ? 'name' : $data['option'];
}
if ($config = ConfigModel::create($data)) {
cache('system_config', null);
$forward = $this->request->param('_pop') == 1 ? null : cookie('__forward__');
// 记录行为
$details = '详情:分组('.$data['group'].')、类型('.$data['type'].')、标题('.$data['title'].')、名称('.$data['name'].')';
action_log('config_add', 'admin_config', $config['id'], UID, $details);
$this->success('新增成功', $forward);
} else {
$this->error('新增失败');
}
}
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('新增')
->addRadio('group', '配置分组', '', config('config_group'), $group)
->addSelect('type', '配置类型', '', config('form_item_type'))
->addText('title', '配置标题', '一般由中文组成,仅用于显示')
->addText('name', '配置名称', '由英文字母和下划线组成,如 <code>web_site_title</code>,调用方法:<code>config(\'web_site_title\')</code>')
->addTextarea('value', '配置值', '该配置的具体内容')
->addTextarea('options', '配置项', '用于单选、多选、下拉、联动等类型')
->addText('ajax_url', '异步请求地址', "如请求的地址是 <code>url('ajax/getCity')</code>,那么只需填写 <code>ajax/getCity</code>,或者直接填写以 <code>http</code>开头的url地址")
->addText('next_items', '下一级联动下拉框的表单名', "与当前有关联的下级联动下拉框名多个用逗号隔开area,other")
->addText('param', '请求参数名', "联动下拉框请求参数名,默认为配置名称")
->addNumber('level', '级别', '需要显示的级别数量默认为2', 2, 2, 4)
->addText('table', '表名', '要查询的表里面必须含有id、name、pid三个字段其中id和name字段可在下面重新定义')
->addText('pid', '父级id字段名', '即表中的父级ID字段名如果表中的主键字段名为pid则可不填写')
->addText('key', '键字段名', '即表中的主键字段名如果表中的主键字段名为id则可不填写')
->addText('option', '值字段名', '下拉菜单显示的字段名如果表中的该字段名为name则可不填写')
->addText('ak', 'APPKEY', '百度编辑器APPKEY')
->addText('format', '格式')
->addText('tips', '配置说明', '该配置的具体说明')
->addText('sort', '排序', '', 100)
->setTrigger('type', 'linkage', 'ajax_url,next_items,param')
->setTrigger('type', 'linkages', 'table,pid,level,key,option')
->setTrigger('type', 'bmap', 'ak')
->setTrigger('type', 'masked,date,time,datetime', 'format')
->fetch();
}
/**
* 编辑
* @param int $id
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function edit($id = 0)
{
if ($id === 0) $this->error('参数错误');
// 保存数据
if ($this->request->isPost()) {
// 表单数据
$data = $this->request->post();
// 验证
$result = $this->validate($data, 'Config');
if(true !== $result) $this->error($result);
// 如果是快速联动
if ($data['type'] == 'linkages') {
$data['key'] = $data['key'] == '' ? 'id' : $data['key'];
$data['pid'] = $data['pid'] == '' ? 'pid' : $data['pid'];
$data['level'] = $data['level'] == '' ? '2' : $data['level'];
$data['option'] = $data['option'] == '' ? 'name' : $data['option'];
}
// 原配置内容
$config = ConfigModel::where('id', $id)->find();
$details = '原数据:分组('.$config['group'].')、类型('.$config['type'].')、标题('.$config['title'].')、名称('.$config['name'].')';
if ($config = ConfigModel::update($data)) {
cache('system_config', null);
$forward = $this->request->param('_pop') == 1 ? null : cookie('__forward__');
// 记录行为
action_log('config_edit', 'admin_config', $config['id'], UID, $details);
$this->success('编辑成功', $forward, '_parent_reload');
} else {
$this->error('编辑失败');
}
}
// 获取数据
$info = ConfigModel::get($id);
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('编辑')
->addHidden('id')
->addRadio('group', '配置分组', '', config('config_group'))
->addSelect('type', '配置类型', '', config('form_item_type'))
->addText('title', '配置标题', '一般由中文组成,仅用于显示')
->addText('name', '配置名称', '由英文字母和下划线组成,如 <code>web_site_title</code>,调用方法:<code>config(\'web_site_title\')</code>')
->addTextarea('value', '配置值', '该配置的具体内容')
->addTextarea('options', '配置项', '用于单选、多选、下拉、联动等类型')
->addText('ajax_url', '异步请求地址', "如请求的地址是 <code>url('ajax/getCity')</code>,那么只需填写 <code>ajax/getCity</code>,或者直接填写以 <code>http</code>开头的url地址")
->addText('next_items', '下一级联动下拉框的表单名', "与当前有关联的下级联动下拉框名多个用逗号隔开area,other")
->addText('param', '请求参数名', "联动下拉框请求参数名,默认为配置名称")
->addNumber('level', '级别', '需要显示的级别数量默认为2', 2, 2, 4)
->addText('table', '表名', '要查询的表里面必须含有id、name、pid三个字段其中id和name字段可在下面重新定义')
->addText('pid', '父级id字段名', '即表中的父级ID字段名如果表中的主键字段名为pid则可不填写')
->addText('key', '键字段名', '即表中的主键字段名如果表中的主键字段名为id则可不填写')
->addText('option', '值字段名', '下拉菜单显示的字段名如果表中的该字段名为name则可不填写')
->addText('ak', 'APPKEY', '百度编辑器APPKEY')
->addText('format', '格式')
->addText('tips', '配置说明', '该配置的具体说明')
->addText('sort', '排序', '', 100)
->setTrigger('type', 'linkage', 'ajax_url,next_items,param')
->setTrigger('type', 'linkages', 'table,pid,level,key,option')
->setTrigger('type', 'bmap', 'ak')
->setTrigger('type', 'masked,date,time,datetime', 'format')
->setFormData($info)
->fetch();
}
/**
* 删除配置
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function delete($record = [])
{
return $this->setStatus('delete');
}
/**
* 启用配置
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function enable($record = [])
{
return $this->setStatus('enable');
}
/**
* 禁用配置
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function disable($record = [])
{
return $this->setStatus('disable');
}
/**
* 设置配置状态:删除、禁用、启用
* @param string $type 类型delete/enable/disable
* @param array $record
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function setStatus($type = '', $record = [])
{
$ids = $this->request->isPost() ? input('post.ids/a') : input('param.ids');
$uid_delete = is_array($ids) ? '' : $ids;
$ids = ConfigModel::where('id', 'in', $ids)->column('title');
return parent::setStatus($type, ['config_'.$type, 'admin_config', $uid_delete, UID, implode('、', $ids)]);
}
/**
* 快速编辑
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function quickEdit($record = [])
{
$id = input('post.pk', '');
$field = input('post.name', '');
$value = input('post.value', '');
$config = ConfigModel::where('id', $id)->value($field);
$details = '字段(' . $field . '),原值(' . $config . '),新值:(' . $value . ')';
return parent::quickEdit(['config_edit', 'admin_config', $id, UID, $details]);
}
}

View File

@ -0,0 +1,366 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use think\Db;
use util\Database as DatabaseModel;
/**
* 数据库管理
* @package app\admin\controller
*/
class Database extends Admin
{
/**
* 数据库管理
* @param string $group 分组
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function index($group = 'export')
{
// 配置分组信息
$list_group = ['export' =>'备份数据库', 'import' => '还原数据库'];
$tab_list = [];
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
switch ($group) {
case 'export':
$data_list = Db::query("SHOW TABLE STATUS");
$data_list = array_map('array_change_key_case', $data_list);
// 自定义按钮
$btn_export = [
'title' => '立即备份',
'icon' => 'fa fa-fw fa-copy',
'class' => 'btn btn-primary ajax-post confirm',
'href' => url('export')
];
$btn_optimize_all = [
'title' => '优化表',
'icon' => 'fa fa-fw fa-cogs',
'class' => 'btn btn-success ajax-post',
'href' => url('optimize')
];
$btn_repair_all = [
'title' => '修复表',
'icon' => 'fa fa-fw fa-wrench',
'class' => 'btn btn-success ajax-post',
'href' => url('repair')
];
$btn_optimize = [
'title' => '优化表',
'icon' => 'fa fa-fw fa-cogs',
'class' => 'btn btn-xs btn-default ajax-get',
'href' => url('optimize', ['ids' => '__id__'])
];
$btn_repair = [
'title' => '修复表',
'icon' => 'fa fa-fw fa-wrench',
'class' => 'btn btn-xs btn-default ajax-get',
'href' => url('repair', ['ids' => '__id__'])
];
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setPageTitle('数据库管理') // 设置页面标题
->setPrimaryKey('name')
->setTabNav($tab_list, $group) // 设置tab分页
->addColumns([ // 批量添加数据列
['name', '表名'],
['rows', '行数'],
['data_length', '大小', 'byte'],
['data_free', '冗余', 'byte'],
['comment', '备注'],
['right_button', '操作', 'btn']
])
->addTopButton('custom', $btn_export) // 添加单个顶部按钮
->addTopButton('custom', $btn_optimize_all) // 添加单个顶部按钮
->addTopButton('custom', $btn_repair_all) // 添加单个顶部按钮
->addRightButton('custom', $btn_optimize) // 添加右侧按钮
->addRightButton('custom', $btn_repair) // 添加右侧按钮
->setRowList($data_list) // 设置表格数据
->fetch(); // 渲染模板
break;
case 'import':
// 列出备份文件列表
$path = config('data_backup_path');
if(!is_dir($path)){
mkdir($path, 0755, true);
}
$path = realpath($path);
$flag = \FilesystemIterator::KEY_AS_FILENAME;
$glob = new \FilesystemIterator($path, $flag);
$data_list = [];
foreach ($glob as $name => $file) {
if(preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)){
$name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d');
$date = "{$name[0]}-{$name[1]}-{$name[2]}";
$time = "{$name[3]}:{$name[4]}:{$name[5]}";
$part = $name[6];
if(isset($data_list["{$date} {$time}"])){
$info = $data_list["{$date} {$time}"];
$info['part'] = max($info['part'], $part);
$info['size'] = $info['size'] + $file->getSize();
} else {
$info['part'] = $part;
$info['size'] = $file->getSize();
}
$extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
$info['compress'] = ($extension === 'SQL') ? '-' : $extension;
$info['time'] = strtotime("{$date} {$time}");
$info['name'] = $info['time'];
$data_list["{$date} {$time}"] = $info;
}
}
$data_list = !empty($data_list) ? array_values($data_list) : $data_list;
// 自定义按钮
$btn_import = [
'title' => '还原',
'icon' => 'fa fa-fw fa-reply',
'class' => 'btn btn-xs btn-default ajax-get confirm',
'href' => url('import', ['time' => '__id__'])
];
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setPageTitle('数据库管理') // 设置页面标题
->setPrimaryKey('time')
->hideCheckbox()
->setTabNav($tab_list, $group) // 设置tab分页
->addColumns([ // 批量添加数据列
['name', '备份名称', 'datetime', '', 'Ymd-His'],
['part', '卷数'],
['compress', '压缩'],
['size', '数据大小', 'byte'],
['time', '备份时间', 'datetime', '', 'Y-m-d H:i:s'],
['right_button', '操作', 'btn']
])
->addRightButton('custom', $btn_import) // 添加右侧按钮
->addRightButton('delete') // 添加右侧按钮
->setRowList($data_list) // 设置表格数据
->fetch(); // 渲染模板
break;
}
}
/**
* 备份数据库(参考onthink 麦当苗儿 <zuojiazi@vip.qq.com>)
* @param null|array $ids 表名
* @param integer $start 起始行数
* @author 蔡伟明 <314013107@qq.com>
*/
public function export($ids = null, $start = 0)
{
$tables = $ids;
if ($this->request->isPost() && !empty($tables) && is_array($tables)) {
// 初始化
$path = config('data_backup_path');
if(!is_dir($path)){
mkdir($path, 0755, true);
}
// 读取备份配置
$config = array(
'path' => realpath($path) . DIRECTORY_SEPARATOR,
'part' => config('data_backup_part_size'),
'compress' => config('data_backup_compress'),
'level' => config('data_backup_compress_level'),
);
// 检查是否有正在执行的任务
$lock = "{$config['path']}backup.lock";
if(is_file($lock)){
$this->error('检测到有一个备份任务正在执行,请稍后再试!');
} else {
// 创建锁文件
file_put_contents($lock, $this->request->time());
}
// 检查备份目录是否可写
is_writeable($config['path']) || $this->error('备份目录不存在或不可写,请检查后重试!');
// 生成备份文件信息
$file = array(
'name' => date('Ymd-His', $this->request->time()),
'part' => 1,
);
// 创建备份文件
$Database = new DatabaseModel($file, $config);
if(false !== $Database->create()){
// 备份指定表
foreach ($tables as $table) {
$start = $Database->backup($table, $start);
while (0 !== $start) {
if (false === $start) { // 出错
$this->error('备份出错!');
}
$start = $Database->backup($table, $start[0]);
}
}
// 备份完成,删除锁定文件
unlink($lock);
// 记录行为
action_log('database_export', 'database', 0, UID, implode(',', $tables));
$this->success('备份完成!');
} else {
$this->error('初始化失败,备份文件创建失败!');
}
} else {
$this->error('参数错误!');
}
}
/**
* 还原数据库(参考onthink 麦当苗儿 <zuojiazi@vip.qq.com>)
* @param int $time 文件时间戳
* @author 蔡伟明 <314013107@qq.com>
*/
public function import($time = 0)
{
if ($time === 0) $this->error('参数错误!');
// 初始化
$name = date('Ymd-His', $time) . '-*.sql*';
$path = realpath(config('data_backup_path')) . DIRECTORY_SEPARATOR . $name;
$files = glob($path);
$list = array();
foreach($files as $name){
$basename = basename($name);
$match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
$gz = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
$list[$match[6]] = array($match[6], $name, $gz);
}
ksort($list);
// 检测文件正确性
$last = end($list);
if(count($list) === $last[0]){
foreach ($list as $item) {
$config = [
'path' => realpath(config('data_backup_path')) . DIRECTORY_SEPARATOR,
'compress' => $item[2]
];
$Database = new DatabaseModel($item, $config);
$start = $Database->import(0);
// 循环导入数据
while (0 !== $start) {
if (false === $start) { // 出错
$this->error('还原数据出错!');
}
$start = $Database->import($start[0]);
}
}
// 记录行为
action_log('database_import', 'database', 0, UID, date('Ymd-His', $time));
$this->success('还原完成!');
} else {
$this->error('备份文件可能已经损坏,请检查!');
}
}
/**
* 优化表
* @param null|string|array $ids 表名
* @author 蔡伟明 <314013107@qq.com>
*/
public function optimize($ids = null)
{
$tables = $ids;
if($tables) {
if(is_array($tables)){
$tables = implode('`,`', $tables);
$list = Db::query("OPTIMIZE TABLE `{$tables}`");
if($list){
// 记录行为
action_log('database_optimize', 'database', 0, UID, "`{$tables}`");
$this->success("数据表优化完成!");
} else {
$this->error("数据表优化出错请重试!");
}
} else {
$list = Db::query("OPTIMIZE TABLE `{$tables}`");
if($list){
// 记录行为
action_log('database_optimize', 'database', 0, UID, $tables);
$this->success("数据表'{$tables}'优化完成!");
} else {
$this->error("数据表'{$tables}'优化出错请重试!");
}
}
} else {
$this->error("请选择要优化的表!");
}
}
/**
* 修复表
* @param null|string|array $ids 表名
* @author 蔡伟明 <314013107@qq.com>
*/
public function repair($ids = null)
{
$tables = $ids;
if($tables) {
if(is_array($tables)){
$tables = implode('`,`', $tables);
$list = Db::query("REPAIR TABLE `{$tables}`");
if($list){
// 记录行为
action_log('database_repair', 'database', 0, UID, "`{$tables}`");
$this->success("数据表修复完成!");
} else {
$this->error("数据表修复出错请重试!");
}
} else {
$list = Db::query("REPAIR TABLE `{$tables}`");
if($list){
// 记录行为
action_log('database_repair', 'database', 0, UID, $tables);
$this->success("数据表'{$tables}'修复完成!");
} else {
$this->error("数据表'{$tables}'修复出错请重试!");
}
}
} else {
$this->error("请指定要修复的表!");
}
}
/**
* 删除备份文件
* @param int $ids 备份时间
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function delete($ids = 0)
{
if ($ids == 0) $this->error('参数错误!');
$name = date('Ymd-His', $ids) . '-*.sql*';
$path = realpath(config('data_backup_path')) . DIRECTORY_SEPARATOR . $name;
array_map("unlink", glob($path));
if(count(glob($path))){
$this->error('备份文件删除失败,请检查权限!');
} else {
// 记录行为
action_log('database_backup_delete', 'database', 0, UID, date('Ymd-His', $ids));
$this->success('备份文件删除成功!');
}
}
}

View File

@ -0,0 +1,234 @@
<?php
namespace app\admin\controller;
use app\admin\model\HookPlugin;
use app\common\builder\ZBuilder;
use app\admin\model\Hook as HookModel;
use app\admin\model\HookPlugin as HookPluginModel;
/**
* 钩子控制器
* @package app\admin\controller
*/
class Hook extends Admin
{
/**
* 钩子管理
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\DbException
*/
public function index()
{
$map = $this->getMap();
$order = $this->getOrder();
// 数据列表
$data_list = HookModel::where($map)->order($order)->paginate();
// 分页数据
$page = $data_list->render();
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setPageTitle('钩子管理') // 设置页面标题
->setSearch(['name' => '钩子名称']) // 设置搜索框
->addColumns([ // 批量添加数据列
['name', '名称'],
['description', '描述'],
['plugin', '所属插件', 'callback', function($plugin){
return $plugin == '' ? '系统' : $plugin;
}],
['system', '系统钩子', 'yesno'],
['status', '状态', 'switch'],
['right_button', '操作', 'btn']
])
->addOrder('name,status')
->addTopButtons('add,enable,disable,delete') // 批量添加顶部按钮
->addRightButtons('edit,delete') // 批量添加右侧按钮
->setRowList($data_list) // 设置表格数据
->setPages($page) // 设置分页数据
->fetch(); // 渲染模板
}
/**
* 新增
* @author 蔡伟明 <314013107@qq.com>
*/
public function add()
{
// 保存数据
if ($this->request->isPost()) {
// 表单数据
$data = $this->request->post();
$data['system'] = 1;
// 验证
$result = $this->validate($data, 'Hook');
if(true !== $result) $this->error($result);
if ($hook = HookModel::create($data)) {
cache('hook_plugins', null);
// 记录行为
action_log('hook_add', 'admin_hook', $hook['id'], UID, $data['name']);
$this->success('新增成功', 'index');
} else {
$this->error('新增失败');
}
}
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('新增')
->addText('name', '钩子名称', '由字母和下划线组成,如:<code>page_tips</code>')
->addText('description', '钩子描述')
->fetch();
}
/**
* 编辑
* @param int $id 钩子id
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function edit($id = 0)
{
if ($id === 0) $this->error('参数错误');
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post();
// 验证
$result = $this->validate($data, 'Hook');
if(true !== $result) $this->error($result);
if ($hook = HookModel::update($data)) {
// 调整插件顺序
if ($data['sort'] != '') {
HookPluginModel::sort($data['name'], $data['sort']);
}
cache('hook_plugins', null);
// 记录行为
action_log('hook_edit', 'admin_hook', $hook['id'], UID, $data['name']);
$this->success('编辑成功', 'index');
} else {
$this->error('编辑失败');
}
}
// 获取数据
$info = HookModel::get($id);
// 该钩子的所有插件
$hooks = HookPluginModel::where('hook', $info['name'])->order('sort')->column('plugin');
$hooks = parse_array($hooks);
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('编辑')
->addHidden('id')
->addText('name', '钩子名称', '由字母和下划线组成,如:<code>page_tips</code>')
->addText('description', '钩子描述')
->addSort('sort', '插件排序', '', $hooks)
->setFormData($info)
->fetch();
}
/**
* 快速编辑(启用/禁用)
* @param string $status 状态
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function quickEdit($status = '')
{
$id = $this->request->post('pk');
$status = $this->request->param('value');
$hook_name = HookModel::where('id', $id)->value('name');
if (false === HookPluginModel::where('hook', $hook_name)->setField('status', $status == 'true' ? 1 : 0)) {
$this->error('操作失败,请重试');
}
cache('hook_plugins', null);
$details = $status == 'true' ? '启用钩子' : '禁用钩子';
return parent::quickEdit(['hook_edit', 'admin_hook', $id, UID, $details]);
}
/**
* 启用
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function enable($record = [])
{
return $this->setStatus('enable');
}
/**
* 禁用
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
/**
* 禁用
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function disable($record = [])
{
return $this->setStatus('disable');
}
/**
* 删除钩子
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @throws \think\exception\PDOException
*/
public function delete($record = [])
{
$ids = $this->request->isPost() ? input('post.ids/a') : input('param.ids');
$map = [
['id', 'in', $ids],
['system', '=', 1],
];
if (HookModel::where($map)->find()) {
$this->error('禁止删除系统钩子');
}
return $this->setStatus('delete');
}
/**
* 设置状态
* @param string $type 类型
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function setStatus($type = '', $record = [])
{
$ids = $this->request->param('ids/a');
foreach ($ids as $id) {
$hook_name = HookModel::where('id', $id)->value('name');
if (false === HookPluginModel::where('hook', $hook_name)->setField('status', $type == 'enable' ? 1 : 0)) {
$this->error('操作失败,请重试');
}
}
cache('hook_plugins', null);
$hook_delete = is_array($ids) ? '' : $ids;
$hook_names = HookModel::where('id', 'in', $ids)->column('name');
return parent::setStatus($type, ['hook_'.$type, 'admin_hook', $hook_delete, UID, implode('、', $hook_names)]);
}
}

View File

@ -0,0 +1,223 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Icon as IconModel;
use app\admin\model\IconList as IconListModel;
/**
* 图标控制器
* @package app\admin\controller
*/
class Icon extends Admin
{
/**
* 图标列表
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\DbException
*/
public function index()
{
$data_list = IconModel::where($this->getMap())
->order($this->getOrder('id DESC'))
->paginate();
return ZBuilder::make('table')
->addTopButtons('add,enable,disable,delete')
->addRightButton('list', [
'title' => '图标列表',
'icon' => 'fa fa-list',
'href' => url('items', ['id' => '__id__'])
])
->addRightButton('reload', [
'title' => '更新图标',
'icon' => 'fa fa-refresh',
'class' => 'btn btn-xs btn-default ajax-get confirm',
'href' => url('reload', ['id' => '__id__'])
])
->addRightButton('delete')
->setSearch('name')
->addColumns([
['id', 'ID'],
['name', '名称', 'text.edit'],
['url', '链接', 'text.edit'],
['status', '状态', 'switch'],
['create_time', '创建时间', 'datetime'],
['right_button', '操作', 'btn'],
])
->setRowList($data_list)
->fetch();
}
/**
* 新增
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function add()
{
if ($this->request->isPost()) {
$data = $this->request->post('', null, 'trim');
$data['name'] == '' && $this->error('请填写名称');
$data['url'] == '' && $this->error('请填写链接');
$data['create_time'] = $this->request->time();
$data['update_time'] = $this->request->time();
// 获取图标信息
$url = substr($data['url'], 0, 4) == 'http' ? $data['url'] : 'http:'.$data['url'];
$content = file_get_contents($url);
// 获取字体名
$font_family = '';
$pattern = '/font-family: "(.*)";/';
if (preg_match($pattern, $content, $match)) {
$font_family = $match[1];
} else {
$this->error('无法获取字体名');
}
$IconModel = new IconModel();
if ($id = $IconModel->insertGetId($data)) {
// 拉取图标列表
$pattern = '/\.(.*):before/';
if (preg_match_all($pattern, $content, $matches)) {
$icon_list = [];
foreach ($matches[1] as $match) {
$icon_list[] = [
'icon_id' => $id,
'title' => $match,
'class' => $font_family . ' ' . $match,
'code' => $match,
];
}
$IconListModel = new IconListModel();
if ($IconListModel->saveAll($icon_list)) {
$this->success('新增成功', 'index');
} else {
$IconModel->where('id', $id)->delete();
$this->error('图标添加失败');
}
}
$this->success('新增成功', 'index');
} else {
$this->error('新增失败');
}
}
return ZBuilder::make('form')
->addFormItems([
['text', 'name', '名称', '可填写中文'],
['text', 'url', '链接', '如://at.alicdn.com/t/font_588968_z5hsg7xluoh41jor.css'],
])
->fetch();
}
/**
* 图标列表
* @param string $id
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\DbException
*/
public function items($id = '')
{
$data_list = IconListModel::where($this->getMap())
->order($this->getOrder('id DESC'))
->where('icon_id', $id)
->paginate();
return ZBuilder::make('table')
->setTableName('admin_icon_list')
->addTopButtons('back')
->addTopButton('add', [
'title' => '更新图标',
'icon' => 'fa fa-refresh',
'class' => 'btn btn-primary ajax-get confirm',
'href' => url('reload', ['id' => $id])
])
->setSearch('title,code')
->addColumns([
['icon', '图标', 'callback', function($data){
return '<i class="'.$data['class'].'"></i>';
}, '__data__'],
['title', '图标标题', 'text.edit'],
['code', '图标关键词', 'text.edit'],
['class', '图标类名'],
])
->setRowList($data_list)
->fetch();
}
/**
* 更新图标
* @param string $id
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function reload($id = '')
{
$icon = IconModel::get($id);
// 获取图标信息
$url = substr($icon['url'], 0, 4) == 'http' ? $icon['url'] : 'http:'.$icon['url'];
$content = file_get_contents($url);
// 获取字体名
$font_family = '';
$pattern = '/font-family: "(.*)";/';
if (preg_match($pattern, $content, $match)) {
$font_family = $match[1];
} else {
$this->error('无法获取字体名');
}
// 拉取图标列表
$pattern = '/\.(.*):before/';
if (preg_match_all($pattern, $content, $matches)) {
$icon_list = [];
foreach ($matches[1] as $match) {
$icon_list[] = [
'icon_id' => $id,
'title' => $match,
'class' => $font_family . ' ' . $match,
'code' => $match,
];
}
$IconListModel = new IconListModel();
$IconListModel->where('icon_id', $id)->delete();
if ($IconListModel->saveAll($icon_list)) {
$this->success('更新成功');
} else {
$this->error('图标添加失败');
}
}
$this->success('更新成功');
}
/**
* 删除图标库
* @param string $ids
* @throws \think\Exception
* @throws \think\exception\PDOException
* @author 蔡伟明 <314013107@qq.com>
*/
public function delete($ids = '')
{
$ids == '' && $this->error('请选择要删除的数据');
$ids = (array)$ids;
// 删除图标列表
if (false !== IconListModel::where('icon_id', 'in', $ids)->delete()) {
// 删除图标库
if (false !== IconModel::where('id', 'in', $ids)->delete()) {
$this->success('删除成功');
}
}
$this->error('删除失败');
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace app\admin\controller;
use app\common\controller\Common;
/**
* ie提示页面控制器
* @package app\admin\controller
*/
class Ie extends Common
{
/**
* 显示ie提示
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function index(){
// ie浏览器判断
if (get_browser_type() == 'ie') {
return $this->fetch();
} else {
$this->redirect('admin/index/index');
}
}
}

View File

@ -0,0 +1,157 @@
<?php
namespace app\admin\controller;
use think\facade\Cache;
use think\facade\Env;
use think\helper\Hash;
use think\Db;
use app\common\builder\ZBuilder;
use app\user\model\User as UserModel;
/**
* 后台默认控制器
* @package app\admin\controller
*/
class Index extends Admin
{
/**
* 后台首页
* @author 蔡伟明 <314013107@qq.com>
* @return string
*/
public function index()
{
$admin_pass = Db::name('admin_user')->where('id', 1)->value('password');
if (UID == 1 && $admin_pass && Hash::check('admin', $admin_pass)) {
$this->assign('default_pass', 1);
}
return $this->fetch();
}
/**
* 清空系统缓存
* @author 蔡伟明 <314013107@qq.com>
*/
public function wipeCache()
{
$wipe_cache_type = config('wipe_cache_type');
if (!empty($wipe_cache_type)) {
foreach ($wipe_cache_type as $item) {
switch ($item) {
case 'TEMP_PATH':
array_map('unlink', glob(Env::get('runtime_path'). 'temp/*.*'));
break;
case 'LOG_PATH':
$dirs = (array) glob(Env::get('runtime_path') . 'log/*');
foreach ($dirs as $dir) {
array_map('unlink', glob($dir . '/*.log'));
}
array_map('rmdir', $dirs);
break;
case 'CACHE_PATH':
array_map('unlink', glob(Env::get('runtime_path'). 'cache/*.*'));
break;
}
}
Cache::clear();
$this->success('清空成功');
} else {
$this->error('请在系统设置中选择需要清除的缓存类型');
}
}
/**
* 个人设置
* @author 蔡伟明 <314013107@qq.com>
*/
public function profile()
{
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post();
$data['nickname'] == '' && $this->error('昵称不能为空');
$data['id'] = UID;
// 如果没有填写密码,则不更新密码
if ($data['password'] == '') {
unset($data['password']);
}
$UserModel = new UserModel();
if ($user = $UserModel->allowField(['nickname', 'email', 'password', 'mobile', 'avatar'])->update($data)) {
// 记录行为
action_log('user_edit', 'admin_user', UID, UID, get_nickname(UID));
$this->success('编辑成功');
} else {
$this->error('编辑失败');
}
}
// 获取数据
$info = UserModel::where('id', UID)->field('password', true)->find();
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->addFormItems([ // 批量添加表单项
['static', 'username', '用户名', '不可更改'],
['text', 'nickname', '昵称', '可以是中文'],
['text', 'email', '邮箱', ''],
['password', 'password', '密码', '必填6-20位'],
['text', 'mobile', '手机号'],
['image', 'avatar', '头像']
])
->setFormData($info) // 设置表单数据
->fetch();
}
/**
* 检查版本更新
* @author 蔡伟明 <314013107@qq.com>
* @return \think\response\Json
* @throws \think\db\exception\BindParamException
* @throws \think\exception\PDOException
*/
public function checkUpdate()
{
$params = config('dolphin.');
$params['domain'] = request()->domain();
$params['website'] = config('web_site_title');
$params['ip'] = $_SERVER['SERVER_ADDR'];
$params['php_os'] = PHP_OS;
$params['php_version'] = PHP_VERSION;
$params['mysql_version'] = db()->query('select version() as version')[0]['version'];
$params['server_software'] = $_SERVER['SERVER_SOFTWARE'];
$params = http_build_query($params);
$opts = [
CURLOPT_TIMEOUT => 20,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_URL => config('dolphin.product_update'),
CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'],
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $params
];
// 初始化并执行curl请求
$ch = curl_init();
curl_setopt_array($ch, $opts);
$data = curl_exec($ch);
curl_close($ch);
$result = json_decode($data, true);
if ($result['code'] == 1) {
return json([
'update' => '<a class="badge badge-primary" href="http://www.dolphinphp.com/download" target="_blank">有新版本:'.$result["version"].'</a>',
'auth' => $result['auth']
]);
} else {
return json([
'update' => '',
'auth' => $result['auth']
]);
}
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Log as LogModel;
/**
* 系统日志控制器
* @package app\admin\controller
*/
class Log extends Admin
{
/**
* 日志列表
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function index()
{
// 查询
$map = $this->getMap();
// 排序
$order = $this->getOrder('admin_log.id desc');
// 数据列表
$data_list = LogModel::getAll($map, $order);
// 分页数据
$page = $data_list->render();
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setPageTitle('系统日志') // 设置页面标题
->setSearch(['admin_action.title' => '行为名称', 'admin_user.username' => '执行者', 'admin_module.title' => '所属模块']) // 设置搜索框
->hideCheckbox()
->addColumns([ // 批量添加数据列
['id', '编号'],
['title', '行为名称'],
['username', '执行者'],
['action_ip', '执行IP', 'callback', function($value){
return long2ip(intval($value));
}],
['module_title', '所属模块'],
['create_time', '执行时间', 'datetime', '', 'Y-m-d H:i:s'],
['right_button', '操作', 'btn']
])
->addOrder(['title' => 'admin_action', 'username' => 'admin_user', 'module_title' => 'admin_module.title'])
->addFilter(['admin_action.title', 'admin_user.username', 'module_title' => 'admin_module.title'])
->addRightButton('edit', ['icon' => 'fa fa-eye', 'title' => '详情', 'href' => url('details', ['id' => '__id__'])])
->setRowList($data_list) // 设置表格数据
->setPages($page) // 设置分页数据
->fetch(); // 渲染模板
}
/**
* 日志详情
* @param null $id 日志id
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function details($id = null)
{
if ($id === null) $this->error('缺少参数');
$info = LogModel::getAll(['admin_log.id' => $id]);
$info = $info[0];
$info['action_ip'] = long2ip(intval($info['action_ip']));
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('编辑') // 设置页面标题
->addFormItems([ // 批量添加表单项
['hidden', 'id'],
['static', 'title', '行为名称'],
['static', 'username', '执行者'],
['static', 'record_id', '目标ID'],
['static', 'action_ip', '执行IP'],
['static', 'module_title', '所属模块'],
['textarea', 'remark', '备注'],
])
->hideBtn('submit')
->setFormData($info) // 设置表单数据
->fetch();
}
}

View File

@ -0,0 +1,537 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Module as ModuleModel;
use app\admin\model\Menu as MenuModel;
use app\user\model\Role as RoleModel;
use think\facade\Cache;
/**
* 节点管理
* @package app\admin\controller
*/
class Menu extends Admin
{
/**
* 节点首页
* @param string $group 分组
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \Exception
*/
public function index($group = 'admin')
{
// 保存模块排序
if ($this->request->isPost()) {
$modules = $this->request->post('sort/a');
if ($modules) {
$data = [];
foreach ($modules as $key => $module) {
$data[] = [
'id' => $module,
'sort' => $key + 1
];
}
$MenuModel = new MenuModel();
if (false !== $MenuModel->saveAll($data)) {
$this->success('保存成功');
} else {
$this->error('保存失败');
}
}
}
cookie('__forward__', $_SERVER['REQUEST_URI']);
// 配置分组信息
$list_group = MenuModel::getGroup();
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
// 模块排序
if ($group == 'module-sort') {
$map['status'] = 1;
$map['pid'] = 0;
$modules = MenuModel::where($map)->order('sort,id')->column('icon,title', 'id');
$this->assign('modules', $modules);
} else {
// 获取节点数据
$data_list = MenuModel::getMenusByGroup($group);
$max_level = $this->request->get('max', 0);
$this->assign('menus', $this->getNestMenu($data_list, $max_level));
}
$this->assign('tab_nav', ['tab_list' => $tab_list, 'curr_tab' => $group]);
$this->assign('page_title', '节点管理');
return $this->fetch();
}
/**
* 新增节点
* @param string $module 所属模块
* @param string $pid 所属节点id
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \Exception
*/
public function add($module = 'admin', $pid = '')
{
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post('', null, 'trim');
// 验证
$result = $this->validate($data, 'Menu');
// 验证失败 输出错误信息
if(true !== $result) $this->error($result);
// 顶部节点url检查
if ($data['pid'] == 0 && $data['url_value'] == '' && ($data['url_type'] == 'module_admin' || $data['url_type'] == 'module_home')) {
$this->error('顶级节点的节点链接不能为空');
}
if ($menu = MenuModel::create($data)) {
// 自动创建子节点
if ($data['auto_create'] == 1 && !empty($data['child_node'])) {
unset($data['icon']);
unset($data['params']);
$this->createChildNode($data, $menu['id']);
}
// 添加角色权限
if (isset($data['role'])) {
$this->setRoleMenu($menu['id'], $data['role']);
}
Cache::clear();
// 记录行为
$details = '所属模块('.$data['module'].'),所属节点ID('.$data['pid'].'),节点标题('.$data['title'].'),节点链接('.$data['url_value'].')';
action_log('menu_add', 'admin_menu', $menu['id'], UID, $details);
$this->success('新增成功', cookie('__forward__'));
} else {
$this->error('新增失败');
}
}
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('新增节点')
->addLinkage('module', '所属模块', '', ModuleModel::getModule(), $module, url('ajax/getModuleMenus'), 'pid')
->addFormItems([
['select', 'pid', '所属节点', '所属上级节点', MenuModel::getMenuTree(0, '', $module), $pid],
['text', 'title', '节点标题'],
['radio', 'url_type', '链接类型', '', ['module_admin' => '模块链接(后台)', 'module_home' => '模块链接(前台)', 'link' => '普通链接'], 'module_admin']
])
->addFormItem(
'text',
'url_value',
'节点链接',
"可留空,如果是模块链接,请填写<code>模块/控制器/操作</code>,如:<code>admin/menu/add</code>。如果是普通链接则直接填写url地址<code>http://www.dolphinphp.com</code>"
)
->addText('params', '参数', '如a=1&b=2')
->addSelect('role', '角色', '除超级管理员外,拥有该节点权限的角色', RoleModel::where('id', 'neq', 1)->column('id,name'), '', 'multiple')
->addRadio('auto_create', '自动添加子节点', '选择【是】则自动添加指定的子节点', ['否', '是'], 0)
->addCheckbox('child_node', '子节点', '仅上面选项为【是】时起作用', ['add' => '新增', 'edit' => '编辑', 'delete' => '删除', 'enable' => '启用', 'disable' => '禁用', 'quickedit' => '快速编辑'], 'add,edit,delete,enable,disable,quickedit')
->addRadio('url_target', '打开方式', '', ['_self' => '当前窗口', '_blank' => '新窗口'], '_self')
->addIcon('icon', '图标', '导航图标')
->addRadio('online_hide', '网站上线后隐藏', '关闭开发模式后,则隐藏该菜单节点', ['否', '是'], 0)
->addText('sort', '排序', '', 100)
->setTrigger('auto_create', '1', 'child_node', false)
->fetch();
}
/**
* 编辑节点
* @param int $id 节点ID
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function edit($id = 0)
{
if ($id === 0) $this->error('缺少参数');
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post('', null, 'trim');
// 验证
$result = $this->validate($data, 'Menu');
// 验证失败 输出错误信息
if(true !== $result) $this->error($result);
// 顶部节点url检查
if ($data['pid'] == 0 && $data['url_value'] == '' && ($data['url_type'] == 'module_admin' || $data['url_type'] == 'module_home')) {
$this->error('顶级节点的节点链接不能为空');
}
// 设置角色权限
$this->setRoleMenu($data['id'], isset($data['role']) ? $data['role'] : []);
// 验证是否更改所属模块,如果是,则该节点的所有子孙节点的模块都要修改
$map['id'] = $data['id'];
$map['module'] = $data['module'];
if (!MenuModel::where($map)->find()) {
MenuModel::changeModule($data['id'], $data['module']);
}
if (MenuModel::update($data)) {
Cache::clear();
// 记录行为
$details = '节点ID('.$id.')';
action_log('menu_edit', 'admin_menu', $id, UID, $details);
$this->success('编辑成功', cookie('__forward__'));
} else {
$this->error('编辑失败');
}
}
// 获取数据
$info = MenuModel::get($id);
// 拥有该节点权限的角色
$info['role'] = RoleModel::getRoleWithMenu($id);
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('编辑节点')
->addFormItem('hidden', 'id')
->addLinkage('module', '所属模块', '', ModuleModel::getModule(), '', url('ajax/getModuleMenus'), 'pid')
->addFormItem('select', 'pid', '所属节点', '所属上级节点', MenuModel::getMenuTree(0, '', $info['module']))
->addFormItem('text', 'title', '节点标题')
->addFormItem('radio', 'url_type', '链接类型', '', ['module_admin' => '模块链接(后台)', 'module_home' => '模块链接(前台)', 'link' => '普通链接'], 'module_admin')
->addFormItem(
'text',
'url_value',
'节点链接',
"可留空,如果是模块链接,请填写<code>模块/控制器/操作</code>,如:<code>admin/menu/add</code>。如果是普通链接则直接填写url地址<code>http://www.dolphinphp.com</code>"
)
->addText('params', '参数', '如a=1&b=2')
->addSelect('role', '角色', '除超级管理员外,拥有该节点权限的角色', RoleModel::where('id', 'neq', 1)->column('id,name'), '', 'multiple')
->addRadio('url_target', '打开方式', '', ['_self' => '当前窗口', '_blank' => '新窗口'], '_self')
->addIcon('icon', '图标', '导航图标')
->addRadio('online_hide', '网站上线后隐藏', '关闭开发模式后,则隐藏该菜单节点', ['否', '是'])
->addText('sort', '排序', '', 100)
->setFormData($info)
->fetch();
}
/**
* 设置角色权限
* @param string $role_id 角色id
* @param array $roles 角色id
* @author 蔡伟明 <314013107@qq.com>
* @throws \Exception
*/
private function setRoleMenu($role_id = '', $roles = [])
{
$RoleModel = new RoleModel();
// 该节点的所有子节点,包括本身节点
$menu_child = MenuModel::getChildsId($role_id);
$menu_child[] = (int)$role_id;
// 该节点的所有上下级节点
$menu_all = MenuModel::getLinkIds($role_id);
$menu_all = array_map('strval', $menu_all);
if (!empty($roles)) {
// 拥有该节点的所有角色id及节点权限
$role_menu_auth = RoleModel::getRoleWithMenu($role_id, true);
// 已有该节点权限的角色id
$role_exists = array_keys($role_menu_auth);
// 新节点权限的角色
$role_new = $roles;
// 原有权限角色差集
$role_diff = array_diff($role_exists, $role_new);
// 新权限角色差集
$role_diff_new = array_diff($role_new, $role_exists);
// 新节点角色权限
$role_new_auth = RoleModel::getAuthWithRole($roles);
// 删除原先角色的该节点权限
if ($role_diff) {
$role_del_auth = [];
foreach ($role_diff as $role) {
$auth = json_decode($role_menu_auth[$role], true);
$auth_new = array_diff($auth, $menu_child);
$role_del_auth[] = [
'id' => $role,
'menu_auth' => array_values($auth_new)
];
}
if ($role_del_auth) {
$RoleModel->saveAll($role_del_auth);
}
}
// 新增权限角色
if ($role_diff_new) {
$role_update_auth = [];
foreach ($role_new_auth as $role => $auth) {
$auth = json_decode($auth, true);
if (in_array($role, $role_diff_new)) {
$auth = array_unique(array_merge($auth, $menu_all));
}
$role_update_auth[] = [
'id' => $role,
'menu_auth' => array_values($auth)
];
}
if ($role_update_auth) {
$RoleModel->saveAll($role_update_auth);
}
}
} else {
$role_menu_auth = RoleModel::getRoleWithMenu($role_id, true);
$role_del_auth = [];
foreach ($role_menu_auth as $role => $auth) {
$auth = json_decode($auth, true);
$auth_new = array_diff($auth, $menu_child);
$role_del_auth[] = [
'id' => $role,
'menu_auth' => array_values($auth_new)
];
}
if ($role_del_auth) {
$RoleModel->saveAll($role_del_auth);
}
}
}
/**
* 删除节点
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function delete($record = [])
{
$id = $this->request->param('id');
$menu = MenuModel::where('id', $id)->find();
if ($menu['system_menu'] == '1') $this->error('系统节点,禁止删除');
// 获取该节点的所有后辈节点id
$menu_childs = MenuModel::getChildsId($id);
// 要删除的所有节点id
$all_ids = array_merge([(int)$id], $menu_childs);
// 删除节点
if (MenuModel::destroy($all_ids)) {
Cache::clear();
// 记录行为
$details = '节点ID('.$id.'),节点标题('.$menu['title'].'),节点链接('.$menu['url_value'].')';
action_log('menu_delete', 'admin_menu', $id, UID, $details);
$this->success('删除成功');
} else {
$this->error('删除失败');
}
}
/**
* 保存节点排序
* @author 蔡伟明 <314013107@qq.com>
*/
public function save()
{
if ($this->request->isPost()) {
$data = $this->request->post();
if (!empty($data)) {
$menus = $this->parseMenu($data['menus']);
foreach ($menus as $menu) {
if ($menu['pid'] == 0) {
continue;
}
MenuModel::update($menu);
}
Cache::clear();
$this->success('保存成功');
} else {
$this->error('没有需要保存的节点');
}
}
$this->error('非法请求');
}
/**
* 添加子节点
* @param array $data 节点数据
* @param string $pid 父节点id
* @author 蔡伟明 <314013107@qq.com>
*/
private function createChildNode($data = [], $pid = '')
{
$url_value = substr($data['url_value'], 0, strrpos($data['url_value'], '/')).'/';
$child_node = [];
$data['pid'] = $pid;
foreach ($data['child_node'] as $item) {
switch ($item) {
case 'add':
$data['title'] = '新增';
break;
case 'edit':
$data['title'] = '编辑';
break;
case 'delete':
$data['title'] = '删除';
break;
case 'enable':
$data['title'] = '启用';
break;
case 'disable':
$data['title'] = '禁用';
break;
case 'quickedit':
$data['title'] = '快速编辑';
break;
}
$data['url_value'] = $url_value.$item;
$data['create_time'] = $this->request->time();
$data['update_time'] = $this->request->time();
$child_node[] = $data;
}
if ($child_node) {
$MenuModel = new MenuModel();
$MenuModel->insertAll($child_node);
}
}
/**
* 递归解析节点
* @param array $menus 节点数据
* @param int $pid 上级节点id
* @author 蔡伟明 <314013107@qq.com>
* @return array 解析成可以写入数据库的格式
*/
private function parseMenu($menus = [], $pid = 0)
{
$sort = 1;
$result = [];
foreach ($menus as $menu) {
$result[] = [
'id' => (int)$menu['id'],
'pid' => (int)$pid,
'sort' => $sort,
];
if (isset($menu['children'])) {
$result = array_merge($result, $this->parseMenu($menu['children'], $menu['id']));
}
$sort ++;
}
return $result;
}
/**
* 获取嵌套式节点
* @param array $lists 原始节点数组
* @param int $pid 父级id
* @param int $max_level 最多返回多少层0为不限制
* @param int $curr_level 当前层数
* @author 蔡伟明 <314013107@qq.com>
* @return string
*/
private function getNestMenu($lists = [], $max_level = 0, $pid = 0, $curr_level = 1)
{
$result = '';
foreach ($lists as $key => $value) {
if ($value['pid'] == $pid) {
$disable = $value['status'] == 0 ? 'dd-disable' : '';
// 组合节点
$result .= '<li class="dd-item dd3-item '.$disable.'" data-id="'.$value['id'].'">';
$result .= '<div class="dd-handle dd3-handle">拖拽</div><div class="dd3-content"><i class="'.$value['icon'].'"></i> '.$value['title'];
if ($value['url_value'] != '') {
$result .= '<span class="link"><i class="fa fa-link"></i> '.$value['url_value'].'</span>';
}
$result .= '<div class="action">';
$result .= '<a href="'.url('add', ['module' => $value['module'], 'pid' => $value['id']]).'" data-toggle="tooltip" data-original-title="新增子节点"><i class="list-icon fa fa-plus fa-fw"></i></a><a href="'.url('edit', ['id' => $value['id']]).'" data-toggle="tooltip" data-original-title="编辑"><i class="list-icon fa fa-pencil fa-fw"></i></a>';
if ($value['status'] == 0) {
// 启用
$result .= '<a href="javascript:void(0);" data-ids="'.$value['id'].'" class="enable" data-toggle="tooltip" data-original-title="启用"><i class="list-icon fa fa-check-circle-o fa-fw"></i></a>';
} else {
// 禁用
$result .= '<a href="javascript:void(0);" data-ids="'.$value['id'].'" class="disable" data-toggle="tooltip" data-original-title="禁用"><i class="list-icon fa fa-ban fa-fw"></i></a>';
}
$result .= '<a href="'.url('delete', ['id' => $value['id'], 'table' => 'admin_menu']).'" data-toggle="tooltip" data-original-title="删除" class="ajax-get confirm"><i class="list-icon fa fa-times fa-fw"></i></a></div>';
$result .= '</div>';
if ($max_level == 0 || $curr_level != $max_level) {
unset($lists[$key]);
// 下级节点
$children = $this->getNestMenu($lists, $max_level, $value['id'], $curr_level + 1);
if ($children != '') {
$result .= '<ol class="dd-list">'.$children.'</ol>';
}
}
$result .= '</li>';
}
}
return $result;
}
/**
* 启用节点
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function enable($record = [])
{
$id = input('param.ids');
$menu = MenuModel::where('id', $id)->find();
$details = '节点ID('.$id.'),节点标题('.$menu['title'].'),节点链接('.$menu['url_value'].')';
$this->setStatus('enable', ['menu_enable', 'admin_menu', $id, UID, $details]);
}
/**
* 禁用节点
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function disable($record = [])
{
$id = input('param.ids');
$menu = MenuModel::where('id', $id)->find();
$details = '节点ID('.$id.'),节点标题('.$menu['title'].'),节点链接('.$menu['url_value'].')';
$this->setStatus('disable', ['menu_disable', 'admin_menu', $id, UID, $details]);
}
/**
* 设置状态
* @param string $type 类型
* @param array $record 行为日志
* @author 小乌 <82950492@qq.com>
*/
public function setStatus($type = '', $record = [])
{
$id = input('param.ids');
$status = $type == 'enable' ? 1 : 0;
if (false !== MenuModel::where('id', $id)->setField('status', $status)) {
Cache::clear();
// 记录行为日志
if (!empty($record)) {
call_user_func_array('action_log', $record);
}
$this->success('操作成功');
} else {
$this->error('操作失败');
}
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\user\model\Message as MessageModel;
/**
* 消息控制器
* @package app\admin\controller
*/
class Message extends Admin
{
/**
* 消息中心
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\DbException
*/
public function index()
{
$data_list = MessageModel::where($this->getMap())
->where('uid_receive', UID)
->order($this->getOrder('id DESC'))
->paginate();
return ZBuilder::make('table')
->setTableName('admin_message')
->addTopButton('enable', ['title' => '设置已阅读'])
->addTopButton('delete')
->addRightButton('enable', ['title' => '设置已阅读'])
->addRightButton('delete')
->addColumns([
['uid_send', '发送者', 'callback', 'get_nickname'],
['type', '分类'],
['content', '内容'],
['status', '状态', 'status', '', ['未读', '已读']],
['create_time', '发送时间', 'datetime'],
['read_time', '阅读时间', 'datetime'],
['right_button', '操作', 'btn'],
])
->addFilter('type')
->addFilter('status', ['未读', '已读'])
->setRowList($data_list)
->fetch();
}
/**
* 设置已阅读
* @param array $ids
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function enable($ids = [])
{
empty($ids) && $this->error('参数错误');
$map = [
['uid_receive', '=', UID],
['id', 'in', $ids]
];
$result = MessageModel::where($map)
->update(['status' => 1, 'read_time' => $this->request->time()]);
if (false !== $result) {
$this->success('设置成功');
} else {
$this->error('设置失败');
}
}
}

View File

@ -0,0 +1,607 @@
<?php
namespace app\admin\controller;
use app\admin\model\Module as ModuleModel;
use app\admin\model\Plugin as PluginModel;
use app\admin\model\Menu as MenuModel;
use app\admin\model\Action as ActionModel;
use think\facade\Cache;
use util\Database;
use util\Sql;
use util\File;
use util\PHPZip;
use util\Tree;
use think\Db;
use think\facade\Hook;
use think\facade\Env;
/**
* 模块管理控制器
* @package app\admin\controller
*/
class Module extends Admin
{
/**
* 模块首页
* @param string $group 分组
* @param string $type 显示类型
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function index($group = 'local', $type = '')
{
// 配置分组信息
$list_group = ['local' => '本地模块'];
$tab_list = [];
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
// 监听tab钩子
Hook::listen('module_index_tab_list', $tab_list);
switch ($group) {
case 'local':
// 查询条件
$keyword = $this->request->get('keyword', '');
if (input('?param.status') && input('param.status') != '_all') {
$status = input('param.status');
} else {
$status = '';
}
$ModuleModel = new ModuleModel();
$result = $ModuleModel->getAll($keyword, $status);
if ($result['modules'] === false) {
$this->error($ModuleModel->getError());
}
$type_show = Cache::get('module_type_show');
$type_show = $type != '' ? $type : ($type_show == false ? 'block' : $type_show);
Cache::set('module_type_show', $type_show);
$type = $type_show == 'block' ? 'list' : 'block';
$this->assign('page_title', '模块管理');
$this->assign('modules', $result['modules']);
$this->assign('total', $result['total']);
$this->assign('tab_nav', ['tab_list' => $tab_list, 'curr_tab' => $group]);
$this->assign('type', $type);
return $this->fetch();
break;
case 'online':
return '<h2>正在建设中...</h2>';
break;
default:
$this->error('非法操作');
}
}
/**
* 安装模块
* @param string $name 模块标识
* @param int $confirm 是否确认
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function install($name = '', $confirm = 0)
{
// 设置最大执行时间和内存大小
ini_set('max_execution_time', '0');
ini_set('memory_limit', '1024M');
if ($name == '') $this->error('模块不存在!');
if ($name == 'admin' || $name == 'user') $this->error('禁止操作系统核心模块!');
// 模块配置信息
$module_info = ModuleModel::getInfoFromFile($name);
if ($confirm == 0) {
$need_module = [];
$need_plugin = [];
$table_check = [];
// 检查模块依赖
if (isset($module_info['need_module']) && !empty($module_info['need_module'])) {
$need_module = $this->checkDependence('module', $module_info['need_module']);
}
// 检查插件依赖
if (isset($module_info['need_plugin']) && !empty($module_info['need_plugin'])) {
$need_plugin = $this->checkDependence('plugin', $module_info['need_plugin']);
}
// 检查数据表
if (isset($module_info['tables']) && !empty($module_info['tables'])) {
foreach ($module_info['tables'] as $table) {
if (Db::query("SHOW TABLES LIKE '".config('database.prefix')."{$table}'")) {
$table_check[] = [
'table' => config('database.prefix')."{$table}",
'result' => '<span class="text-danger">存在同名</span>'
];
} else {
$table_check[] = [
'table' => config('database.prefix')."{$table}",
'result' => '<i class="fa fa-check text-success"></i>'
];
}
}
}
$this->assign('need_module', $need_module);
$this->assign('need_plugin', $need_plugin);
$this->assign('table_check', $table_check);
$this->assign('name', $name);
$this->assign('page_title', '安装模块:'. $name);
return $this->fetch();
}
// 执行安装文件
$install_file = realpath(Env::get('app_path').$name.'/install.php');
if (file_exists($install_file)) {
@include($install_file);
}
// 执行安装模块sql文件
$sql_file = realpath(Env::get('app_path').$name.'/sql/install.sql');
if (file_exists($sql_file)) {
if (isset($module_info['database_prefix']) && !empty($module_info['database_prefix'])) {
$sql_statement = Sql::getSqlFromFile($sql_file, false, [$module_info['database_prefix'] => config('database.prefix')]);
} else {
$sql_statement = Sql::getSqlFromFile($sql_file);
}
if (!empty($sql_statement)) {
foreach ($sql_statement as $value) {
try{
Db::execute($value);
}catch(\Exception $e){
$this->error('导入SQL失败请检查install.sql的语句是否正确');
}
}
}
}
// 添加菜单
$menus = ModuleModel::getMenusFromFile($name);
if (is_array($menus) && !empty($menus)) {
if (false === $this->addMenus($menus, $name)) {
$this->error('菜单添加失败,请重新安装');
}
}
// 检查是否有模块设置信息
if (isset($module_info['config']) && !empty($module_info['config'])) {
$module_info['config'] = json_encode(parse_config($module_info['config']));
}
// 检查是否有模块授权配置
if (isset($module_info['access']) && !empty($module_info['access'])) {
$module_info['access'] = json_encode($module_info['access']);
}
// 检查是否有行为规则
if (isset($module_info['action']) && !empty($module_info['action'])) {
$ActionModel = new ActionModel;
if (!$ActionModel->saveAll($module_info['action'])) {
MenuModel::where('module', $name)->delete();
$this->error('行为添加失败,请重新安装');
}
}
// 将模块信息写入数据库
$ModuleModel = new ModuleModel($module_info);
$allowField = ['name','title','icon','description','author','author_url','config','access','version','identifier','status'];
if ($ModuleModel->allowField($allowField)->save()) {
// 复制静态资源目录
File::copy_dir(Env::get('app_path'). $name. '/public', Env::get('root_path'). 'public');
// 删除静态资源目录
File::del_dir(Env::get('app_path'). $name. '/public');
cache('modules', null);
cache('module_all', null);
// 记录行为
action_log('module_install', 'admin_module', 0, UID, $module_info['title']);
$this->success('模块安装成功', 'index');
} else {
MenuModel::where('module', $name)->delete();
$this->error('模块安装失败');
}
}
/**
* 卸载模块
* @param string $name 模块名
* @param int $confirm 是否确认
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function uninstall($name = '', $confirm = 0)
{
if ($name == '') $this->error('模块不存在!');
if ($name == 'admin') $this->error('禁止操作系统模块!');
// 模块配置信息
$module_info = ModuleModel::getInfoFromFile($name);
if ($confirm == 0) {
$this->assign('name', $name);
$this->assign('page_title', '卸载模块:'. $name);
return $this->fetch();
}
// 执行卸载文件
$uninstall_file = realpath(Env::get('app_path').$name.'/uninstall.php');
if (file_exists($uninstall_file)) {
@include($uninstall_file);
}
// 执行卸载模块sql文件
$clear = $this->request->get('clear');
if ($clear == 1) {
$sql_file = realpath(Env::get('app_path').$name.'/sql/uninstall.sql');
if (file_exists($sql_file)) {
if (isset($module_info['database_prefix']) && !empty($module_info['database_prefix'])) {
$sql_statement = Sql::getSqlFromFile($sql_file, false, [$module_info['database_prefix'] => config('database.prefix')]);
} else {
$sql_statement = Sql::getSqlFromFile($sql_file);
}
if (!empty($sql_statement)) {
foreach ($sql_statement as $sql) {
try{
Db::execute($sql);
}catch(\Exception $e){
$this->error('卸载失败请检查uninstall.sql的语句是否正确');
}
}
}
}
}
// 删除菜单
if (false === MenuModel::where('module', $name)->delete()) {
$this->error('菜单删除失败,请重新卸载');
}
// 删除授权信息
if (false === Db::name('admin_access')->where('module', $name)->delete()) {
$this->error('删除授权信息失败,请重新卸载');
}
// 删除行为规则
if (false === Db::name('admin_action')->where('module', $name)->delete()) {
$this->error('删除行为信息失败,请重新卸载');
}
// 删除模块信息
if (ModuleModel::where('name', $name)->delete()) {
// 复制静态资源目录
File::copy_dir(Env::get('root_path'). 'public/static/'. $name, Env::get('app_path').$name.'/public/static/'. $name);
// 删除静态资源目录
File::del_dir(Env::get('root_path'). 'public/static/'. $name);
cache('modules', null);
cache('module_all', null);
// 记录行为
action_log('module_uninstall', 'admin_module', 0, UID, $module_info['title']);
$this->success('模块卸载成功', 'index');
} else {
$this->error('模块卸载失败');
}
}
/**
* 更新模块配置
* @param string $name 模块名
* @author 蔡伟明 <314013107@qq.com>
*/
public function update($name = '')
{
$name == '' && $this->error('缺少模块名!');
$Module = ModuleModel::get(['name' => $name]);
!$Module && $this->error('模块不存在,或未安装');
// 模块配置信息
$module_info = ModuleModel::getInfoFromFile($name);
unset($module_info['name']);
// 检查是否有模块设置信息
if (isset($module_info['config']) && !empty($module_info['config'])) {
$module_info['config'] = json_encode(parse_config($module_info['config']));
} else {
$module_info['config'] = '';
}
// 检查是否有模块授权配置
if (isset($module_info['access']) && !empty($module_info['access'])) {
$module_info['access'] = json_encode($module_info['access']);
} else {
$module_info['access'] = '';
}
// 更新模块信息
if (false !== $Module->save($module_info)) {
$this->success('模块配置更新成功');
} else {
$this->error('模块配置更新失败,请重试');
}
}
/**
* 导出模块
* @param string $name 模块名
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function export($name = '')
{
if ($name == '') $this->error('缺少模块名');
$export_data = $this->request->get('export_data', '');
if ($export_data == '') {
$this->assign('page_title', '导出模块:'. $name);
return $this->fetch();
}
// 模块导出目录
$module_dir = Env::get('root_path'). 'export/module/'. $name;
// 删除旧的导出数据
if (is_dir($module_dir)) {
File::del_dir($module_dir);
}
// 复制模块目录到导出目录
File::copy_dir(Env::get('app_path'). $name, $module_dir);
// 复制静态资源目录
File::copy_dir(Env::get('root_path'). 'public/static/'. $name, $module_dir.'/public/static/'. $name);
// 模块本地配置信息
$module_info = ModuleModel::getInfoFromFile($name);
// 检查是否有模块设置信息
if (isset($module_info['config'])) {
$db_config = ModuleModel::where('name', $name)->value('config');
$db_config = json_decode($db_config, true);
// 获取最新的模块设置信息
$module_info['config'] = set_config_value($module_info['config'], $db_config);
}
// 检查是否有模块行为信息
$action = Db::name('admin_action')->where('module', $name)->field('module,name,title,remark,rule,log,status')->select();
if ($action) {
$module_info['action'] = $action;
}
// 表前缀
$module_info['database_prefix'] = config('database.prefix');
// 生成配置文件
if (false === $this->buildInfoFile($module_info, $name)) {
$this->error('模块配置文件创建失败,请重新导出');
}
// 获取模型菜单并导出
$fields = 'id,pid,title,icon,url_type,url_value,url_target,online_hide,sort,status';
$menus = MenuModel::getMenusByGroup($name, $fields);
if (false === $this->buildMenuFile($menus, $name)) {
$this->error('模型菜单文件创建失败,请重新导出');
}
// 导出数据库表
if (isset($module_info['tables']) && !empty($module_info['tables'])) {
if (!is_dir($module_dir. '/sql')) {
mkdir($module_dir. '/sql', 644, true);
}
if (!Database::export($module_info['tables'], $module_dir. '/sql/install.sql', config('database.prefix'), $export_data)) {
$this->error('数据库文件创建失败,请重新导出');
}
if (!Database::exportUninstall($module_info['tables'], $module_dir. '/sql/uninstall.sql', config('database.prefix'))) {
$this->error('数据库文件创建失败,请重新导出');
}
}
// 记录行为
action_log('module_export', 'admin_module', 0, UID, $module_info['title']);
// 打包下载
$archive = new PHPZip;
return $archive->ZipAndDownload($module_dir, $name);
}
/**
* 创建模块菜单文件
* @param array $menus 菜单
* @param string $name 模块名
* @author 蔡伟明 <314013107@qq.com>
* @return int
*/
private function buildMenuFile($menus = [], $name = '')
{
$menus = Tree::toLayer($menus);
// 美化数组格式
$menus = var_export($menus, true);
$menus = preg_replace("/(\d+|'id'|'pid') =>(.*)/", '', $menus);
$menus = preg_replace("/'child' => (.*)(\r\n|\r|\n)\s*array/", "'child' => $1array", $menus);
$menus = str_replace(['array (', ')'], ['[', ']'], $menus);
$menus = preg_replace("/(\s*?\r?\n\s*?)+/", "\n", $menus);
$content = <<<INFO
<?php
/**
* 菜单信息
*/
return {$menus};
INFO;
// 写入到文件
return file_put_contents(Env::get('root_path'). 'export/module/'. $name. '/menus.php', $content);
}
/**
* 创建模块配置文件
* @param array $info 模块配置信息
* @param string $name 模块名
* @author 蔡伟明 <314013107@qq.com>
* @return int
*/
private function buildInfoFile($info = [], $name = '')
{
// 美化数组格式
$info = var_export($info, true);
$info = preg_replace("/'(.*)' => (.*)(\r\n|\r|\n)\s*array/", "'$1' => array", $info);
$info = preg_replace("/(\d+) => (\s*)(\r\n|\r|\n)\s*array/", "array", $info);
$info = preg_replace("/(\d+ => )/", "", $info);
$info = preg_replace("/array \((\r\n|\r|\n)\s*\)/", "[)", $info);
$info = preg_replace("/array \(/", "[", $info);
$info = preg_replace("/\)/", "]", $info);
$content = <<<INFO
<?php
/**
* 模块信息
*/
return {$info};
INFO;
// 写入到文件
return file_put_contents(Env::get('root_path'). 'export/module/'. $name. '/info.php', $content);
}
/**
* 设置状态
* @param string $type 类型disable/enable
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function setStatus($type = '', $record = [])
{
$ids = input('param.ids');
empty($ids) && $this->error('缺少主键');
$module = ModuleModel::where('id', $ids)->find();
$module['system_module'] == 1 && $this->error('禁止操作系统内置模块');
$status = $type == 'enable' ? 1 : 0;
// 将模块对应的菜单禁用或启用
$map = [
'pid' => 0,
'module' => $module['name']
];
MenuModel::where($map)->setField('status', $status);
if (false !== ModuleModel::where('id', $ids)->setField('status', $status)) {
// 记录日志
call_user_func_array('action_log', ['module_'.$type, 'admin_module', 0, UID, $module['title']]);
$this->success('操作成功');
} else {
$this->error('操作失败');
}
}
/**
* 禁用模块
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function disable($record = [])
{
$this->setStatus('disable');
}
/**
* 启用模块
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function enable($record = [])
{
$this->setStatus('enable');
}
/**
* 添加模型菜单
* @param array $menus 菜单
* @param string $module 模型名称
* @param int $pid 父级ID
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
private function addMenus($menus = [], $module = '', $pid = 0)
{
foreach ($menus as $menu) {
$data = [
'pid' => $pid,
'module' => $module,
'title' => $menu['title'],
'icon' => isset($menu['icon']) ? $menu['icon'] : 'fa fa-fw fa-puzzle-piece',
'url_type' => isset($menu['url_type']) ? $menu['url_type'] : 'module_admin',
'url_value' => isset($menu['url_value']) ? $menu['url_value'] : '',
'url_target' => isset($menu['url_target']) ? $menu['url_target'] : '_self',
'online_hide' => isset($menu['online_hide']) ? $menu['online_hide'] : 0,
'status' => isset($menu['status']) ? $menu['status'] : 1
];
$result = MenuModel::create($data);
if (!$result) return false;
if (isset($menu['child'])) {
$this->addMenus($menu['child'], $module, $result['id']);
}
}
return true;
}
/**
* 检查依赖
* @param string $type 类型module/plugin
* @param array $data 检查数据
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
private function checkDependence($type = '', $data = [])
{
$need = [];
foreach ($data as $key => $value) {
if (!isset($value[3])) {
$value[3] = '=';
}
// 当前版本
if ($type == 'module') {
$curr_version = ModuleModel::where('identifier', $value[1])->value('version');
} else {
$curr_version = PluginModel::where('identifier', $value[1])->value('version');
}
// 比对版本
$result = version_compare($curr_version, $value[2], $value[3]);
$need[$key] = [
$type => $value[0],
'identifier' => $value[1],
'version' => $curr_version ? $curr_version : '未安装',
'version_need' => $value[3].$value[2],
'result' => $result ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>'
];
}
return $need;
}
}

View File

@ -0,0 +1,146 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Packet as PacketModel;
/**
* 数据包控制器
* @package app\admin\controller
*/
class Packet extends Admin
{
/**
* 首页
* @param string $group 分组
* @author 蔡伟明 <314013107@qq.com>
* @return mixed|string
* @throws \think\Exception
*/
public function index($group = 'local')
{
// 配置分组信息
$list_group = ['local' => '本地数据包'];
$tab_list = [];
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
$PacketModel = new PacketModel;
$data_list = $PacketModel->getAll();
foreach ($data_list as &$value) {
if (isset($value['author_url']) && !empty($value['author_url'])) {
$value['author'] = '<a href="'. $value['author_url']. '" target="_blank">'. $value['author'] .'</a>';
}
}
if ($data_list === false) {
$this->error($PacketModel->getError());
}
// 自定义按钮
$btn_install = [
'title' => '安装',
'icon' => 'fa fa-fw fa-sign-in',
'class' => 'btn btn-xs btn-default ajax-get confirm',
'href' => url('install', ['name' => '__id__'])
];
$btn_uninstall = [
'title' => '卸载',
'icon' => 'fa fa-fw fa-sign-out',
'class' => 'btn btn-xs btn-default ajax-get confirm',
'href' => url('uninstall', ['name' => '__id__'])
];
$btn_install_all = [
'title' => '安装',
'icon' => 'fa fa-fw fa-sign-in',
'class' => 'btn btn-primary ajax-post confirm',
'href' => url('install')
];
$btn_uninstall_all = [
'title' => '卸载',
'icon' => 'fa fa-fw fa-sign-out',
'class' => 'btn btn-danger ajax-post confirm',
'href' => url('uninstall')
];
switch ($group) {
case 'local':
// 使用ZBuilder快速创建数据表格
return ZBuilder::make('table')
->setPageTitle('数据包管理') // 设置页面标题
->setPrimaryKey('name')
->setTabNav($tab_list, $group) // 设置tab分页
->addColumns([ // 批量添加数据列
['name', '名称'],
['title', '标题'],
['author', '作者'],
['version', '版本号'],
['status', '是否安装', 'yesno'],
['right_button', '操作', 'btn']
])
->addTopButton('custom', $btn_install_all)
->addTopButton('custom', $btn_uninstall_all)
->addRightButton('custom', $btn_install) // 添加右侧按钮
->addRightButton('custom', $btn_uninstall) // 添加右侧按钮
->setRowList($data_list) // 设置表格数据
->fetch(); // 渲染模板
break;
case 'online':
return '<h2>正在制作中...</h2>';
break;
}
}
/**
* 安装
* @param string $name 数据包名
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function install($name = '')
{
$names = $name != '' ? (array)$name : $this->request->param('ids/a');
foreach ($names as $name) {
$result = PacketModel::install($name);
if ($result === true) {
if (!PacketModel::where('name', $name)->find()) {
$data = PacketModel::getInfoFromFile($name);
$data['status'] = 1;
$data['tables'] = json_encode($data['tables']);
PacketModel::create($data);
}
} else {
$this->error('安装失败:'. $result);
}
}
// 记录行为
$packet_titles = PacketModel::where('name', 'in', $names)->column('title');
action_log('packet_install', 'admin_packet', 0, UID, implode('、', $packet_titles));
$this->success('安装成功');
}
/**
* 卸载
* @param string $name 数据包名
* @author 蔡伟明 <314013107@qq.com>
*/
public function uninstall($name = '')
{
$names = $name != '' ? (array)$name : $this->request->param('ids/a');
// 记录行为
$packet_titles = PacketModel::where('name', 'in', $names)->column('title');
action_log('packet_uninstall', 'admin_packet', 0, UID, implode('、', $packet_titles));
foreach ($names as $name) {
PacketModel::uninstall($name);
}
$this->success('卸载成功');
}
}

View File

@ -0,0 +1,612 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Plugin as PluginModel;
use app\admin\model\HookPlugin as HookPluginModel;
use think\facade\Cache;
use util\Sql;
use think\Db;
use think\facade\Hook;
/**
* 插件管理控制器
* @package app\admin\controller
*/
class Plugin extends Admin
{
/**
* 首页
* @param string $group 分组
* @param string $type 显示类型
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function index($group = 'local', $type = '')
{
// 配置分组信息
$list_group = ['local' => '本地插件'];
$tab_list = [];
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
// 监听tab钩子
Hook::listen('plugin_index_tab_list', $tab_list);
switch ($group) {
case 'local':
// 查询条件
$keyword = $this->request->get('keyword', '');
if (input('?param.status') && input('param.status') != '_all') {
$status = input('param.status');
} else {
$status = '';
}
$PluginModel = new PluginModel;
$result = $PluginModel->getAll($keyword, $status);
if ($result['plugins'] === false) {
$this->error($PluginModel->getError());
}
$type_show = Cache::get('plugin_type_show');
$type_show = $type != '' ? $type : ($type_show == false ? 'block' : $type_show);
Cache::set('plugin_type_show', $type_show);
$type = $type_show == 'block' ? 'list' : 'block';
$this->assign('page_title', '插件管理');
$this->assign('plugins', $result['plugins']);
$this->assign('total', $result['total']);
$this->assign('tab_nav', ['tab_list' => $tab_list, 'curr_tab' => $group]);
$this->assign('type', $type);
return $this->fetch();
break;
case 'online':
break;
}
}
/**
* 安装插件
* @param string $name 插件标识
* @author 蔡伟明 <314013107@qq.com>
*/
public function install($name = '')
{
// 设置最大执行时间和内存大小
ini_set('max_execution_time', '0');
ini_set('memory_limit', '1024M');
$plug_name = trim($name);
if ($plug_name == '') $this->error('插件不存在!');
$plugin_class = get_plugin_class($plug_name);
if (!class_exists($plugin_class)) {
$this->error('插件不存在!');
}
// 实例化插件
$plugin = new $plugin_class;
// 插件预安装
if(!$plugin->install()) {
$this->error('插件预安装失败!原因:'. $plugin->getError());
}
// 添加钩子
if (isset($plugin->hooks) && !empty($plugin->hooks)) {
if (!HookPluginModel::addHooks($plugin->hooks, $name)) {
$this->error('安装插件钩子时出现错误,请重新安装');
}
cache('hook_plugins', null);
}
// 执行安装插件sql文件
$sql_file = realpath(config('plugin_path').$name.'/install.sql');
if (file_exists($sql_file)) {
if (isset($plugin->database_prefix) && $plugin->database_prefix != '') {
$sql_statement = Sql::getSqlFromFile($sql_file, false, [$plugin->database_prefix => config('database.prefix')]);
} else {
$sql_statement = Sql::getSqlFromFile($sql_file);
}
if (!empty($sql_statement)) {
foreach ($sql_statement as $value) {
Db::execute($value);
}
}
}
// 插件配置信息
$plugin_info = $plugin->info;
// 验证插件信息
$result = $this->validate($plugin_info, 'Plugin');
// 验证失败 输出错误信息
if(true !== $result) $this->error($result);
// 并入插件配置值
$plugin_info['config'] = $plugin->getConfigValue();
// 将插件信息写入数据库
if (PluginModel::create($plugin_info)) {
cache('plugin_all', null);
$this->success('插件安装成功');
} else {
$this->error('插件安装失败');
}
}
/**
* 卸载插件
* @param string $name 插件标识
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function uninstall($name = '')
{
$plug_name = trim($name);
if ($plug_name == '') $this->error('插件不存在!');
$class = get_plugin_class($plug_name);
if (!class_exists($class)) {
$this->error('插件不存在!');
}
// 实例化插件
$plugin = new $class;
// 插件预卸
if(!$plugin->uninstall()) {
$this->error('插件预卸载失败!原因:'. $plugin->getError());
}
// 卸载插件自带钩子
if (isset($plugin->hooks) && !empty($plugin->hooks)) {
if (false === HookPluginModel::deleteHooks($plug_name)) {
$this->error('卸载插件钩子时出现错误,请重新卸载');
}
cache('hook_plugins', null);
}
// 执行卸载插件sql文件
$sql_file = realpath(config('plugin_path').$plug_name.'/uninstall.sql');
if (file_exists($sql_file)) {
if (isset($plugin->database_prefix) && $plugin->database_prefix != '') {
$sql_statement = Sql::getSqlFromFile($sql_file, true, [$plugin->database_prefix => config('database.prefix')]);
} else {
$sql_statement = Sql::getSqlFromFile($sql_file, true);
}
if (!empty($sql_statement)) {
Db::execute($sql_statement);
}
}
// 删除插件信息
if (PluginModel::where('name', $plug_name)->delete()) {
cache('plugin_all', null);
$this->success('插件卸载成功');
} else {
$this->error('插件卸载失败');
}
}
/**
* 插件管理
* @param string $name 插件名
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function manage($name = '')
{
cookie('__forward__', $_SERVER['REQUEST_URI']);
// 加载自定义后台页面
if (plugin_action_exists($name, 'Admin', 'index')) {
return plugin_action($name, 'Admin', 'index');
}
// 加载系统的后台页面
$class = get_plugin_class($name);
if (!class_exists($class)) {
$this->error($name.'插件不存在!');
}
// 实例化插件
$plugin = new $class;
// 获取后台字段信息,并分析
if (isset($plugin->admin)) {
$admin = $this->parseAdmin($plugin->admin);
} else {
$admin = $this->parseAdmin();
}
if (!plugin_model_exists($name)) {
$this->error('插件: '.$name.' 缺少模型文件!');
}
// 获取插件模型实例
$PluginModel = get_plugin_model($name);
$order = $this->getOrder();
$map = $this->getMap();
$data_list = $PluginModel->where($map)->order($order)->paginate();
$page = $data_list->render();
// 使用ZBuilder快速创建数据表格
$builder = ZBuilder::make('table')
->setPageTitle($admin['title']) // 设置页面标题
->setPluginName($name)
->setTableName($admin['table_name'])
->setSearch($admin['search_field'], $admin['search_title']) // 设置搜索框
->addOrder($admin['order'])
->addTopButton('back', [
'title' => '返回插件列表',
'icon' => 'fa fa-reply',
'href' => url('index')
])
->addTopButtons($admin['top_buttons']) // 批量添加顶部按钮
->addRightButtons($admin['right_buttons']); // 批量添加右侧按钮
// 自定义顶部按钮
if (!empty($admin['custom_top_buttons'])) {
foreach ($admin['custom_top_buttons'] as $custom) {
$builder->addTopButton('custom', $custom);
}
}
// 自定义右侧按钮
if (!empty($admin['custom_right_buttons'])) {
foreach ($admin['custom_right_buttons'] as $custom) {
$builder->addRightButton('custom', $custom);
}
}
// 表头筛选
if (is_array($admin['filter'])) {
foreach ($admin['filter'] as $column => $params) {
$options = isset($params[0]) ? $params[0] : [];
$default = isset($params[1]) ? $params[1] : [];
$type = isset($params[2]) ? $params[2] : 'checkbox';
$builder->addFilter($column, $options, $default, $type);
}
} else {
$builder->addFilter($admin['filter']);
}
return $builder
->addColumns($admin['columns']) // 批量添加数据列
->setRowList($data_list) // 设置表格数据
->setPages($page) // 设置分页数据
->fetch(); // 渲染模板
}
/**
* 插件新增方法
* @param string $plugin_name 插件名称
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function add($plugin_name = '')
{
// 如果存在自定义的新增方法,则优先执行
if (plugin_action_exists($plugin_name, 'Admin', 'add')) {
$params = $this->request->param();
return plugin_action($plugin_name, 'Admin', 'add', $params);
}
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post();
// 执行插件的验证器(如果存在的话)
if (plugin_validate_exists($plugin_name)) {
$plugin_validate = get_plugin_validate($plugin_name);
if (!$plugin_validate->check($data)) {
// 验证失败 输出错误信息
$this->error($plugin_validate->getError());
}
}
// 实例化模型并添加数据
$PluginModel = get_plugin_model($plugin_name);
if ($PluginModel->data($data)->save()) {
$this->success('新增成功', cookie('__forward__'));
} else {
$this->error('新增失败');
}
}
// 获取插件模型
$class = get_plugin_class($plugin_name);
if (!class_exists($class)) {
$this->error('插件不存在!');
}
// 实例化插件
$plugin = new $class;
if (!isset($plugin->fields)) {
$this->error('插件新增、编辑字段不存在!');
}
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('新增')
->addFormItems($plugin->fields)
->fetch();
}
/**
* 编辑插件方法
* @param string $id 数据id
* @param string $plugin_name 插件名称
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
*/
public function edit($id = '', $plugin_name = '')
{
// 如果存在自定义的编辑方法,则优先执行
if (plugin_action_exists($plugin_name, 'Admin', 'edit')) {
$params = $this->request->param();
return plugin_action($plugin_name, 'Admin', 'edit', $params);
}
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post();
// 执行插件的验证器(如果存在的话)
if (plugin_validate_exists($plugin_name)) {
$plugin_validate = get_plugin_validate($plugin_name);
if (!$plugin_validate->check($data)) {
// 验证失败 输出错误信息
$this->error($plugin_validate->getError());
}
}
// 实例化模型并添加数据
$PluginModel = get_plugin_model($plugin_name);
if (false !== $PluginModel->isUpdate(true)->save($data)) {
$this->success('编辑成功', cookie('__forward__'));
} else {
$this->error('编辑失败');
}
}
// 获取插件类名
$class = get_plugin_class($plugin_name);
if (!class_exists($class)) {
$this->error('插件不存在!');
}
// 实例化插件
$plugin = new $class;
if (!isset($plugin->fields)) {
$this->error('插件新增、编辑字段不存在!');
}
// 获取数据
$PluginModel = get_plugin_model($plugin_name);
$info = $PluginModel->find($id);
if (!$info) {
$this->error('找不到数据!');
}
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('编辑')
->addHidden('id')
->addFormItems($plugin->fields)
->setFormData($info)
->fetch();
}
/**
* 插件参数设置
* @param string $name 插件名称
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @throws \think\exception\PDOException
*/
public function config($name = '')
{
// 更新配置
if ($this->request->isPost()) {
$data = $this->request->post();
$data = json_encode($data);
if (false !== PluginModel::where('name', $name)->update(['config' => $data])) {
$this->success('更新成功', 'index');
} else {
$this->error('更新失败');
}
}
$plugin_class = get_plugin_class($name);
// 实例化插件
$plugin = new $plugin_class;
$trigger = isset($plugin->trigger) ? $plugin->trigger : [];
// 插件配置值
$info = PluginModel::where('name', $name)->field('id,name,config')->find();
$db_config = json_decode($info['config'], true);
// 插件配置项
$config = include config('plugin_path'). $name. '/config.php';
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('插件设置')
->addFormItems($config)
->setFormData($db_config)
->setTrigger($trigger)
->fetch();
}
/**
* 设置状态
* @param string $type 状态类型:enable/disable
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function setStatus($type = '', $record = [])
{
$_t = input('param._t', '');
$ids = $this->request->isPost() ? input('post.ids/a') : input('param.ids');
empty($ids) && $this->error('缺少主键');
$status = $type == 'enable' ? 1 : 0;
if ($_t != '') {
parent::setStatus($type, $record);
} else {
$plugins = PluginModel::where('id', 'in', $ids)->value('name');
if ($plugins) {
HookPluginModel::$type($plugins);
}
if (false !== PluginModel::where('id', 'in', $ids)->setField('status', $status)) {
$this->success('操作成功');
} else {
$this->error('操作失败');
}
}
}
/**
* 禁用插件/禁用插件数据
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function disable($record = [])
{
$this->setStatus('disable');
}
/**
* 启用插件/启用插件数据
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function enable($record = [])
{
$this->setStatus('enable');
}
/**
* 删除插件数据
* @param array $record
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function delete($record = [])
{
$this->setStatus('delete');
}
/**
* 执行插件内部方法
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public function execute()
{
$plugin = input('param._plugin');
$controller = input('param._controller');
$action = input('param._action');
$params = $this->request->except(['_plugin', '_controller', '_action'], 'param');
if (empty($plugin) || empty($controller) || empty($action)) {
$this->error('没有指定插件名称、控制器名称或操作名称');
}
if (!plugin_action_exists($plugin, $controller, $action)) {
$this->error("找不到方法:{$plugin}/{$controller}/{$action}");
}
return plugin_action($plugin, $controller, $action, $params);
}
/**
* 分析后台字段信息
* @param array $data 字段信息
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
private function parseAdmin($data = [])
{
$admin = [
'title' => '数据列表',
'search_title' => '',
'search_field' => [],
'order' => '',
'filter' => '',
'table_name' => '',
'columns' => [],
'right_buttons' => [],
'top_buttons' => [],
'customs' => [],
];
if (empty($data)) {
return $admin;
}
// 处理工具栏按钮链接
if (isset($data['top_buttons']) && !empty($data['top_buttons'])) {
$this->parseButton('top_buttons', $data);
}
// 处理右侧按钮链接
if (isset($data['right_buttons']) && !empty($data['right_buttons'])) {
$this->parseButton('right_buttons', $data);
}
return array_merge($admin, $data);
}
/**
* 解析按钮链接
* @param string $button 按钮名称
* @param array $data 字段信息
* @author 蔡伟明 <314013107@qq.com>
*/
private function parseButton($button, &$data)
{
foreach ($data[$button] as $key => &$value) {
// 处理自定义按钮
if ($key === 'customs') {
if (!empty($value)) {
foreach ($value as &$custom) {
if (isset($custom['href']['url']) && $custom['href']['url'] != '') {
$params = isset($custom['href']['params']) ? $custom['href']['params'] : [];
$custom['href'] = plugin_url($custom['href']['url'], $params);
$data['custom_'.$button][] = $custom;
}
}
}
unset($data[$button][$key]);
}
if (!is_numeric($key) && isset($value['href']['url']) && $value['href']['url'] != '') {
$value['href'] = plugin_url($value['href']['url']);
}
}
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Config as ConfigModel;
use app\admin\model\Module as ModuleModel;
/**
* 系统模块控制器
* @package app\admin\controller
*/
class System extends Admin
{
/**
* 系统设置
* @param string $group 分组
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public function index($group = 'base')
{
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post();
if (isset(config('config_group')[$group])) {
// 查询该分组下所有的配置项名和类型
$items = ConfigModel::where('group', $group)->where('status', 1)->column('name,type');
foreach ($items as $name => $type) {
if (!isset($data[$name])) {
switch ($type) {
// 开关
case 'switch':
$data[$name] = 0;
break;
case 'checkbox':
$data[$name] = '';
break;
}
} else {
// 如果值是数组则转换成字符串,适用于复选框等类型
if (is_array($data[$name])) {
$data[$name] = implode(',', $data[$name]);
}
switch ($type) {
// 开关
case 'switch':
$data[$name] = 1;
break;
// 日期时间
case 'date':
case 'time':
case 'datetime':
$data[$name] = strtotime($data[$name]);
break;
}
}
ConfigModel::where('name', $name)->update(['value' => $data[$name]]);
}
} else {
// 保存模块配置
if (false === ModuleModel::where('name', $group)->update(['config' => json_encode($data)])) {
$this->error('更新失败');
}
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('module_config_'.$group, $data);
}
}
cache('system_config', null);
// 记录行为
action_log('system_config_update', 'admin_config', 0, UID, "分组($group)");
$this->success('更新成功', url('index', ['group' => $group]));
} else {
// 配置分组信息
$list_group = config('config_group');
// 读取模型配置
$modules = ModuleModel::where('config', 'neq', '')
->where('status', 1)
->column('config,title,name', 'name');
foreach ($modules as $name => $module) {
$list_group[$name] = $module['title'];
}
$tab_list = [];
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
if (isset(config('config_group')[$group])) {
// 查询条件
$map['group'] = $group;
$map['status'] = 1;
// 数据列表
$data_list = ConfigModel::where($map)
->order('sort asc,id asc')
->field('group', true)
->select();
$data_list = $data_list->toArray();
foreach ($data_list as &$value) {
// 解析options
if ($value['options'] != '') {
$value['options'] = parse_attr($value['options']);
}
// 默认模块列表
if ($value['name'] == 'home_default_module') {
$value['options'] = array_merge(['index' => '默认'], ModuleModel::getModule());
}
switch ($value['type']) {
// 日期时间
case 'date':
$value['value'] = $value['value'] != '' ? date('Y-m-d', $value['value']) : '';
break;
case 'time':
$value['value'] = $value['value'] != '' ? date('H:i:s', $value['value']) : '';
break;
case 'datetime':
$value['value'] = $value['value'] != '' ? date('Y-m-d H:i:s', $value['value']) : '';
break;
case 'linkages':
$value['token'] = $this->createLinkagesToken($value['table'], $value['option'], $value['key']);
break;
case 'colorpicker':
$value['mode'] = 'rgba';
break;
}
}
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('系统设置')
->setTabNav($tab_list, $group)
->setFormItems($data_list)
->fetch();
} else {
// 模块配置
$module_info = ModuleModel::getInfoFromFile($group);
$config = $module_info['config'];
$trigger = isset($module_info['trigger']) ? $module_info['trigger'] : [];
// 数据库内的模块信息
$db_config = ModuleModel::where('name', $group)->value('config');
$db_config = json_decode($db_config, true);
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('模块设置')
->setTabNav($tab_list, $group)
->addFormItems($config)
->setFormdata($db_config) // 设置表格数据
->setTrigger($trigger) // 设置触发
->fetch();
}
}
}
/**
* 创建快速多级联动Token
* @param string $table 表名
* @param string $option
* @param string $key
* @author 蔡伟明 <314013107@qq.com>
* @return bool|string
*/
private function createLinkagesToken($table = '', $option = '', $key = '')
{
$table_token = substr(sha1($table.'-'.$option.'-'.$key.'-'.session('user_auth.last_login_ip').'-'.UID.'-'.session('user_auth.last_login_time')), 0, 8);
session($table_token, ['table' => $table, 'option' => $option, 'key' => $key]);
return $table_token;
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace app\admin\model;
use think\Model;
use think\facade\Request;
/**
* 统一授权模型
* @package app\admin\model
*/
class Access extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_access';
/**
* 获取用户授权节点
* @param int $uid 用户id
* @param string $group 权限分组可以以点分开模型名称和分组名称如user.group
* @author 蔡伟明 <314013107@qq.com>
* @return array|bool
*/
public function getAuthNode($uid = 0, $group = '')
{
if ($uid == 0 || $group == '') {
$this->error = '缺少参数';
return false;
}
if (strpos($group, '.')) {
list($module, $group) = explode('.', $group);
} else {
$module = Request::module();
}
$map = [
'module' => $module,
'group' => $group,
'uid' => $uid
];
return $this->where($map)->column('nid');
}
/**
* 检查用户的某个节点是否授权
* @param int $uid 用户id
* @param string $group $group 权限分组可以以点分开模型名称和分组名称如user.group
* @param int $node 需要检查的节点id
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
public function checkAuthNode($uid = 0, $group = '', $node = 0)
{
if ($uid == 0 || $group == '' || $node == 0) {
$this->error = '缺少参数';
return false;
}
// 获取该用户的所有授权节点
$nodes = $this->getAuthNode($uid, $group);
if (!$nodes) {
$this->error = '该用户没有授权任何节点';
return false;
}
$nodes = array_flip($nodes);
if (isset($nodes[$node])) {
return true;
} else {
$this->error = '未授权';
return false;
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace app\admin\model;
use think\Model;
/**
* 日志模型
* @package app\admin\model
*/
class Action extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_action';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
}

View File

@ -0,0 +1,87 @@
<?php
namespace app\admin\model;
use think\Model;
/**
* 附件模型
* @package app\admin\model
*/
class Attachment extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_attachment';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 根据附件id获取路径
* @param string|array $id 附件id
* @param int $type 类型0-补全目录1-直接返回数据库记录的地址
* @author 蔡伟明 <314013107@qq.com>
* @return array|bool|mixed|string
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getFilePath($id = '', $type = 0)
{
if (is_array($id)) {
$data_list = $this->where('id', 'in', $id)->select();
$paths = [];
foreach ($data_list as $key => $value) {
if ($value['driver'] == 'local') {
$paths[$value['id']] = ($type == 0 ? PUBLIC_PATH : '').$value['path'];
} else {
$paths[$value['id']] = $value['path'];
}
}
return $paths;
} else {
$data = $this->where('id', $id)->find();
if ($data) {
if ($data['driver'] == 'local') {
return ($type == 0 ? PUBLIC_PATH : '').$data['path'];
} else {
return $data['path'];
}
} else {
return false;
}
}
}
/**
* 根据图片id获取缩略图路径如果缩略图不存在则返回原图路径
* @param string $id 图片id
* @author 蔡伟明 <314013107@qq.com>
* @return array|mixed|string|Model|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getThumbPath($id = '')
{
$result = $this->where('id', $id)->field('path,driver,thumb')->find();
if ($result) {
if ($result['driver'] == 'local') {
return $result['thumb'] != '' ? PUBLIC_PATH.$result['thumb'] : PUBLIC_PATH.$result['path'];
} else {
return $result['thumb'] != '' ? $result['thumb'] : $result['path'];
}
} else {
return $result;
}
}
/**
* 根据附件id获取名称
* @param string $id 附件id
* @return string 名称
*/
public function getFileName($id = '')
{
return $this->where('id', $id)->value('name');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace app\admin\model;
use think\Model;
/**
* 后台配置模型
* @package app\admin\model
*/
class Config extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_config';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 获取配置信息
* @param string $name 配置名
* @return mixed
*/
public function getConfig($name = '')
{
$configs = self::column('value,type', 'name');
$result = [];
foreach ($configs as $config) {
switch ($config['type']) {
case 'array':
$result[$config['name']] = parse_attr($config['value']);
break;
case 'checkbox':
if ($config['value'] != '') {
$result[$config['name']] = explode(',', $config['value']);
} else {
$result[$config['name']] = [];
}
break;
default:
$result[$config['name']] = $config['value'];
break;
}
}
return $name != '' ? $result[$name] : $result;
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace app\admin\model;
use think\Model;
/**
* 钩子模型
* @package app\admin\model
*/
class Hook extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_hook';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 添加钩子
* @param array $hooks 钩子
* @param string $plugin_name
* @author 蔡伟明 <314013107@qq.com>
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function addHooks($hooks = [], $plugin_name = '')
{
if (!empty($hooks) && is_array($hooks)) {
$data = [];
foreach ($hooks as $name => $description) {
if (is_numeric($name)) {
$name = $description;
$description = '';
}
if (self::where('name', $name)->find()) {
continue;
}
$data[] = [
'name' => $name,
'plugin' => $plugin_name,
'description' => $description,
'create_time' => request()->time(),
'update_time' => request()->time(),
];
}
if (!empty($data) && false === self::insertAll($data)) {
return false;
}
}
return true;
}
/**
* 删除钩子
* @param string $plugin_name 钩子名称
* @author 蔡伟明 <314013107@qq.com>
* @return bool
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public static function deleteHooks($plugin_name = '')
{
if (!empty($plugin_name)) {
if (false === self::where('plugin', $plugin_name)->delete()) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace app\admin\model;
use think\Model;
use app\admin\model\Hook as HookModel;
/**
* 钩子-插件模型
* @package app\admin\model
*/
class HookPlugin extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_hook_plugin';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 启用插件钩子
* @param string $plugin 插件名称
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
public static function enable($plugin = '')
{
return self::where('plugin', $plugin)->setField('status', 1);
}
/**
* 禁用插件钩子
* @param string $plugin 插件名称
* @author 蔡伟明 <314013107@qq.com>
* @return int
*/
public static function disable($plugin = '')
{
return self::where('plugin', $plugin)->setField('status', 0);
}
/**
* 添加钩子-插件对照
* @param array $hooks 钩子
* @param string $plugin_name 插件名称
* @author 蔡伟明 <314013107@qq.com>
* @return bool|int|string
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function addHooks($hooks = [], $plugin_name = '')
{
if (!empty($hooks) && is_array($hooks)) {
// 添加钩子
if (!HookModel::addHooks($hooks, $plugin_name)) {
return false;
}
$data = [];
foreach ($hooks as $name => $description) {
if (is_numeric($name)) {
$name = $description;
}
$data[] = [
'hook' => $name,
'plugin' => $plugin_name,
'create_time' => request()->time(),
'update_time' => request()->time(),
];
}
return self::insertAll($data);
}
return false;
}
/**
* 删除钩子
* @param string $plugin_name 钩子名称
* @author 蔡伟明 <314013107@qq.com>
* @return bool
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public static function deleteHooks($plugin_name = '')
{
if (!empty($plugin_name)) {
// 删除钩子
if (!HookModel::deleteHooks($plugin_name)) {
return false;
}
if (false === self::where('plugin', $plugin_name)->delete()) {
return false;
}
}
return true;
}
/**
* 钩子插件排序
* @param string $hook 钩子
* @param string $plugins 插件名
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
public static function sort($hook = '', $plugins = '')
{
if ($hook != '' && $plugins != '') {
$plugins = is_array($plugins) ? $plugins : explode(',', $plugins);
foreach ($plugins as $key => $plugin) {
$map = [
'hook' => $hook,
'plugin' => $plugin
];
self::where($map)->setField('sort', $key + 1);
}
}
return true;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace app\admin\model;
use think\Model;
/**
* 图标模型
* @package app\admin\model
*/
class Icon extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_icon';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 图标列表
* @author 蔡伟明 <314013107@qq.com>
* @return \think\model\relation\HasMany
*/
public function icons()
{
return $this->hasMany('IconList', 'icon_id')->field('title,class,code');
}
/**
* 获取图标css链接
* @author 蔡伟明 <314013107@qq.com>
* @return array|string|\think\Collection
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function getUrls()
{
$list = self::where('status', 1)->select();
if ($list) {
foreach ($list as $key => $item) {
if ($item['icons']) {
$html = '<ul class="js-icon-list items-push-2x text-center">';
foreach ($item['icons'] as $icon) {
$html .= '<li title="'.$icon['title'].'"><i class="'.$icon['class'].'"></i> <code>'.$icon['code'].'</code></li>';
}
$html .= '</ul>';
} else {
$html = '<p class="text-center text-muted">暂无图标</p>';
}
$list[$key]['html'] = $html;
}
}
return $list;
}
}

View File

@ -0,0 +1,24 @@
<?php
// +----------------------------------------------------------------------
// | 海豚PHP框架 [ DolphinPHP ]
// +----------------------------------------------------------------------
// | 版权所有 2016~2017 河源市卓锐科技有限公司 [ http://www.zrthink.com ]
// +----------------------------------------------------------------------
// | 官方网站: http://dolphinphp.com
// +----------------------------------------------------------------------
// | 开源协议 ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
namespace app\admin\model;
use think\Model;
/**
* 图标列表模型
* @package app\admin\model
*/
class IconList extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_icon_list';
}

View File

@ -0,0 +1,37 @@
<?php
namespace app\admin\model;
use think\Model;
/**
* 日志记录模型
* @package app\admin\model
*/
class Log extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_log';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 获取所有日志
* @param array $map 条件
* @param string $order 排序
* @author 蔡伟明 <314013107@qq.com>
* @return \think\Paginator
* @throws \think\exception\DbException
*/
public static function getAll($map = [], $order = '')
{
$data_list = self::view('admin_log', true)
->view('admin_action', 'title,module', 'admin_action.id=admin_log.action_id', 'left')
->view('admin_user', 'username', 'admin_user.id=admin_log.user_id', 'left')
->view('admin_module', ['title' => 'module_title'], 'admin_module.name=admin_action.module')
->where($map)
->order($order)
->paginate();
return $data_list;
}
}

View File

@ -0,0 +1,322 @@
<?php
// +----------------------------------------------------------------------
// | 海豚PHP框架 [ DolphinPHP ]
// +----------------------------------------------------------------------
// | 版权所有 2016~2017 河源市卓锐科技有限公司 [ http://www.zrthink.com ]
// +----------------------------------------------------------------------
// | 官方网站: http://dolphinphp.com
// +----------------------------------------------------------------------
// | 开源协议 ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
namespace app\admin\model;
use app\user\model\Role as RoleModel;
use think\Model;
use think\Exception;
use util\Tree;
/**
* 节点模型
* @package app\admin\model
*/
class Menu extends Model
{
// 设置当前模型对应的完整数据表名称
protected $table = '__ADMIN_MENU__';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
// 将节点url转为小写
public function setUrlValueAttr($value)
{
return strtolower(trim($value));
}
/**
* 递归修改所属模型
* @param int $id 父级节点id
* @param string $module 模型名称
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
public static function changeModule($id = 0, $module = '')
{
if ($id > 0) {
$ids = self::where('pid', $id)->column('id');
if ($ids) {
foreach ($ids as $id) {
self::where('id', $id)->setField('module', $module);
self::changeModule($id, $module);
}
}
}
return true;
}
/**
* 获取树形节点
* @param int $id 需要隐藏的节点id
* @param string $default 默认第一个节点项默认为“顶级节点”如果为false则不显示也可传入其他名称
* @param string $module 模型名
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public static function getMenuTree($id = 0, $default = '', $module = '')
{
$result[0] = '顶级节点';
$where = [
['status', 'egt', 0]
];
if ($module != '') {
$where[] = ['module', '=', $module];
}
// 排除指定节点及其子节点
if ($id !== 0) {
$hide_ids = array_merge([$id], self::getChildsId($id));
$where[] = ['id', 'not in', $hide_ids];
}
// 获取节点
$menus = Tree::toList(self::where($where)->order('pid,id')->column('id,pid,title'));
foreach ($menus as $menu) {
$result[$menu['id']] = $menu['title_display'];
}
// 设置默认节点项标题
if ($default != '') {
$result[0] = $default;
}
// 隐藏默认节点项
if ($default === false) {
unset($result[0]);
}
return $result;
}
/**
* 获取顶部节点
* @param string $max 最多返回多少个
* @param string $cache_tag 缓存标签
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
public static function getTopMenu($max = '', $cache_tag = '')
{
$cache_tag .= '_role_'.session('user_auth.role');
$menus = cache($cache_tag);
if (!$menus) {
// 非开发模式,只显示可以显示的菜单
if (config('develop_mode') == 0) {
$map['online_hide'] = 0;
}
$map['status'] = 1;
$map['pid'] = 0;
$list_menu = self::where($map)->order('sort,id')->column('id,pid,module,title,url_value,url_type,url_target,icon,params');
$i = 0;
$menus = [];
foreach ($list_menu as $key => &$menu) {
if ($max != '' && $i >= $max) {
break;
}
// 没有访问权限的节点不显示
if (!RoleModel::checkAuth($menu['id'])) {
continue;
}
if ($menu['url_value'] != '' && ($menu['url_type'] == 'module_admin' || $menu['url_type'] == 'module_home')) {
$url = explode('/', $menu['url_value']);
$menu['controller'] = $url[1];
$menu['action'] = $url[2];
$menu['url_value'] = $menu['url_type'] == 'module_admin' ? admin_url($menu['url_value'], $menu['params']) : home_url($menu['url_value'], $menu['params']);
}
$menus[$key] = $menu;
$i++;
}
// 非开发模式,缓存菜单
if (config('develop_mode') == 0) {
cache($cache_tag, $menus);
}
}
return $menus;
}
/**
* 获取侧栏节点
* @param string $id 模块id
* @param string $module 模块名
* @param string $controller 控制器名
* @author 蔡伟明 <314013107@qq.com>
* @return array|mixed
*/
public static function getSidebarMenu($id = '', $module = '', $controller = '')
{
$module = $module == '' ? request()->module() : $module;
$controller = $controller == '' ? request()->controller() : $controller;
$cache_tag = strtolower('_sidebar_menus_' . $module . '_' . $controller).'_role_'.session('user_auth.role');
$menus = cache($cache_tag);
if (!$menus) {
// 获取当前节点地址
$location = self::getLocation($id);
// 当前顶级节点id
$top_id = $location[0]['id'];
// 获取顶级节点下的所有节点
$map = [
'status' => 1
];
// 非开发模式,只显示可以显示的菜单
if (config('develop_mode') == 0) {
$map['online_hide'] = 0;
}
$menus = self::where($map)->order('sort,id')->column('id,pid,module,title,url_value,url_type,url_target,icon,params');
// 解析模块链接
foreach ($menus as $key => &$menu) {
// 没有访问权限的节点不显示
if (!RoleModel::checkAuth($menu['id'])) {
unset($menus[$key]);
continue;
}
if ($menu['url_value'] != '' && ($menu['url_type'] == 'module_admin' || $menu['url_type'] == 'module_home')) {
$menu['url_value'] = $menu['url_type'] == 'module_admin' ? admin_url($menu['url_value'], $menu['params']) : home_url($menu['url_value'], $menu['params']);
}
}
$menus = Tree::toLayer($menus, $top_id, 2);
// 非开发模式,缓存菜单
if (config('develop_mode') == 0) {
cache($cache_tag, $menus);
}
}
return $menus;
}
/**
* 获取指定节点ID的位置
* @param string $id 节点id如果没有指定则取当前节点id
* @param bool $del_last_url 是否删除最后一个节点的url地址
* @param bool $check 检查节点是否存在,不存在则抛出错误
* @author 蔡伟明 <314013107@qq.com>
* @return array
* @throws \think\Exception
*/
public static function getLocation($id = '', $del_last_url = false, $check = true)
{
$model = request()->module();
$controller = request()->controller();
$action = request()->action();
if ($id != '') {
$cache_name = 'location_menu_'.$id;
} else {
$cache_name = 'location_'.$model.'_'.$controller.'_'.$action;
}
$location = cache($cache_name);
if (!$location) {
$map = [
['pid', '<>', 0],
['url_value', '=', strtolower($model.'/'.trim(preg_replace("/[A-Z]/", "_\\0", $controller), "_").'/'.$action)]
];
// 当前操作对应的节点ID
$curr_id = $id == '' ? self::where($map)->value('id') : $id;
// 获取节点ID是所有父级节点
$location = Tree::getParents(self::column('id,pid,title,url_value,params'), $curr_id);
if ($check && empty($location)) {
throw new Exception('获取不到当前节点地址,可能未添加节点', 9001);
}
// 剔除最后一个节点url
if ($del_last_url) {
$location[count($location) - 1]['url_value'] = '';
}
// 非开发模式,缓存菜单
if (config('develop_mode') == 0) {
cache($cache_name, $location);
}
}
return $location;
}
/**
* 根据分组获取节点
* @param string $group 分组名称
* @param bool|string $fields 要返回的字段
* @param array $map 查找条件
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
public static function getMenusByGroup($group = '', $fields = true, $map = [])
{
$map['module'] = $group;
return self::where($map)->order('sort,id')->column($fields, 'id');
}
/**
* 获取节点分组
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
public static function getGroup()
{
$map['status'] = 1;
$map['pid'] = 0;
$menus = self::where($map)->order('id,sort')->column('module,title');
return $menus;
}
/**
* 获取所有子节点id
* @param int $pid 父级id
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
public static function getChildsId($pid = 0)
{
$ids = self::where('pid', $pid)->column('id');
foreach ($ids as $value) {
$ids = array_merge($ids, self::getChildsId($value));
}
return $ids;
}
/**
* 获取所有父节点id
* @param int $id 节点id
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
public static function getParentsId($id = 0)
{
$pid = self::where('id', $id)->value('pid');
$pids = [];
if ($pid != 0) {
$pids[] = $pid;
$pids = array_merge($pids, self::getParentsId($pid));
}
return $pids;
}
/**
* 根据节点id获取上下级的所有id
* @param int $id 节点id
* @author 蔡伟明 <314013107@qq.com>
* @return array
*/
public static function getLinkIds($id = 0)
{
$childs = self::getChildsId($id);
$parents = self::getParentsId($id);
return array_merge((array)(int)$id, $childs, $parents);
}
}

View File

@ -0,0 +1,343 @@
<?php
// +----------------------------------------------------------------------
// | 海豚PHP框架 [ DolphinPHP ]
// +----------------------------------------------------------------------
// | 版权所有 2016~2017 河源市卓锐科技有限公司 [ http://www.zrthink.com ]
// +----------------------------------------------------------------------
// | 官方网站: http://dolphinphp.com
// +----------------------------------------------------------------------
// | 开源协议 ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
namespace app\admin\model;
use think\Model;
use think\facade\Env;
/**
* 模块模型
* @package app\admin\model
*/
class Module extends Model
{
// 设置当前模型对应的完整数据表名称
protected $table = '__ADMIN_MODULE__';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 获取所有模块的名称和标题
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public static function getModule()
{
$modules = cache('modules');
if (!$modules) {
$modules = self::where('status', '>=', 0)->order('id')->column('name,title');
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('modules', $modules);
}
}
return $modules;
}
/**
* 获取所有模块信息
* @param string $keyword 查找关键词
* @param string $status 查找状态
* @author 蔡伟明 <314013107@qq.com>
* @return array|bool
*/
public function getAll($keyword = '', $status = '')
{
$result = cache('module_all');
if (!$result) {
$dirs = array_map('basename', glob(Env::get('app_path').'*', GLOB_ONLYDIR));
if ($dirs === false || !file_exists(Env::get('app_path'))) {
$this->error = '模块目录不可读或者不存在';
return false;
}
// 不读取模块信息的目录
$except_module = config('system.except_module');
// 正常模块(包括已安装和未安装)
$dirs = array_diff($dirs, $except_module);
// 读取数据库模块表
$modules = $this->order('sort asc,id desc')->column(true, 'name');
// 读取未安装的模块
foreach ($dirs as $module) {
if (!isset($modules[$module])) {
// 获取模块信息
$info = self::getInfoFromFile($module);
$modules[$module]['name'] = $module;
// 模块模块信息缺失
if (empty($info)) {
$modules[$module]['status'] = '-2';
continue;
}
// 模块模块信息不完整
if (!$this->checkInfo($info)) {
$modules[$module]['status'] = '-3';
continue;
}
// 模块未安装
$modules[$module] = $info;
$modules[$module]['status'] = '-1'; // 模块未安装
}
}
// 数量统计
$total = [
'all' => count($modules), // 所有模块数量
'-2' => 0, // 已损坏数量
'-1' => 0, // 未安装数量
'0' => 0, // 已禁用数量
'1' => 0, // 已启用数量
];
// 过滤查询结果和统计数量
foreach ($modules as $key => $value) {
// 统计数量
if (in_array($value['status'], ['-2', '-3'])) {
// 已损坏数量
$total['-2']++;
} else {
$total[(string)$value['status']]++;
}
// 过滤查询
if ($status != '') {
if ($status == '-2') {
// 过滤掉非已损坏的模块
if (!in_array($value['status'], ['-2', '-3'])) {
unset($modules[$key]);
continue;
}
} else if ($value['status'] != $status) {
unset($modules[$key]);
continue;
}
}
if ($keyword != '') {
if (stristr($value['name'], $keyword) === false && (!isset($value['title']) || stristr($value['title'], $keyword) === false) && (!isset($value['author']) || stristr($value['author'], $keyword) === false)) {
unset($modules[$key]);
continue;
}
}
}
// 处理状态及模块按钮
foreach ($modules as &$module) {
// 系统核心模块
if (isset($module['system_module']) && $module['system_module'] == '1') {
$module['actions'] = '<button class="btn btn-sm btn-noborder btn-danger" type="button" disabled>不可操作</button>';
$module['status_class'] = 'text-success';
$module['status_info'] = '<i class="fa fa-check"></i> 已启用';
$module['bg_color'] = 'success';
continue;
}
switch ($module['status']) {
case '-3': // 模块信息不完整
$module['title'] = '模块信息不完整';
$module['bg_color'] = 'danger';
$module['status_class'] = 'text-danger';
$module['status_info'] = '<i class="fa fa-times"></i> 已损坏';
$module['actions'] = '<button class="btn btn-sm btn-noborder btn-danger" type="button" disabled>不可操作</button>';
break;
case '-2': // 模块信息缺失
$module['title'] = '模块信息缺失';
$module['bg_color'] = 'danger';
$module['status_class'] = 'text-danger';
$module['status_info'] = '<i class="fa fa-times"></i> 已损坏';
$module['actions'] = '<button class="btn btn-sm btn-noborder btn-danger" type="button" disabled>不可操作</button>';
break;
case '-1': // 未安装
$module['bg_color'] = 'info';
$module['actions'] = '<a class="btn btn-sm btn-noborder btn-success" href="'.url('install', ['name' => $module['name']]).'">安装</a>';
$module['status_class'] = 'text-info';
$module['status_info'] = '<i class="fa fa-fw fa-th-large"></i> 未安装';
break;
case '0': // 禁用
$module['bg_color'] = 'warning';
$module['actions'] = '<a class="btn btn-sm btn-noborder btn-success ajax-get confirm" href="'.url('enable', ['ids' => $module['id']]).'">启用</a> ';
$module['actions'] .= '<a class="btn btn-sm btn-noborder btn-primary" href="'.url('export', ['name' => $module['name']]).'">导出</a> ';
$module['actions'] .= '<a class="btn btn-sm btn-noborder btn-danger" href="'.url('uninstall', ['name' => $module['name']]).'">卸载</a> ';
$module['status_class'] = 'text-warning';
$module['status_info'] = '<i class="fa fa-ban"></i> 已禁用';
break;
case '1': // 启用
$module['bg_color'] = 'success';
$module['actions'] = '<a class="btn btn-sm btn-noborder btn-info ajax-get confirm" href="'.url('update', ['name' => $module['name']]).'">更新</a> ';
$module['actions'] .= '<a class="btn btn-sm btn-noborder btn-warning ajax-get confirm" href="'.url('disable', ['ids' => $module['id']]).'">禁用</a> ';
$module['actions'] .= '<a class="btn btn-sm btn-noborder btn-primary" href="'.url('export', ['name' => $module['name']]).'">导出</a> ';
$module['actions'] .= '<a class="btn btn-sm btn-noborder btn-danger" href="'.url('uninstall', ['name' => $module['name']]).'">卸载</a> ';
$module['status_class'] = 'text-success';
$module['status_info'] = '<i class="fa fa-check"></i> 已启用';
break;
default: // 未知
$module['title'] = '未知';
break;
}
}
$result = ['total' => $total, 'modules' => $modules];
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('module_all', $result);
}
}
return $result;
}
/**
* 从文件获取模块信息
* @param string $name 模块名称
* @author 蔡伟明 <314013107@qq.com>
* @return array|mixed
*/
public static function getInfoFromFile($name = '')
{
$info = [];
if ($name != '') {
// 从配置文件获取
if (is_file(Env::get('app_path'). $name . '/info.php')) {
$info = include Env::get('app_path'). $name . '/info.php';
}
}
return $info;
}
/**
* 检查模块模块信息是否完整
* @param string $info 模块模块信息
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
private function checkInfo($info = '')
{
$default_item = ['name','title','author','version'];
foreach ($default_item as $item) {
if (!isset($info[$item]) || $info[$item] == '') {
return false;
}
}
return true;
}
/**
* 获取模型配置信息
* @param string $name 模型名
* @param string $item 指定返回的模块配置项
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
*/
public static function getConfig($name = '', $item = '')
{
$name = $name == '' ? request()->module() : $name;
$config = cache('module_config_'.$name);
if (!$config) {
$config = self::where('name', $name)->value('config');
if (!$config) {
return [];
}
$config = json_decode($config, true);
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('module_config_'.$name, $config);
}
}
if (!empty($item)) {
$items = explode(',', $item);
if (count($items) == 1) {
return isset($config[$item]) ? $config[$item] : '';
}
$result = [];
foreach ($items as $item) {
$result[$item] = isset($config[$item]) ? $config[$item] : '';
}
return $result;
}
return $config;
}
/**
* 获取模型配置信息
* @param string $name 插件名.配置名
* @param string $value 配置值
* @author caiweiming <314013107@qq.com>
* @return bool
*/
public static function setConfig($name = '', $value = '')
{
$item = '';
if (strpos($name, '.')) {
list($name, $item) = explode('.', $name);
}
// 获取缓存
$config = cache('module_config_'.$name);
if (!$config) {
$config = self::where('name', $name)->value('config');
if (!$config) {
return false;
}
$config = json_decode($config, true);
}
if ($item === '') {
// 批量更新
if (!is_array($value) || empty($value)) {
// 值的格式错误,必须为数组
return false;
}
$config = array_merge($config, $value);
} else {
// 更新单个值
$config[$item] = $value;
}
if (false === self::where('name', $name)->setField('config', json_encode($config))) {
return false;
}
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('module_config_'.$name, $config);
}
return true;
}
/**
* 从文件获取模块菜单
* @param string $name 模块名称
* @author 蔡伟明 <314013107@qq.com>
* @return array|mixed
*/
public static function getMenusFromFile($name = '')
{
$menus = [];
if ($name != '' && is_file(Env::get('app_path'). $name . '/menus.php')) {
// 从菜单文件获取
$menus = include Env::get('app_path'). $name . '/menus.php';
}
return $menus;
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace app\admin\model;
use think\Model;
use think\Db;
use util\Sql;
/**
* 数据包模型
* @package app\admin\model
*/
class Packet extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_packet';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
/**
* 获取所有数据包列表
* @author 蔡伟明 <314013107@qq.com>
* @return array|bool
*/
public function getAll()
{
// 获取数据包目录下的所有插件目录
$dirs = array_map('basename', glob(config('packet_path').'*', GLOB_ONLYDIR));
if ($dirs === false || !file_exists(config('packet_path'))) {
$this->error = '插件目录不可读或者不存在';
return false;
}
// 读取数据库数据包表
$packets = $this->column(true, 'name');
// 读取未安装的数据包
foreach ($dirs as $packet) {
if (!isset($packets[$packet])) {
$info = $this->getInfoFromFile($packet);
$info['status'] = 0;
$packets[] = $info;
}
}
return $packets;
}
/**
* 从文件获取数据包信息
* @param string $name 数据包名称
* @author 蔡伟明 <314013107@qq.com>
* @return array|mixed
*/
public static function getInfoFromFile($name = '')
{
$info = [];
if ($name != '') {
// 从配置文件获取
if (is_file(config('packet_path'). $name . '/info.php')) {
$info = include config('packet_path'). $name . '/info.php';
}
}
return $info;
}
/**
* 安装数据包
* @param string $name 数据包名
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
public static function install($name = '')
{
$info = self::getInfoFromFile($name);
foreach ($info['tables'] as $table) {
$sql_file = realpath(config('packet_path').$name."/{$table}.sql");
if (file_exists($sql_file)) {
if (isset($info['database_prefix']) && $info['database_prefix'] != '') {
$sql_statement = Sql::getSqlFromFile($sql_file, false, [$info['database_prefix'] => config('database.prefix')]);
} else {
$sql_statement = Sql::getSqlFromFile($sql_file);
}
if (!empty($sql_statement)) {
foreach ($sql_statement as $value) {
Db::execute($value);
}
}
} else {
return "{$table}.sql】文件不存在";
}
}
return true;
}
/**
* 卸载数据包
* @param string $name 数据包名
* @author 蔡伟明 <314013107@qq.com>
* @return bool
* @throws \think\Exception
* @throws \think\exception\PDOException
*/
public static function uninstall($name = '')
{
$info = self::getInfoFromFile($name);
foreach ($info['tables'] as $table) {
$sql = "DROP TABLE IF EXISTS `". config('database.prefix') ."{$table}`;";
Db::execute($sql);
self::where('name', $name)->delete();
}
return true;
}
}

View File

@ -0,0 +1,299 @@
<?php
namespace app\admin\model;
use think\Model;
/**
* 插件模型
* @package app\admin\model
*/
class Plugin extends Model
{
// 设置当前模型对应的完整数据表名称
protected $name = 'admin_plugin';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
// 写入时处理config
public function setConfigAttr($value)
{
return !empty($value) ? json_encode($value) : '';
}
/**
* 获取所有插件信息
* @param string $keyword 查找关键词
* @param string $status 查找状态
* @author 蔡伟明 <314013107@qq.com>
* @return array|bool
*/
public function getAll($keyword = '', $status = '')
{
$result = cache('plugin_all');
if (!$result) {
// 获取插件目录下的所有插件目录
$dirs = array_map('basename', glob(config('plugin_path').'*', GLOB_ONLYDIR));
if ($dirs === false || !file_exists(config('plugin_path'))) {
$this->error = '插件目录不可读或者不存在';
return false;
}
// 读取数据库插件表
$plugins = $this->order('sort asc,id desc')->column(true, 'name');
// 读取未安装的插件
foreach ($dirs as $plugin) {
if (!isset($plugins[$plugin])) {
$plugins[$plugin]['name'] = $plugin;
// 获取插件类名
$class = get_plugin_class($plugin);
// 插件类不存在则跳过实例化
if (!class_exists($class)) {
// 插件的入口文件不存在!
$plugins[$plugin]['status'] = '-2';
continue;
}
// 实例化插件
$obj = new $class;
// 插件插件信息缺失
if (!isset($obj->info) || empty($obj->info)) {
// 插件信息缺失!
$plugins[$plugin]['status'] = '-3';
continue;
}
// 插件插件信息不完整
if (!$this->checkInfo($obj->info)) {
$plugins[$plugin]['status'] = '-4';
continue;
}
// 插件未安装
$plugins[$plugin] = $obj->info;
$plugins[$plugin]['status'] = '-1';
}
}
// 数量统计
$total = [
'all' => count($plugins), // 所有插件数量
'-2' => 0, // 错误插件数量
'-1' => 0, // 未安装数量
'0' => 0, // 未启用数量
'1' => 0, // 已启用数量
];
// 过滤查询结果和统计数量
foreach ($plugins as $key => $value) {
// 统计数量
if (in_array($value['status'], ['-2', '-3', '-4'])) {
// 已损坏数量
$total['-2']++;
} else {
$total[(string)$value['status']]++;
}
// 过滤查询
if ($status != '') {
if ($status == '-2') {
// 过滤掉非已损坏的插件
if (!in_array($value['status'], ['-2', '-3', '-4'])) {
unset($plugins[$key]);
continue;
}
} else if ($value['status'] != $status) {
unset($plugins[$key]);
continue;
}
}
if ($keyword != '') {
if (stristr($value['name'], $keyword) === false && (!isset($value['title']) || stristr($value['title'], $keyword) === false) && (!isset($value['author']) || stristr($value['author'], $keyword) === false)) {
unset($plugins[$key]);
continue;
}
}
}
// 处理状态及插件按钮
foreach ($plugins as &$plugin) {
switch ($plugin['status']) {
case '-4': // 插件信息不完整
$plugin['title'] = '插件信息不完整';
$plugin['bg_color'] = 'danger';
$plugin['status_class'] = 'text-danger';
$plugin['status_info'] = '<i class="fa fa-times"></i> 已损坏';
$plugin['actions'] = '<button class="btn btn-sm btn-noborder btn-danger" type="button" disabled>不可操作</button>';
break;
case '-3': // 插件信息缺失
$plugin['title'] = '插件信息缺失';
$plugin['bg_color'] = 'danger';
$plugin['status_class'] = 'text-danger';
$plugin['status_info'] = '<i class="fa fa-times"></i> 已损坏';
$plugin['actions'] = '<button class="btn btn-sm btn-noborder btn-danger" type="button" disabled>不可操作</button>';
break;
case '-2': // 入口文件不存在
$plugin['title'] = '入口文件不存在';
$plugin['bg_color'] = 'danger';
$plugin['status_class'] = 'text-danger';
$plugin['status_info'] = '<i class="fa fa-times"></i> 已损坏';
$plugin['actions'] = '<button class="btn btn-sm btn-noborder btn-danger" type="button" disabled>不可操作</button>';
break;
case '-1': // 未安装
$plugin['bg_color'] = 'info';
$plugin['actions'] = '<a class="btn btn-sm btn-noborder btn-success ajax-get confirm" href="'.url('install', ['name' => $plugin['name']]).'">安装</a>';
$plugin['status_class'] = 'text-info';
$plugin['status_info'] = '<i class="fa fa-fw fa-th-large"></i> 未安装';
break;
case '0': // 禁用
$plugin['bg_color'] = 'warning';
$plugin['actions'] = '<a class="btn btn-sm btn-noborder btn-success ajax-get confirm" href="'.url('enable', ['ids' => $plugin['id']]).'">启用</a> ';
$plugin['actions'] .= '<a class="btn btn-sm btn-noborder btn-danger ajax-get confirm" data-tips="如果包括数据库,将同时删除数据库!" href="'.url('uninstall', ['name' => $plugin['name']]).'">卸载</a> ';
if (isset($plugin['config']) && $plugin['config'] != '') {
$plugin['actions'] .= '<a class="btn btn-sm btn-noborder btn-info" href="'.url('config', ['name' => $plugin['name']]).'">设置</a> ';
}
if ($plugin['admin'] != '0') {
$plugin['actions'] .= '<a class="btn btn-sm btn-noborder btn-primary" href="'.url('manage', ['name' => $plugin['name']]).'">管理</a> ';
}
$plugin['status_class'] = 'text-warning';
$plugin['status_info'] = '<i class="fa fa-ban"></i> 已禁用';
break;
case '1': // 启用
$plugin['bg_color'] = 'success';
$plugin['actions'] = '<a class="btn btn-sm btn-noborder btn-warning ajax-get confirm" href="'.url('disable', ['ids' => $plugin['id']]).'">禁用</a> ';
$plugin['actions'] .= '<a class="btn btn-sm btn-noborder btn-danger ajax-get confirm" data-tips="如果包括数据库,将同时删除数据库!" href="'.url('uninstall', ['name' => $plugin['name']]).'">卸载</a> ';
if (isset($plugin['config']) && $plugin['config'] != '') {
$plugin['actions'] .= '<a class="btn btn-sm btn-noborder btn-info" href="'.url('config', ['name' => $plugin['name']]).'">设置</a> ';
}
if ($plugin['admin'] != '0') {
$plugin['actions'] .= '<a class="btn btn-sm btn-noborder btn-primary" href="'.url('manage', ['name' => $plugin['name']]).'">管理</a> ';
}
$plugin['status_class'] = 'text-success';
$plugin['status_info'] = '<i class="fa fa-check"></i> 已启用';
break;
default: // 未知
$plugin['title'] = '未知';
break;
}
}
$result = ['total' => $total, 'plugins' => $plugins];
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('plugin_all', $result);
}
}
return $result;
}
/**
* 检查插件插件信息是否完整
* @param string $info 插件插件信息
* @author 蔡伟明 <314013107@qq.com>
* @return bool
*/
private function checkInfo($info = '')
{
$default_item = ['name','title','author','version'];
foreach ($default_item as $item) {
if (!isset($info[$item]) || $info[$item] == '') {
return false;
}
}
return true;
}
/**
* 获取插件配置
* @param string $name 插件名称
* @param string $item 指定返回的插件配置项
* @author 蔡伟明 <314013107@qq.com>
* @return array|mixed
*/
public function getConfig($name = '', $item = '')
{
$config = cache('plugin_config_'.$name);
if (!$config) {
$config = $this->where('name', $name)->value('config');
if (!$config) {
return [];
}
$config = json_decode($config, true);
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('plugin_config_'.$name, $config);
}
}
if (!empty($item)) {
$items = explode(',', $item);
if (count($items) == 1) {
return isset($config[$item]) ? $config[$item] : '';
}
$result = [];
foreach ($items as $item) {
$result[$item] = isset($config[$item]) ? $config[$item] : '';
}
return $result;
}
return $config;
}
/**
* 设置插件配置
* @param string $name 插件名.配置名
* @param string $value 配置值
* @author caiweiming <314013107@qq.com>
* @return bool
*/
public function setConfig($name = '', $value = '')
{
$item = '';
if (strpos($name, '.')) {
list($name, $item) = explode('.', $name);
}
// 获取缓存
$config = cache('plugin_config_'.$name);
if (!$config) {
$config = $this->where('name', $name)->value('config');
if (!$config) {
return false;
}
$config = json_decode($config, true);
}
if ($item === '') {
// 批量更新
if (!is_array($value) || empty($value)) {
// 值的格式错误,必须为数组
return false;
}
$config = array_merge($config, $value);
} else {
// 更新单个值
$config[$item] = $value;
}
if (false === $this->where('name', $name)->setField('config', json_encode($config))) {
return false;
}
// 非开发模式,缓存数据
if (config('develop_mode') == 0) {
cache('plugin_config_'.$name, $config);
}
return true;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace app\admin\validate;
use think\Validate;
/**
* 行为验证器
* @package app\admin\validate
* @author 蔡伟明 <314013107@qq.com>
*/
class Action extends Validate
{
//定义验证规则
protected $rule = [
'module|所属模块' => 'require',
'name|行为标识' => 'require|regex:^[a-zA-Z]\w{0,39}$|unique:admin_action,name^module',
'title|行为名称' => 'require|length:1,80',
'remark|行为描述' => 'require|length:1,128'
];
//定义验证提示
protected $message = [
'name.regex' => '行为标识由字母和下划线组成',
];
}

View File

@ -0,0 +1,31 @@
<?php
namespace app\admin\validate;
use think\Validate;
/**
* 配置验证器
* @package app\admin\validate
* @author 蔡伟明 <314013107@qq.com>
*/
class Config extends Validate
{
// 定义验证规则
protected $rule = [
'group|配置分组' => 'require',
'type|配置类型' => 'require',
'name|配置名称' => 'require|regex:^[a-zA-Z]\w{0,39}$|unique:admin_config',
'title|配置标题' => 'require',
];
// 定义验证提示
protected $message = [
'name.regex' => '配置名称由字母和下划线组成',
];
// 定义场景,供快捷编辑时验证
protected $scene = [
'name' => ['name'],
'title' => ['title'],
];
}

View File

@ -0,0 +1,22 @@
<?php
namespace app\admin\validate;
use think\Validate;
/**
* 钩子验证器
* @package app\admin\validate
* @author 蔡伟明 <314013107@qq.com>
*/
class Hook extends Validate
{
//定义验证规则
protected $rule = [
'name|钩子名称' => 'require|regex:^[a-zA-Z]\w{0,39}$|unique:admin_hook'
];