├── .gitignore ├── README.md ├── composer.json ├── config └── ltool.php ├── doc ├── CHANGELOG.md ├── ControllerTraits.md ├── auths-redis-token.md ├── excels.md ├── images │ └── Respository原理图.png ├── reppositories.md ├── search.md ├── sign.md ├── sqlToLog.md └── 发表在其他网站上的文件.md ├── src ├── Auths │ └── Cache │ │ ├── CacheGuard.php │ │ ├── Token.php │ │ └── TokenHandle.php ├── Console │ └── Commands │ │ └── Backup │ │ ├── Restore.php │ │ └── Run.php ├── Contracts │ ├── Debug │ │ └── MessageBagErrors.php │ ├── Search │ │ └── SearchInterface.php │ └── Sign │ │ └── SignDriverInterface.php ├── Excels │ └── ExcelAbstract.php ├── Exceptions │ ├── DeleteResourceFailedException.php │ ├── Exception.php │ ├── ResourceException.php │ ├── SearchException.php │ ├── SignException.php │ ├── StoreResourceFailedException.php │ ├── TokenException.php │ └── UpdateResourceFailedException.php ├── Facades │ └── Sign.php ├── Libraries │ ├── RedisUnique.php │ └── VueMessage.php ├── Listeners │ └── QueryExecutedListener.php ├── Middlewares │ ├── CacheTokenMiddleware.php │ └── VerifySignMiddleware.php ├── Providers │ └── LaravelServiceProvider.php ├── Queue │ └── Worker.php ├── Repositories │ └── RepositoryAbstract.php ├── Searchs │ └── SearchAbstract.php ├── Services │ └── ServiceAbstract.php ├── Sign │ ├── Drivers │ │ └── Md5.php │ ├── SignAbstract.php │ └── SignManager.php └── Traits │ └── CodeTrait.php └── test └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .idea 4 | /examples/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel 开发辅助工具 2 | 3 | ### 配置 4 | 5 | #### 添加服务提供商 6 | 7 | 将下面这行添加至 config/app.php 文件 providers 数组中: 8 | 9 | ```php 10 | 'providers' => [ 11 | ... 12 | App\Plugins\Auth\Providers\LaravelServiceProvider::class 13 | ] 14 | ``` 15 | 16 | ### 插件及文档 17 | 18 | - [redisToken认证](./doc/auths-redis-token.md) 19 | - [Repository 模式](./doc/reppositories.md) 20 | - [表单搜索辅助插件](./doc/search.md) 21 | - [Excels导出辅助插件](./doc/excels.md) 22 | - [Sign 加签](./doc/sign.md) 23 | - [Sql 写进日志-事件](./doc/sqlToLog.md) 24 | - [Controller Traits](./doc/ControllerTraits.md) 25 | 26 | ### 更新日志 27 | 28 | - [CHANGELOG](./doc/CHANGELOG.md) 29 | 30 | ### 最后想说说 31 | 第一次把自己东西写个文件放在git上,欢迎`pr`和`star`。会一直更新和优化下去。很多地方是之前赶项目时写的。现在有空会一个个优化。 -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "luffyzhao/laravel-tools", 3 | "require": { 4 | "php": ">=7.2", 5 | "illuminate/auth": ">=5.0", 6 | "illuminate/contracts": ">=5.0", 7 | "illuminate/http": ">=5.0", 8 | "illuminate/support": ">=5.0", 9 | "illuminate/redis": ">=5.0", 10 | "illuminate/console": ">=5.0", 11 | "illuminate/database": ">=5.0", 12 | "illuminate/routing": ">=5.0", 13 | "ext-openssl": "*", 14 | "ext-json": "*", 15 | "ext-mbstring": "*" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "LTools\\": "src/" 20 | } 21 | }, 22 | "authors": [ 23 | { 24 | "name": "luffyzhao", 25 | "email": "luffyzhao@vip.126.com" 26 | } 27 | ], 28 | "extra": { 29 | "laravel": { 30 | "providers": [ 31 | "LTools\\Providers\\LaravelServiceProvider" 32 | ] 33 | } 34 | }, 35 | "repositories": { 36 | "packagist": { 37 | "type": "composer", 38 | "url": "https://mirrors.aliyun.com/composer/" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /config/ltool.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'time_out' => env('LTOOL_SIGN_TIME_OUT', 60), 6 | 7 | 'sign_key' => env('LTOOL_SIGN_SIGN_KEY', 'DFECXFE53ER432Ef'), 8 | 9 | 'rsa_private_key' => env('LTOOL_SIGN_RSA_PRIVATE', __DIR__.'/../pems/rsa_private_key.pem'), 10 | 11 | 'rsa_public_key' => env('LTOOL_SIGN_RSA_PUBLIC', __DIR__.'/../pems/rsa_public_key.pem'), 12 | ], 13 | 14 | // token-cache验证 15 | 'token-cache' => [ 16 | // 缓存key前缀 17 | 'key_prefix' => 'token:cache:', 18 | // 有效时间 19 | 'exp' => 10, 20 | // 刷新有效期 21 | 'ttl' => 86400 22 | ] 23 | ]; -------------------------------------------------------------------------------- /doc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ### v1.9.5 4 | - 增加join可嵌套使用 5 | - repo增加方法newQuery -------------------------------------------------------------------------------- /doc/ControllerTraits.md: -------------------------------------------------------------------------------- 1 | # Controller Traits 2 | 3 | ### 介绍 4 | controller公用方法 5 | 6 | ### 使用方法 7 | 8 | 在 App\Http\Controllers\Controller 类中 use \luffyzhao\laravelTools\Traits\ResponseTrait -------------------------------------------------------------------------------- /doc/auths-redis-token.md: -------------------------------------------------------------------------------- 1 | # redis-token 认证 2 | 3 | ### 插件介绍 4 | 5 | 把token保存在redis。同时支持登录过期时间设置,登录之前,登录之后事件处理。 6 | 7 | 8 | ### 配置 Auth guard 9 | 10 | 在 config/auth.php 文件中,你需要将 guards/driver 更新为 redis-token: 11 | 12 | ```php 13 | 'defaults' => [ 14 | 'guard' => 'api', 15 | 'passwords' => 'users', 16 | ], 17 | 18 | ... 19 | 20 | 'guards' => [ 21 | 'api' => [ 22 | 'driver' => 'redis-token', 23 | 'provider' => 'users', 24 | ], 25 | ], 26 | ``` 27 | 28 | ### 更改 Model 29 | 30 | 如果需要使用 redis-token 作为用户认证,我们需要对我们的 User 模型进行一点小小的改变,实现一个接口,变更后的 User 模型如下: 31 | 32 | ```php 33 | getKey(); 45 | } 46 | } 47 | 48 | ``` 49 | 50 | ### 登录 51 | 52 | ```php 53 | /** 54 | * 登录 55 | * @method store 56 | * @param StoreRequest $request 57 | * 58 | * @return \Illuminate\Http\JsonResponse 59 | * 60 | * @author luffyzhao@vip.126.com 61 | */ 62 | public function store(StoreRequest $request) 63 | { 64 | $token = auth('api')->attempt( 65 | $request->only(['phone', 'password']) 66 | ); 67 | 68 | if (!$token) { 69 | return $this->respondWithError('用户不存在,或者密码不正确!'); 70 | } 71 | 72 | return $this->respondWithToken((string) $token); 73 | } 74 | ``` 75 | 76 | ### 退出 77 | 78 | ```php 79 | /** 80 | * 退出登录. 81 | * 82 | * @method logout 83 | * 84 | * @return \Illuminate\Http\JsonResponse 85 | * 86 | * @author luffyzhao@vip.126.com 87 | */ 88 | public function logout() 89 | { 90 | auth('api')->logout(); 91 | 92 | return $this->respondWithSuccess([], '退出成功'); 93 | } 94 | ``` 95 | 96 | ### 事件 97 | 98 | | 事件名 | 事件对象 | 99 | | --- | --- | 100 | | 登录之前 | luffyzhao\laravelTools\Events\Auths\BeforeLogin::class| 101 | | 登录之后 | luffyzhao\laravelTools\Events\Auths\AfterLogin::class| 102 | | 销毁或退出之前 | luffyzhao\laravelTools\Events\Auths\BeforeLogout::class| 103 | | 销毁或退出之后 | luffyzhao\laravelTools\Events\Auths\AfterLogout::class| 104 | 105 | 106 | ### 方法 107 | 108 | | 方法名 | 说明 | 109 | | --- | --- | 110 | | authenticate() | 认证 | 111 | | check() | 确定当前用户是否已被认证 | 112 | | guest() | 确定当前用户是否为访客。 | 113 | | id() | 获取当前用户的主键 | 114 | | setUser() | 设置当前用户 | 115 | | getProvider() | 获取卫士使用的用户提供程序。 | 116 | | setProvider() | 设置卫士使用的用户提供程序。 | 117 | | user() | 获取当前登录用户 | 118 | | attempt() | 尝试登录 | 119 | | login() | 登录 | 120 | | destroy() | 销毁某个登录用户 | 121 | | logout() | 退出当前登录 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /doc/excels.md: -------------------------------------------------------------------------------- 1 | # Excels导出辅助插件 2 | 3 | ### 插件介绍 4 | 5 | Excels导出辅助插件 6 | 7 | ### 创建 Excels 8 | ``` 9 | php artisan make:excel User 10 | ``` 11 | 12 | 上面命令会创建一个 App\Excels\Modules\UserExcel::class 的类 13 | 14 | ### 编写Search 15 | 16 | ```php 17 | id, 55 | $this->phone, 56 | $this->name 57 | ]; 58 | } 59 | 60 | 61 | /** 62 | * 搜索参数 63 | * @return {[type]} [description] 64 | */ 65 | protected function getAttributes() 66 | { 67 | return new ExcelSearch(request()->only([ 68 | 'phone', 69 | 'name', 70 | ])); 71 | } 72 | 73 | 74 | } 75 | ``` 76 | 77 | > 更多用法 请参考 [maatwebsite/excel](https://github.com/Maatwebsite/Laravel-Excel) -------------------------------------------------------------------------------- /doc/images/Respository原理图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luffyzhao/laravel-tools/690e9b3f2ae4163469f4e981b46701693b86a715/doc/images/Respository原理图.png -------------------------------------------------------------------------------- /doc/reppositories.md: -------------------------------------------------------------------------------- 1 | ### Repository 模式 2 | 3 | ### 插件介绍 4 | 5 | 首先需要声明的是设计模式和使用的框架以及语言是无关的,关键是要理解设计模式背后的原则,这样才能不管你用的是什么技术,都能够在实践中实现相应的设计模式。 6 | 7 | 按照最初提出者的介绍,Repository 是衔接数据映射层和领域层之间的一个纽带,作用相当于一个在内存中的域对象集合。客户端对象把查询的一些实体进行组合,并把它 们提交给 Repository。对象能够从 Repository 中移除或者添加,就好比这些对象在一个 Collection 对象上进行数据操作,同时映射层的代码会对应的从数据库中取出相应的数据。 8 | 9 | 从概念上讲,Repository 是把一个数据存储区的数据给封装成对象的集合并提供了对这些集合的操作。 10 | 11 | Repository 模式将业务逻辑和数据访问分离开,两者之间通过 Repository 接口进行通信,通俗点说,可以把 Repository 看做仓库管理员,我们要从仓库取东西(业务逻辑),只需要找管理员要就是了(Repository),不需要自己去找(数据访问),具体流程如下图所示: 12 | 13 | ![Respository原理](./images/Respository原理图.png) 14 | 15 | 16 | ### 创建 Repository 17 | 18 | #### 不使用缓存 19 | ``` 20 | php artisan make:repo User 21 | ``` 22 | 23 | #### 使用缓存 24 | ```php 25 | php artisan make:repo User --cache 26 | ``` 27 | 28 | 29 | > 创建 UserRepository 时会询问是否创建Model ,如果Model以存在,需要把 App\Repositories\Modules\User\Provider::class 的Model替换成当前使用的Model 30 | 31 | ### 配置Providers 32 | 33 | 将下面这行添加至 App\Providers\AppServiceProvider::class 文件 register 方法中: 34 | 35 | ```php 36 | public function register() 37 | { 38 | $this->app->register(\App\Repositories\Modules\User\Provider::class); 39 | } 40 | ``` 41 | 42 | ### 使用 43 | 44 | ```php 45 | repo = $repo; 60 | } 61 | 62 | public function index(Request $request){ 63 | return $this->respondWithSuccess($this->repo->get(['*'])); 64 | } 65 | } 66 | ``` 67 | 68 | > 配合 [Search](./search.md) 更灵活 69 | 70 | ```php 71 | public function index(Request $request){ 72 | return $this->respondWithSuccess( 73 | $this->repo->getwhere( 74 | new IndexSearch($request->olny(['name'])) , 75 | ['*'] 76 | ) 77 | ); 78 | } 79 | ``` 80 | 81 | ### 方法 82 | | 方法名 | 参数 | 83 | | --- | --- | 84 | | getModel | 无| 85 | | newModel | 无| 86 | | getTable | 无| 87 | | find | id:主键, $columns 要获取的列| 88 | | findMany | id:主键集, $columns 要获取的列| 89 | | findWhere | attributes:where条件, $columns 要获取的列| 90 | | findValue | attributes:where条件, $columns 要获取的列| 91 | | get | $columns 要获取的列| 92 | | getWhere | attributes:where条件, $columns 要获取的列| 93 | | chunkById | $columns 要获取的列, $count 每次获取多少条,$callback 回调处理,$column 不知道 怎么用文字表达, $alias 不知道 怎么用文字表达 | 94 | | firstOrCreate | $attributes:where条件,$values 附加参数| 95 | | updateOrCreate | $attributes:where条件,$values 附加参数| 96 | | paginate | $attributes:where条件,$perPage 每页显示N条,$columns 要获取的列, $pageName 页码key, $page 当前页码| 97 | | simplePaginate | $attributes:where条件,$perPage 每页显示N条,$columns 要获取的列, $pageName 页码key, $page 当前页码| 98 | | limit | $attributes:where条件,$perPage 每页显示N条,$columns 要获取的列| 99 | | create| $attributes: create数据| 100 | | update | $model: Model, $values 要更改的数据,array $attributes where条件 | 101 | | updateWhere | array $values 要更改的数据 $attributes where条件 | 102 | | delete | $model Model | 103 | | deleteWhere | $attributes where条件 | 104 | | with | $with 渴求式加载 | 105 | | make | $with 渴求式加载 | 106 | | scope | $scope 查询作用域 | 107 | | join | $relations 要关联的模型 | 108 | 109 | -------------------------------------------------------------------------------- /doc/search.md: -------------------------------------------------------------------------------- 1 | # 表单搜索辅助插件 2 | 3 | ### 插件介绍 4 | 5 | 把表单提交的一些参数传换成 `where` 语句. 6 | 7 | ### 创建 Search 8 | 生成一个UserController::index控制器使用的搜索辅助类 9 | ``` 10 | php artisan make:search User\IndexSearch 11 | ``` 12 | 13 | 上面命令会创建一个 App\Searchs\Modules\User\IndexSearch::class 的类 14 | 15 | > 创建Search时,建议根据 Controller\ActionSearch 的格式创建。 16 | 17 | ### 编写Search 18 | 19 | ```php 20 | '=', 30 | 'name' => 'like', 31 | 'date' => 'between' 32 | ]; 33 | 34 | public function getNameAttribute($value) 35 | { 36 | return $value . '%'; 37 | } 38 | 39 | public function getDateAttribute($value){ 40 | return function ($query){ 41 | $query->where('date', '>', '2018-05-05')->where('status', 1); 42 | }; 43 | } 44 | } 45 | ``` 46 | 47 | ### 使用Search 48 | 49 | ```php 50 | repo = $repo; 66 | } 67 | 68 | public function index(Request $request){ 69 | return $this->respondWithSuccess( 70 | $this->repo->getWhere( 71 | new IndexSearch( 72 | $request->only(['phone', 'name', 'date']) 73 | ), 74 | ['*'] 75 | ) 76 | ); 77 | } 78 | } 79 | ``` 80 | 81 | ### 生成的sql 82 | 83 | 请求参数: 84 | ``` 85 | phone=18565215214&name=成龙&date=2018-08-21 86 | ``` 87 | 88 | 生成的sql 89 | 90 | ```sql 91 | WHERE (phone = 18565215214) AND (name like '成龙%') AND (date > '2018-05-05' AND status = 1) 92 | ``` 93 | 94 | 95 | -------------------------------------------------------------------------------- /doc/sign.md: -------------------------------------------------------------------------------- 1 | # Sign 加签 2 | 3 | ### 插件介绍 4 | 5 | 请求参数加签验证 6 | 7 | ### 配置 Sign 8 | 如果你使用的是md5加签方式请在config/app.php文件中,添加 sign_key 配置。如果你使用的是Rsa加签方式请在config/app.php文件中,添加app.sign_rsa_private_key和app.sign_rsa_public_key配置 9 | 10 | ### 配置中间件 11 | 在app/Http/Kernel.php文件中,您需要把 'sign' => \luffyzhao\laravelTools\Middleware\VerifySign::class, 添加到$routeMiddleware属性中 12 | 13 | ### 使用 14 | 15 | ```php 16 | 'sign:api'], 20 | function($route){ 21 | Route::get('xxx', 'xxx'); 22 | } 23 | ); 24 | ``` 25 | 26 | 27 | ##### 加签方式 28 | 29 | `rsa` 和 `md5` 30 | 31 | ##### 参数排序 32 | 33 | * 准备参数 34 | * 添加 `timestamp` 字段 35 | * 然后按照字段名的 ASCII 码从小到大排序(字典序) 36 | * 生成 `url` 参数串 37 | * 拼接 key 然后 md5 或者 rsa 38 | 39 | 40 | 如下所示: 41 | 42 | ``` 43 | { 44 | "name": "4sd65f4asd5f4as5df", 45 | "aimncm": "54854185", 46 | "df4": ["dfadsf"], 47 | "dfsd3": { 48 | "a": { 49 | "gfdfsg": "56fdg", 50 | "afdfsg": "56fdg" 51 | } 52 | } 53 | } 54 | ``` 55 | 排序后: 56 | ``` 57 | { 58 | "aimncm": "54854185", 59 | "df4": ["dfadsf"], 60 | "dfsd3": { 61 | "a": { 62 | "afdfsg": "56fdg", 63 | "gfdfsg": "56fdg" 64 | } 65 | }, 66 | "name": "4sd65f4asd5f4as5df", 67 | "timestamp": "2018-05-29 17:25:34" 68 | } 69 | ``` 70 | 生成url参数串: 71 | 72 | > aimncm=54854185&df4[0]=dfadsf&dfsd3[a][afdfsg]=56fdg&dfsd3[a][gfdfsg]=56fdg&name=4sd65f4asd5f4as5df×tamp=2018-05-29 17:25:34 73 | 74 | 拼接 key : 75 | 76 | > aimncm=54854185&df4[0]=dfadsf&dfsd3[a][afdfsg]=56fdg&dfsd3[a][gfdfsg]=56fdg&name=4sd65f4asd5f4as5df×tamp=2018-05-29 17:25:34base64:Z9I7IMHdO+T9qD3pS492GWNxNkzCxinuI+ih4xC4dWY= 77 | 78 | md5加密 79 | 80 | > ddab78e7edfe56594e2776d892589a9c 81 | 82 | -------------------------------------------------------------------------------- /doc/sqlToLog.md: -------------------------------------------------------------------------------- 1 | # Sql 写进日志-事件 2 | 3 | ### 介绍 4 | 把sql语句记录到日志里 5 | 6 | ### 使用 7 | 在 laravel 自带的 EventServiceProvider 类里 listen 添加 8 | ``` 9 | 'Illuminate\Database\Events\QueryExecuted' => [ 10 | 'luffyzhao\laravelTools\Listeners\QueryListeners' 11 | ] 12 | ``` 13 | 14 | ### 生成事件 15 | 16 | ``` 17 | php artisan event:generate 18 | ``` 19 | -------------------------------------------------------------------------------- /doc/发表在其他网站上的文件.md: -------------------------------------------------------------------------------- 1 | # laravel 开发辅助工具 2 | 3 | ## 安装 4 | 5 | ```php 6 | composer require luffyzhao/laravel-tools 7 | ``` 8 | 9 | ## 配置 10 | 11 | ### 添加服务提供商 12 | 13 | 将下面这行添加至 config/app.php 文件 providers 数组中: 14 | 15 | ```php 16 | 'providers' => [ 17 | ... 18 | App\Plugins\Auth\Providers\LaravelServiceProvider::class 19 | ] 20 | ``` 21 | 22 | ### 插件及文档 23 | 24 | - [redisToken认证](https://github.com/luffyzhao/luffy-laravel-tools/tree/master/doc/auths-redis-token.md) 25 | - [Repository 模式](https://github.com/luffyzhao/luffy-laravel-tools/tree/master/doc/reppositories.md) 26 | - [表单搜索辅助插件](https://github.com/luffyzhao/luffy-laravel-tools/tree/master/doc/search.md) 27 | - [Excels导出辅助插件](https://github.com/luffyzhao/luffy-laravel-tools/tree/master/doc/excels.md) 28 | - [Sign 加签](https://github.com/luffyzhao/luffy-laravel-tools/tree/master/doc/sign.md) 29 | - [Sql 写进日志-事件](https://github.com/luffyzhao/luffy-laravel-tools/tree/master/doc/sqlToLog.md) 30 | - [Controller Traits](https://github.com/luffyzhao/luffy-laravel-tools/tree/master/doc/ControllerTraits.md) 31 | 32 | 33 | ## Repository 模式 34 | 35 | ### 插件介绍 36 | 37 | 首先需要声明的是设计模式和使用的框架以及语言是无关的,关键是要理解设计模式背后的原则,这样才能不管你用的是什么技术,都能够在实践中实现相应的设计模式。 38 | 39 | 按照最初提出者的介绍,Repository 是衔接数据映射层和领域层之间的一个纽带,作用相当于一个在内存中的域对象集合。客户端对象把查询的一些实体进行组合,并把它 们提交给 Repository。对象能够从 Repository 中移除或者添加,就好比这些对象在一个 Collection 对象上进行数据操作,同时映射层的代码会对应的从数据库中取出相应的数据。 40 | 41 | 从概念上讲,Repository 是把一个数据存储区的数据给封装成对象的集合并提供了对这些集合的操作。 42 | 43 | Repository 模式将业务逻辑和数据访问分离开,两者之间通过 Repository 接口进行通信,通俗点说,可以把 Repository 看做仓库管理员,我们要从仓库取东西(业务逻辑),只需要找管理员要就是了(Repository),不需要自己去找(数据访问),具体流程如下图所示: 44 | 45 | 46 | ### 创建 Repository 47 | 48 | #### 不使用缓存 49 | ``` 50 | php artisan make:repo User 51 | ``` 52 | 53 | #### 使用缓存 54 | ```php 55 | php artisan make:repo User --cache 56 | ``` 57 | 58 | 59 | > 创建 UserRepository 时会询问是否创建Model ,如果Model以存在,需要把 App\Repositories\Modules\User\Provider::class 的Model替换成当前使用的Model 60 | 61 | ### 配置Providers 62 | 63 | 将下面这行添加至 App\Providers\AppServiceProvider::class 文件 register 方法中: 64 | 65 | ```php 66 | public function register() 67 | { 68 | $this->app->register(\App\Repositories\Modules\User\Provider::class); 69 | } 70 | ``` 71 | 72 | ### 使用 73 | 74 | ```php 75 | repo = $repo; 90 | } 91 | 92 | public function index(Request $request){ 93 | return $this->respondWithSuccess($this->repo->get(['*'])); 94 | } 95 | } 96 | ``` 97 | 98 | > 配合 [Search](./search.md) 更灵活 99 | 100 | ```php 101 | public function index(Request $request){ 102 | return $this->respondWithSuccess( 103 | $this->repo->getwhere( 104 | new IndexSearch($request->olny(['name'])) , 105 | ['*'] 106 | ) 107 | ); 108 | } 109 | ``` 110 | 111 | ### 方法 112 | > 参考 [Repository 方法](https://github.com/luffyzhao/luffy-laravel-tools/blob/master/doc/reppositories.md#方法) 113 | 114 | 115 | 116 | ## 表单搜索辅助插件 117 | 118 | ### 插件介绍 119 | 120 | 把表单提交的一些参数传换成 `where` 语句. 121 | 122 | ### 创建 Search 123 | 生成一个UserController::index控制器使用的搜索辅助类 124 | ``` 125 | php artisan make:search User\IndexSearch 126 | ``` 127 | 128 | 上面命令会创建一个 App\Searchs\Modules\User\IndexSearch::class 的类 129 | 130 | > 创建Search时,建议根据 Controller\ActionSearch 的格式创建。 131 | 132 | ### 编写Search 133 | 134 | ```php 135 | '=', 145 | 'name' => 'like', 146 | 'date' => 'between' 147 | ]; 148 | 149 | public function getNameAttribute($value) 150 | { 151 | return $value . '%'; 152 | } 153 | 154 | public function getDateAttribute($value){ 155 | return function ($query){ 156 | $query->where('date', '>', '2018-05-05')->where('status', 1); 157 | }; 158 | } 159 | } 160 | ``` 161 | 162 | ### 使用Search 163 | 164 | ```php 165 | repo = $repo; 181 | } 182 | 183 | public function index(Request $request){ 184 | return $this->respondWithSuccess( 185 | $this->repo->getWhere( 186 | new IndexSearch( 187 | $request->only(['phone', 'name', 'date']) 188 | ), 189 | ['*'] 190 | ) 191 | ); 192 | } 193 | } 194 | ``` 195 | 196 | ### 生成的sql 197 | 198 | 请求参数: 199 | ``` 200 | phone=18565215214&name=成龙&date=2018-08-21 201 | ``` 202 | 203 | 生成的sql 204 | 205 | ```sql 206 | WHERE (phone = 18565215214) AND (name like '成龙%') AND (date > '2018-05-05' AND status = 1) 207 | ``` 208 | 209 | 210 | 211 | ## Excels导出辅助插件 212 | 213 | ### 插件介绍 214 | 215 | Excels导出辅助插件 216 | 217 | ### 创建 Excels 218 | ``` 219 | php artisan make:excel User 220 | ``` 221 | 222 | 上面命令会创建一个 App\Excels\Modules\UserExcel::class 的类 223 | 224 | ### 编写Search 225 | 226 | ```php 227 | id, 265 | $this->phone, 266 | $this->name 267 | ]; 268 | } 269 | 270 | 271 | /** 272 | * 搜索参数 273 | * @return {[type]} [description] 274 | */ 275 | protected function getAttributes() 276 | { 277 | return new ExcelSearch(request()->only([ 278 | 'phone', 279 | 'name', 280 | ])); 281 | } 282 | 283 | 284 | } 285 | ``` 286 | 287 | > 更多用法 请参考 [maatwebsite/excel](https://github.com/Maatwebsite/Laravel-Excel) 288 | 289 | 290 | 291 | ## Sql 写进日志-事件 292 | 293 | ### 介绍 294 | 把sql语句记录到日志里 295 | 296 | ### 使用 297 | 在 laravel 自带的 EventServiceProvider 类里 listen 添加 298 | ``` 299 | 'Illuminate\Database\Events' => [ 300 | 'luffyzhao\laravelTools\Listeners\QueryListeners' 301 | ] 302 | ``` 303 | 304 | ### 生成事件 305 | 306 | ``` 307 | php artisan event:generate 308 | ``` 309 | 310 | 311 | 312 | 313 | ## Controller Traits 314 | 315 | ### 介绍 316 | controller公用方法 317 | 318 | ### 使用方法 319 | 320 | 在 App\Http\Controllers\Controller 类中 use \luffyzhao\laravelTools\Traits\ResponseTrait 321 | 322 | 323 | 324 | 325 | ## Sign 加签 326 | 327 | ### 插件介绍 328 | 329 | 请求参数加签验证 330 | 331 | ### 配置 Sign 332 | 如果你使用的是md5加签方式请在config/app.php文件中,添加 sign_key 配置。如果你使用的是Rsa加签方式请在config/app.php文件中,添加app.sign_rsa_private_key和app.sign_rsa_public_key配置 333 | 334 | ### 配置中间件 335 | 在app/Http/Kernel.php文件中,您需要把 'sign' => \luffyzhao\laravelTools\Middleware\VerifySign::class, 添加到$routeMiddleware属性中 336 | 337 | ### 使用 338 | 339 | ```php 340 | 'sign:api'], 344 | function($route){ 345 | Route::get('xxx', 'xxx'); 346 | } 347 | ); 348 | ``` 349 | 350 | 351 | ##### 加签方式 352 | 353 | `rsa` 和 `md5` 354 | 355 | ##### 参数排序 356 | 357 | * 准备参数 358 | * 添加 `timestamp` 字段 359 | * 然后按照字段名的 ASCII 码从小到大排序(字典序) 360 | * 生成 `url` 参数串 361 | * 拼接 key 然后 md5 或者 rsa 362 | 363 | 364 | 如下所示: 365 | 366 | ``` 367 | { 368 | "name": "4sd65f4asd5f4as5df", 369 | "aimncm": "54854185", 370 | "df4": ["dfadsf"], 371 | "dfsd3": { 372 | "a": { 373 | "gfdfsg": "56fdg", 374 | "afdfsg": "56fdg" 375 | } 376 | } 377 | } 378 | ``` 379 | 排序后: 380 | ``` 381 | { 382 | "aimncm": "54854185", 383 | "df4": ["dfadsf"], 384 | "dfsd3": { 385 | "a": { 386 | "afdfsg": "56fdg", 387 | "gfdfsg": "56fdg" 388 | } 389 | }, 390 | "name": "4sd65f4asd5f4as5df", 391 | "timestamp": "2018-05-29 17:25:34" 392 | } 393 | ``` 394 | 生成url参数串: 395 | 396 | > aimncm=54854185&df4[0]=dfadsf&dfsd3[a][afdfsg]=56fdg&dfsd3[a][gfdfsg]=56fdg&name=4sd65f4asd5f4as5df×tamp=2018-05-29 17:25:34 397 | 398 | 拼接 key : 399 | 400 | > aimncm=54854185&df4[0]=dfadsf&dfsd3[a][afdfsg]=56fdg&dfsd3[a][gfdfsg]=56fdg&name=4sd65f4asd5f4as5df×tamp=2018-05-29 17:25:34base64:Z9I7IMHdO+T9qD3pS492GWNxNkzCxinuI+ih4xC4dWY= 401 | 402 | md5加密 403 | 404 | > ddab78e7edfe56594e2776d892589a9c 405 | 406 | 407 | 408 | # redis-token 认证 409 | 410 | ### 插件介绍 411 | 412 | 把token保存在redis。同时支持登录过期时间设置,登录之前,登录之后事件处理。 413 | 414 | 415 | ### 配置 Auth guard 416 | 417 | 在 config/auth.php 文件中,你需要将 guards/driver 更新为 redis-token: 418 | 419 | ```php 420 | 'defaults' => [ 421 | 'guard' => 'api', 422 | 'passwords' => 'users', 423 | ], 424 | 425 | ... 426 | 427 | 'guards' => [ 428 | 'api' => [ 429 | 'driver' => 'redis-token', 430 | 'provider' => 'users', 431 | ], 432 | ], 433 | ``` 434 | 435 | ### 更改 Model 436 | 437 | 如果需要使用 redis-token 作为用户认证,我们需要对我们的 User 模型进行一点小小的改变,实现一个接口,变更后的 User 模型如下: 438 | 439 | ```php 440 | getKey(); 452 | } 453 | } 454 | 455 | ``` 456 | 457 | ### 登录 458 | 459 | ```php 460 | /** 461 | * 登录 462 | * @method store 463 | * @param StoreRequest $request 464 | * 465 | * @return \Illuminate\Http\JsonResponse 466 | * 467 | * @author luffyzhao@vip.126.com 468 | */ 469 | public function store(StoreRequest $request) 470 | { 471 | $token = auth('api')->attempt( 472 | $request->only(['phone', 'password']) 473 | ); 474 | 475 | if (!$token) { 476 | return $this->respondWithError('用户不存在,或者密码不正确!'); 477 | } 478 | 479 | return $this->respondWithToken((string) $token); 480 | } 481 | ``` 482 | 483 | ### 退出 484 | 485 | ```php 486 | /** 487 | * 退出登录. 488 | * 489 | * @method logout 490 | * 491 | * @return \Illuminate\Http\JsonResponse 492 | * 493 | * @author luffyzhao@vip.126.com 494 | */ 495 | public function logout() 496 | { 497 | auth('api')->logout(); 498 | 499 | return $this->respondWithSuccess([], '退出成功'); 500 | } 501 | ``` 502 | 503 | ### 事件 504 | - [方法](https://github.com/luffyzhao/luffy-laravel-tools/blob/master/doc/auths-redis-token.md#事件) 505 | 506 | ### 方法 507 | - [方法](https://github.com/luffyzhao/luffy-laravel-tools/blob/master/doc/auths-redis-token.md#%E6%96%B9%E6%B3%95) -------------------------------------------------------------------------------- /src/Auths/Cache/CacheGuard.php: -------------------------------------------------------------------------------- 1 | provider = $provider; 35 | $this->handle = $handle; 36 | } 37 | 38 | /** 39 | * Get the currently authenticated user. 40 | * 41 | * @return \Illuminate\Contracts\Auth\Authenticatable|null 42 | */ 43 | public function user() 44 | { 45 | if ($this->user !== null) { 46 | return $this->user; 47 | } 48 | 49 | try{ 50 | if ($token = $this->handle->getToken()) { 51 | $this->user = $this->provider->retrieveById($token->getId()); 52 | if(get_class($this->user()) !== $token->getClass()){ 53 | $this->user = null; 54 | } 55 | } 56 | }catch (TokenException $exception){ 57 | 58 | } 59 | 60 | 61 | return $this->user; 62 | } 63 | 64 | /** 65 | * @param array $credentials 66 | * @param bool $login 67 | * 68 | * @return bool|string 69 | */ 70 | public function attempt(array $credentials = [], $login = true) 71 | { 72 | $user = $this->provider->retrieveByCredentials($credentials); 73 | if ($this->hasValidCredentials($user, $credentials)) { 74 | return $login ? $this->login($user) : true; 75 | } 76 | 77 | return false; 78 | } 79 | 80 | /** 81 | * logout 82 | * @author luffyzhao@vip.126.com 83 | * @return bool 84 | * @throws \LTools\Exceptions\TokenException 85 | */ 86 | public function logout(): bool 87 | { 88 | return $this->handle->delete(); 89 | } 90 | 91 | 92 | /** 93 | * refresh 94 | * @author luffyzhao@vip.126.com 95 | * @return mixed 96 | * @throws \LTools\Exceptions\TokenException 97 | */ 98 | public function refresh(){ 99 | return $this->handle->refresh(); 100 | } 101 | 102 | /** 103 | * 104 | * @param mixed $user 105 | * @param array $credentials 106 | * 107 | * @return bool 108 | */ 109 | protected function hasValidCredentials($user, $credentials) 110 | { 111 | return $user !== null 112 | && $this->provider->validateCredentials( 113 | $user, 114 | $credentials 115 | ); 116 | } 117 | 118 | /** 119 | * Validate a user's credentials. 120 | * 121 | * @param array $credentials 122 | * 123 | * @return bool 124 | */ 125 | public function validate(array $credentials = []) 126 | { 127 | return (bool)$this->attempt($credentials, false); 128 | } 129 | 130 | /** 131 | * @param Authenticatable $user 132 | * 133 | * @return bool|string 134 | */ 135 | public function login(Authenticatable $user) 136 | { 137 | $token = $this->handle->generate($user); 138 | $this->setUser($user); 139 | return $token; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Auths/Cache/Token.php: -------------------------------------------------------------------------------- 1 | id = $user->getAuthIdentifier(); 45 | 46 | $this->code = $this->getRedisString(); 47 | 48 | $this->time = time(); 49 | 50 | $this->class = get_class($user); 51 | } 52 | 53 | /** 54 | * generateTokenString 55 | * @author luffyzhao@vip.126.com 56 | * @return string 57 | */ 58 | private function getRedisString(): string 59 | { 60 | return Str::random(16); 61 | } 62 | 63 | /** 64 | * __toString 65 | * @author luffyzhao@vip.126.com 66 | * @return string 67 | */ 68 | public function __toString() : string 69 | { 70 | return Crypt::encrypt($this); 71 | } 72 | 73 | /** 74 | * @return mixed 75 | */ 76 | public function getId() 77 | { 78 | return $this->id; 79 | } 80 | 81 | /** 82 | * @return string 83 | */ 84 | public function getCode(): string 85 | { 86 | return $this->code; 87 | } 88 | 89 | /** 90 | * @return int 91 | */ 92 | public function getTime(): int 93 | { 94 | return $this->time; 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function getClass(): ?string 101 | { 102 | return $this->class; 103 | } 104 | 105 | /** 106 | * __clone 107 | * @author luffyzhao@vip.126.com 108 | */ 109 | private function __clone(){} 110 | } 111 | -------------------------------------------------------------------------------- /src/Auths/Cache/TokenHandle.php: -------------------------------------------------------------------------------- 1 | 86400, 43 | 'exp' => 3600, 44 | 'key_prefix' => 'token:cache:', 45 | ]; 46 | 47 | 48 | public function __construct(Request $request, array $config = []) 49 | { 50 | $this->request = $request; 51 | 52 | $this->config = array_merge($this->config, $config); 53 | } 54 | 55 | 56 | /** 57 | * check 58 | * @author luffyzhao@vip.126.com 59 | */ 60 | public function check(): bool 61 | { 62 | $token = $this->parse(); 63 | 64 | if ($token !== null) { 65 | $tokenArr = Crypt::decrypt($token); 66 | if ($tokenArr instanceof Token && $this->validateInvalidToken($tokenArr)) { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | /** 74 | * getToken 75 | * @return Token 76 | * @throws TokenException 77 | * @author luffyzhao@vip.126.com 78 | */ 79 | public function getToken(): Token 80 | { 81 | $token = $this->parse(); 82 | 83 | if ($token !== null) { 84 | $tokenArr = Crypt::decrypt($token); 85 | if ($tokenArr instanceof Token && $this->validateInvalidToken($tokenArr)) { 86 | return $tokenArr; 87 | } 88 | } 89 | throw new TokenException('invalid'); 90 | } 91 | 92 | /** 93 | * 设置token 94 | * @method setIdentifier 95 | * 96 | * @param Authenticatable $user 97 | * 98 | * @return Token 99 | * @author luffyzhao@vip.126.com 100 | */ 101 | public function generate(Authenticatable $user): Token 102 | { 103 | $token = new Token($user); 104 | 105 | return $token; 106 | } 107 | 108 | /** 109 | * refresh 110 | * @throws TokenException 111 | * @author luffyzhao@vip.126.com 112 | */ 113 | public function refresh() 114 | { 115 | $token = $this->parse(); 116 | if ($token !== null) { 117 | $tokenArr = Crypt::decrypt($token); 118 | if ($tokenArr instanceof Token && $this->validateRefreshToken($tokenArr)) { 119 | /** @var Authenticatable $user */ 120 | $class = $tokenArr->getClass(); 121 | $user = new $class(); 122 | 123 | $newToken = new Token($user->find($tokenArr->getId())); 124 | 125 | return $newToken; 126 | } 127 | } 128 | throw new TokenException('invalid'); 129 | } 130 | 131 | /** 132 | * delete 133 | * @return bool 134 | * @author luffyzhao@vip.126.com 135 | */ 136 | public function delete() 137 | { 138 | return true; 139 | } 140 | 141 | 142 | /** 143 | * validateRefreshToken 144 | * @param Token $tokenArr 145 | * @return bool 146 | * @author luffyzhao@vip.126.com 147 | */ 148 | protected function validateRefreshToken(Token $tokenArr): bool 149 | { 150 | return $tokenArr->getTime() + $this->config['ttl'] > time(); 151 | } 152 | 153 | /** 154 | * validateInvalidToken 155 | * @param Token $tokenArr 156 | * @return bool 157 | * @author luffyzhao@vip.126.com 158 | */ 159 | protected function validateInvalidToken(Token $tokenArr): bool 160 | { 161 | if ($tokenArr->getTime() + $this->config['exp'] < time()) { 162 | return false; 163 | } 164 | return $this->validateToken($tokenArr); 165 | } 166 | 167 | /** 168 | * validateToken 169 | * @param Token $tokenArr 170 | * @return bool 171 | * @author luffyzhao@vip.126.com 172 | */ 173 | protected function validateToken(Token $tokenArr): bool 174 | { 175 | return true; 176 | } 177 | 178 | /** 179 | * 尝试从请求头解析token 180 | * @method parse 181 | * 182 | * @return mixed 183 | * @author luffyzhao@vip.126.com 184 | */ 185 | protected function parse(): ?string 186 | { 187 | $header = $this->request->headers->get($this->header) 188 | ?: $this->fromAltHeaders(); 189 | 190 | if ($header && preg_match('/' . $this->prefix . '\s*(\S+)\b/i', $header, $matches)) { 191 | return $matches[1] ?? null; 192 | } 193 | 194 | return null; 195 | } 196 | 197 | /** 198 | * 试图从某些其他可能的报头解析 token 199 | * @method fromAltHeaders 200 | * 201 | * @return mixed 202 | * @author luffyzhao@vip.126.com 203 | */ 204 | protected function fromAltHeaders() 205 | { 206 | return $this->request->input('_token'); 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/Console/Commands/Backup/Restore.php: -------------------------------------------------------------------------------- 1 | getPaths(database_path('back-up')); 54 | if (count($backups) === 0) { 55 | $this->warn('找不到备份数据!'); 56 | } else { 57 | $dir = $this->askVersion($backups); 58 | $this->restorePath($dir); 59 | } 60 | } 61 | 62 | /** 63 | * 还原 64 | * go 65 | * @param $dir 66 | * @author luffyzhao@vip.126.com 67 | */ 68 | protected function restorePath($dir) 69 | { 70 | 71 | $tables = $this->getPaths($dir); 72 | foreach ($tables as $table) { 73 | if (basename($table) === 'migrations') { 74 | continue; 75 | } 76 | $this->restoreTable($table); 77 | } 78 | } 79 | 80 | /** 81 | * 关闭外键约束 82 | * @author luffyzhao@vip.126.com 83 | */ 84 | protected function disableForeignKey() 85 | { 86 | DB::statement('SET FOREIGN_KEY_CHECKS = 0;'); 87 | } 88 | 89 | /** 90 | * restoreTable 91 | * @param $table 92 | * @author luffyzhao@vip.126.com 93 | */ 94 | protected function restoreTable($table) 95 | { 96 | $this->disableForeignKey(); 97 | DB::beginTransaction(); 98 | try { 99 | $this->truncate(basename($table)); 100 | $files = $this->getFiles($table); 101 | 102 | foreach ($files as $file) { 103 | $string = file_get_contents($file); 104 | $array = json_decode($string, true); 105 | if ($array !== false) { 106 | DB::table(basename($table))->insert($array); 107 | } 108 | } 109 | 110 | DB::commit(); 111 | } catch (\Exception $exception) { 112 | DB::rollBack(); 113 | } 114 | 115 | } 116 | 117 | /** 118 | * @param $table 119 | * @author luffyzhao@vip.126.com 120 | */ 121 | protected function truncate($table) 122 | { 123 | DB::statement('truncate table `' . $table . '`;'); 124 | } 125 | 126 | /** 127 | * 询问要还原的版本号 128 | * askVersion 129 | * @param array $backups 130 | * @return mixed 131 | * @author luffyzhao@vip.126.com 132 | */ 133 | protected function askVersion(array $backups) 134 | { 135 | $askString = "请选择您要的还原的备份数据:"; 136 | foreach ($backups as $key => $item) { 137 | $askString .= "\n [{$key}] {$item} "; 138 | } 139 | $index = $this->ask($askString); 140 | if (!isset($backups[$index])) { 141 | return $this->askVersion($backups); 142 | } 143 | return $backups[$index]; 144 | } 145 | 146 | /** 147 | * 获取目录下所有的文件 148 | * getFiles 149 | * @param $dir 150 | * @return array 151 | * @author luffyzhao@vip.126.com 152 | */ 153 | protected function getFiles($dir): array 154 | { 155 | if (substr($dir, -1) !== DIRECTORY_SEPARATOR) { 156 | $dir .= '/'; 157 | } 158 | if (!is_dir($dir)) { 159 | return []; 160 | } 161 | $fileArr = []; 162 | foreach (scandir($dir, 1) as $item) { 163 | if ($item === '.' || $item === '..') { 164 | continue; 165 | } 166 | if (is_file($dir . $item)) { 167 | $fileArr[] = $dir . $item; 168 | } 169 | } 170 | return $fileArr; 171 | } 172 | 173 | /** 174 | * 获取目录下所有子目录 175 | * getPaths 176 | * @param $dir 177 | * @return array 178 | * @author luffyzhao@vip.126.com 179 | */ 180 | protected function getPaths($dir): array 181 | { 182 | if (substr($dir, -1) !== DIRECTORY_SEPARATOR) { 183 | $dir .= '/'; 184 | } 185 | if (!is_dir($dir)) { 186 | return []; 187 | } 188 | $dirArr = []; 189 | foreach (scandir($dir, 1) as $item) { 190 | if ($item === '.' || $item === '..') { 191 | continue; 192 | } 193 | if (is_dir($dir . $item)) { 194 | $dirArr[] = $dir . $item; 195 | } 196 | } 197 | return $dirArr; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/Console/Commands/Backup/Run.php: -------------------------------------------------------------------------------- 1 | time = date('YmdHis'); 40 | $tables = $this->getTables(); 41 | $this->toFiles($tables); 42 | } 43 | /** 44 | * getTables 45 | * @return array 46 | * @author luffyzhao@vip.126.com 47 | */ 48 | protected function getTables(): array 49 | { 50 | return DB::select('show tables'); 51 | } 52 | /** 53 | * toFiles 54 | * @param array $tables 55 | * @author luffyzhao@vip.126.com 56 | */ 57 | protected function toFiles(array $tables) 58 | { 59 | $config = config('database.connections.'.DB::getDefaultConnection()); 60 | foreach ($tables as $table) { 61 | $tableName = $table->{'Tables_in_'.$config['database']}; 62 | // 地区表不导出 63 | if ($tableName === 'migrations') { 64 | continue; 65 | } 66 | if($tableName === 'areas'){ 67 | continue; 68 | } 69 | $columns = DB::selectOne('show columns from `'.$tableName.'`'); 70 | DB::table($tableName)->orderBy($columns->Field)->chunk( 71 | 1000, 72 | function (Collection $results, int $page) use ($tableName) { 73 | $json = $results->toJson(); 74 | $this->saveFile($tableName, $page, $json); 75 | } 76 | ); 77 | } 78 | } 79 | /** 80 | * saveFile 81 | * @param $table 82 | * @param int $page 83 | * @param string $json 84 | * @author luffyzhao@vip.126.com 85 | */ 86 | protected function saveFile($table, int $page, string $json) 87 | { 88 | $dir = database_path('back-up/'.$this->time.'/'.$table); 89 | if (!is_dir($dir)) { 90 | mkdir($dir, 0755, true); 91 | } 92 | file_put_contents($dir.'/'.$page.'.json', $json); 93 | } 94 | } -------------------------------------------------------------------------------- /src/Contracts/Debug/MessageBagErrors.php: -------------------------------------------------------------------------------- 1 | errors = new MessageBag; 40 | } else { 41 | $this->errors = is_array($errors) ? new MessageBag($errors) : $errors; 42 | } 43 | 44 | parent::__construct(422, $message, $previous, $headers, $code); 45 | } 46 | 47 | /** 48 | * 获取一个错误信息包 49 | * 50 | * @return \Illuminate\Support\MessageBag 51 | */ 52 | public function getErrors() 53 | { 54 | return $this->errors; 55 | } 56 | 57 | /** 58 | * 确定信息包是否有任何错误。 59 | * 60 | * @return bool 61 | */ 62 | public function hasErrors() 63 | { 64 | return ! $this->errors->isEmpty(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Exceptions/SearchException.php: -------------------------------------------------------------------------------- 1 | 36 && $toBase < 2){ 57 | throw new Exception(sprintf('进制不能大于36并且不能小于2')); 58 | } 59 | $incr = base_convert(Redis::incr($key), 10, $toBase); 60 | $strLen = mb_strlen($key) + mb_strlen($incr); 61 | if ($strLen > $length) { 62 | throw new Exception(sprintf('长度不够生成唯一主健')); 63 | } 64 | return $key . str_pad(strtoupper($incr), $length - mb_strlen($key), '0', STR_PAD_LEFT); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Libraries/VueMessage.php: -------------------------------------------------------------------------------- 1 | data = Collection::make(); 14 | } 15 | 16 | /** 17 | * @param $code 18 | * @param $message 19 | * @return void 20 | */ 21 | public function add($code, $message){ 22 | $this->data->add([ 23 | 'code'=>$code, 'message'=>$message 24 | ]); 25 | } 26 | 27 | 28 | /** 29 | * @return array|mixed 30 | */ 31 | public function jsonSerialize() 32 | { 33 | return $this->data; 34 | } 35 | 36 | /** 37 | * @return Collection 38 | */ 39 | public function getData(): Collection 40 | { 41 | return $this->data; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Listeners/QueryExecutedListener.php: -------------------------------------------------------------------------------- 1 | sqlBindings($queryExecuted)); 18 | } 19 | /** 20 | * 解析sql 21 | * @method sqlBindings 22 | * @param QueryExecuted $queryExecuted 23 | * 24 | * @return string 25 | * 26 | * @author luffyzhao@vip.126.com 27 | */ 28 | protected function sqlBindings(QueryExecuted $queryExecuted) 29 | { 30 | foreach ($queryExecuted->bindings as $i => $binding) { 31 | if ($binding instanceof \DateTime) { 32 | $queryExecuted->bindings[$i] = $binding->format('\'Y-m-d H:i:s\''); 33 | } else { 34 | if (is_string($binding)) { 35 | $queryExecuted->bindings[$i] = "'$binding'"; 36 | } 37 | } 38 | } 39 | $query = str_replace(array('%', '?'), array('%%', '%s'), $queryExecuted->sql); 40 | return vsprintf("[ SQL ] [ Driver: %s] %s [ RunTime: %u ms]", [ 41 | $queryExecuted->connectionName, 42 | vsprintf($query, $queryExecuted->bindings), 43 | $queryExecuted->time]); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Middlewares/CacheTokenMiddleware.php: -------------------------------------------------------------------------------- 1 | check()){ 28 | return redirect('/home'); 29 | } 30 | 31 | return $next($request); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Middlewares/VerifySignMiddleware.php: -------------------------------------------------------------------------------- 1 | all())) { 30 | throw new SignException('sign not true'); 31 | } 32 | return $next($request); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Providers/LaravelServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([$path => config_path('ltools.php')], 'config'); 33 | $this->mergeConfigFrom($path, 'ltools'); 34 | } 35 | 36 | /** 37 | * 服务注册. 38 | * 39 | * @method register 40 | * 41 | * @author luffyzhao@vip.126.com 42 | */ 43 | public function register() 44 | { 45 | $this->registerSign(); 46 | 47 | $this->extendAuthGuard(); 48 | 49 | $this->registerCommand(); 50 | } 51 | 52 | /** 53 | * 注册加签 54 | * @method registerSign 55 | * 56 | * @author luffyzhao@vip.126.com 57 | */ 58 | protected function registerSign() 59 | { 60 | $this->app->singleton( 61 | 'ltools.sign', 62 | function ($app) { 63 | return new SignManager($app['request']); 64 | } 65 | ); 66 | } 67 | 68 | /** 69 | * Extend Laravel's Auth. 70 | * 71 | * @return void 72 | */ 73 | protected function extendAuthGuard() 74 | { 75 | $this->app['auth']->extend('ltools.token', function ($app, $name, array $config) { 76 | 77 | $token = new TokenHandle($app['request'], $app['config']['ltools']['token-cache']); 78 | 79 | $guard = new CacheGuard( 80 | $app['auth']->createUserProvider($config['provider']), 81 | $token 82 | ); 83 | $app->refresh('request', $guard, 'setRequest'); 84 | return $guard; 85 | }); 86 | } 87 | 88 | /** */ 89 | protected function registerCommand(){ 90 | $this->app->singleton('luffyzhao.tools.backup.run', function ($app) { 91 | return new Run(); 92 | }); 93 | $this->commands('luffyzhao.tools.backup.run'); 94 | 95 | $this->app->singleton('luffyzhao.tools.backup.restore', function ($app) { 96 | return new Restore(); 97 | }); 98 | $this->commands('luffyzhao.tools.backup.restore'); 99 | } 100 | } -------------------------------------------------------------------------------- /src/Queue/Worker.php: -------------------------------------------------------------------------------- 1 | supportsAsyncSignals()) { 25 | $this->listenForSignals(); 26 | } 27 | 28 | $lastRestart = $this->getTimestampOfLastQueueRestart(); 29 | 30 | $this->callBack = $callBack; 31 | 32 | while (true) { 33 | // Before reserving any jobs, we will make sure this queue is not paused and 34 | // if it is we will just pause this worker for a given amount of time and 35 | // make sure we do not need to kill this worker process off completely. 36 | if (! $this->daemonShouldRun($options, $connectionName, $queue)) { 37 | $this->pauseWorker($options, $lastRestart); 38 | 39 | continue; 40 | } 41 | 42 | // First, we will attempt to get the next job off of the queue. We will also 43 | // register the timeout handler and reset the alarm for this job so it is 44 | // not stuck in a frozen state forever. Then, we can fire off this job. 45 | $job = $this->getNextJob( 46 | $this->manager->connection($connectionName), $queue 47 | ); 48 | 49 | if ($this->supportsAsyncSignals()) { 50 | $this->registerTimeoutHandler($job, $options); 51 | } 52 | 53 | // If the daemon should run (not in maintenance mode, etc.), then we can run 54 | // fire off this job for processing. Otherwise, we will need to sleep the 55 | // worker so no more jobs are processed until they should be processed. 56 | if ($job) { 57 | $this->runJob($job, $connectionName, $options); 58 | } else { 59 | $this->sleep($options->sleep); 60 | } 61 | 62 | if ($this->supportsAsyncSignals()) { 63 | $this->resetTimeoutHandler(); 64 | } 65 | 66 | // Finally, we will check to see if we have exceeded our memory limits or if 67 | // the queue should restart based on other indications. If so, we'll stop 68 | // this worker and let whatever is "monitoring" it restart the process. 69 | $this->stopIfNecessary($options, $lastRestart, $job); 70 | } 71 | } 72 | 73 | /** 74 | * @param string $connectionName 75 | * @param \Illuminate\Contracts\Queue\Job $job 76 | * @param WorkerOptions $options 77 | * @throws Exception 78 | * @throws Throwable 79 | * @author luffyzhao@vip.126.com 80 | */ 81 | public function process($connectionName, $job, WorkerOptions $options) 82 | { 83 | $callBack = $this->callBack; 84 | try{ 85 | $callBack($job); 86 | $job->delete(); 87 | }catch (Throwable | Exception $exception){ 88 | if($job->attempts() <= config('csRabbitMQ.attempts', 5)){ 89 | $job->release(30 * $job->attempts()); 90 | } 91 | throw $exception; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Repositories/RepositoryAbstract.php: -------------------------------------------------------------------------------- 1 | model->findOrFail($id, $columns); 39 | } 40 | 41 | /** 42 | * 通过主键查找一个模型. 43 | * 44 | * @method find 45 | * 46 | * @param int|string $id 主键ID 47 | * @param array $columns 获取字段 48 | * 49 | * @return \Illuminate\Database\Eloquent\Model 50 | * 51 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 52 | * 53 | * @author luffyzhao@vip.126.com 54 | */ 55 | public function lock($id, array $columns = ['*']) 56 | { 57 | return $this->model->lockForUpdate()->findOrFail($id, $columns); 58 | } 59 | 60 | /** 61 | * 获取全部模型. 62 | * 63 | * @method get 64 | * 65 | * @param array $columns 获取字段 66 | * 67 | * @return \Illuminate\Database\Eloquent\Collection 68 | * 69 | * @author luffyzhao@vip.126.com 70 | */ 71 | public function get(array $columns) 72 | { 73 | return $this->model->get($columns); 74 | } 75 | 76 | 77 | /** 78 | * 查找与属性匹配的记录并分页. 79 | * 80 | * @method paginate 81 | * 82 | * @param array $attributes Where条件 83 | * @param int $perPage 每页多少条 84 | * @param array $columns 获取字段 85 | * @param string $pageName 分页input字段 86 | * @param int $page 87 | * 88 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 89 | * 90 | * @author luffyzhao@vip.126.com 91 | */ 92 | public function paginate( 93 | array $attributes, 94 | int $perPage = null, 95 | array $columns = ['*'], 96 | $pageName = 'page', 97 | int $page = null 98 | ) 99 | { 100 | $perPage = request()->has('page-size') ? request()->input('page-size') : $perPage; 101 | return $this->model->where( 102 | $attributes 103 | )->paginate($perPage, $columns, $pageName, $page); 104 | } 105 | 106 | /** 107 | * 查找与属性匹配的记录并分页.(简单版). 108 | * 109 | * @method simplePaginate 110 | * 111 | * @param array $attributes Where条件 112 | * @param int $perPage 113 | * @param array $columns 获取字段 114 | * 115 | * @param string $pageName 116 | * @param null $page 117 | * 118 | * @return \Illuminate\Contracts\Pagination\Paginator 119 | * 120 | * @author luffyzhao@vip.126.com 121 | */ 122 | public function simplePaginate( 123 | array $attributes, 124 | int $perPage = null, 125 | $columns = ['*'], 126 | $pageName = 'page', 127 | $page = null 128 | ) 129 | { 130 | $perPage = request()->has('page-size') ? request()->input('page-size') : $perPage; 131 | return $this->model 132 | ->where($attributes)->simplePaginate($perPage, $columns, $pageName, $page); 133 | } 134 | 135 | /** 136 | * 创建模型. 137 | * 138 | * @method create 139 | * 140 | * @param array $attributes 属性 141 | * 142 | * @return \Illuminate\Database\Eloquent\Model|bool 143 | * 144 | * @author luffyzhao@vip.126.com 145 | */ 146 | public function create(array $attributes = []) 147 | { 148 | return $this->model->create($attributes); 149 | } 150 | 151 | /** 152 | * 更新 153 | * @method update 154 | * 155 | * @param $id 156 | * @param array $values 157 | * 158 | * @return Model | bool 159 | * 160 | * @throws \Throwable 161 | * @author luffyzhao@vip.126.com 162 | */ 163 | public function update($id, array $values) 164 | { 165 | return $this->find($id)->fill($values)->saveOrFail(); 166 | 167 | } 168 | 169 | /** 170 | * 删除数据模型 171 | * @method delete 172 | * 173 | * @param $id 174 | * 175 | * @return bool|mixed 176 | * @throws \Exception 177 | * 178 | * @author luffyzhao@vip.126.com 179 | */ 180 | public function delete($id) 181 | { 182 | return $this->find($id)->delete(); 183 | } 184 | 185 | /** 186 | * 获取第一条 187 | * @param array $attributes 188 | * @param array $columns 189 | * 190 | * @return Model 191 | */ 192 | public function first(array $attributes, array $columns = ['*']) 193 | { 194 | return $this->model->where($attributes)->firstOrFail($columns); 195 | } 196 | 197 | /** 198 | * @param $ids 199 | * @param callable $callback 200 | * @return array 201 | */ 202 | public function batches($ids, callable $callback) 203 | { 204 | $idArr = explode(',', $ids); 205 | $response = []; 206 | if (!empty($idArr)) { 207 | foreach ($idArr as $id) { 208 | $response[] = DB::transaction(function ()use ($id, $callback){ 209 | return $callback($id); 210 | }); 211 | } 212 | } 213 | return $response; 214 | } 215 | } -------------------------------------------------------------------------------- /src/Searchs/SearchAbstract.php: -------------------------------------------------------------------------------- 1 | ', 37 | '<=', 38 | '>=', 39 | '<>', 40 | '!=', 41 | '<=>', 42 | 'like', 43 | 'like binary', 44 | 'not like', 45 | 'ilike', 46 | '&', 47 | '|', 48 | '^', 49 | '<<', 50 | '>>', 51 | 'rlike', 52 | 'regexp', 53 | 'not regexp', 54 | '~', 55 | '~*', 56 | '!~', 57 | '!~*', 58 | 'similar to', 59 | 'not similar to', 60 | 'not ilike', 61 | '~~*', 62 | '!~~*', 63 | 'in', 64 | 'null', 65 | 'no null', 66 | 'date', 67 | 'month', 68 | 'day', 69 | 'year', 70 | 'raw', 71 | 'between', 72 | 'closure', 73 | ]; 74 | 75 | /** 76 | * 要解析的条件. 77 | * 78 | * @var [type] 79 | */ 80 | private $parses 81 | = [ 82 | 'in', 83 | 'null', 84 | 'no null', 85 | 'date', 86 | 'month', 87 | 'day', 88 | 'year', 89 | 'raw', 90 | 'between', 91 | ]; 92 | 93 | 94 | /** 95 | * 关系映射. 96 | * 97 | * @return array 98 | */ 99 | abstract protected function relationship(): array; 100 | 101 | 102 | /** 103 | * 构造函数 104 | * SearchAbstract constructor. 105 | * 106 | * @param Request $attributes 107 | */ 108 | public function __construct(Request $attributes) 109 | { 110 | $this->attributes = $attributes; 111 | } 112 | 113 | /** 114 | * 执行 115 | * 116 | * @method handle 117 | * 118 | * @author luffyzhao@vip.126.com 119 | */ 120 | private function handle(): array 121 | { 122 | $attributes = []; 123 | $relationship = $this->relationship(); 124 | if (!empty($relationship)) { 125 | foreach ($relationship as $column => $operator) { 126 | if ($this->attributes->offsetExists($column)) { 127 | $default = $this->attributes->offsetGet($column); 128 | if(is_null($default)){ 129 | continue; 130 | } 131 | $value = $this->getAttribute($column, $default); 132 | 133 | if (false === $value || is_null($value) || $value === '') { 134 | continue; 135 | } 136 | $attributes[] = $this->validate( 137 | $column, 138 | $operator, 139 | $value 140 | ); 141 | } 142 | } 143 | } 144 | 145 | return $attributes; 146 | } 147 | 148 | /** 149 | * 验证 150 | * @method validate 151 | * 152 | * @param $column 字段 153 | * @param $operator 条件 154 | * @param $value 值 155 | * 156 | * 157 | * @author luffyzhao@vip.126.com 158 | * @return array 159 | */ 160 | private function validate($column, $operator, $value): array 161 | { 162 | // 条件不在可选范围 163 | if (!in_array($operator, $this->operators)) { 164 | throw new SearchException('搜索条件超出可选范围!'); 165 | } 166 | // 条件是要解析的 167 | if (in_array($operator, $this->parses)) { 168 | $value = $this->parse($column, $operator, $value); 169 | } 170 | 171 | if ($value instanceof \Closure) { 172 | return [$value]; 173 | } else { 174 | return [$column, $operator, $value]; 175 | } 176 | } 177 | 178 | /** 179 | * 解析条件. 180 | * 181 | * @method parse 182 | * 183 | * @param [type] $column 字段 184 | * @param [type] $operator 条件 185 | * @param [type] $value 值 186 | * 187 | * @return \Closure 188 | * 189 | * @author luffyzhao@vip.126.com 190 | */ 191 | private function parse($column, $operator, $value): \Closure 192 | { 193 | if ($value instanceof \Closure) { 194 | return $value; 195 | } 196 | 197 | return function ($query) use ($column, $operator, $value) { 198 | switch ($operator) { 199 | case 'in': 200 | $query->whereIn($column, $value); 201 | break; 202 | case 'null': 203 | $query->whereNull($column); 204 | break; 205 | case 'no null': 206 | $query->whereNotNull($column); 207 | break; 208 | case 'date': 209 | $query->whereDate($column, $value); 210 | break; 211 | case 'month': 212 | $query->whereMonth($column, $value); 213 | break; 214 | case 'day': 215 | $query->whereDay($column, $value); 216 | break; 217 | case 'year': 218 | $query->whereYear($column, $value); 219 | break; 220 | case 'column': 221 | $query->whereColumn($column, $value); 222 | break; 223 | case 'exists': 224 | $query->whereExists($value); 225 | break; 226 | case 'raw': 227 | $query->whereRaw($value); 228 | break; 229 | case 'between': 230 | if(count($value) === 2 && !empty($value[0]) && !empty($value[1])){ 231 | $query->whereBetween($column, $value); 232 | } 233 | break; 234 | } 235 | }; 236 | } 237 | 238 | 239 | /** 240 | * 获取属性真实值 241 | * @method getAttr 242 | * 243 | * @param $key 244 | * @param $default 245 | * 246 | * @return mixed 247 | * 248 | * @author luffyzhao@vip.126.com 249 | */ 250 | private function getAttribute($key, $default) 251 | { 252 | $method = 'get'.Str::camel($key).'Attribute'; 253 | if (method_exists($this, $method)) { 254 | $default = call_user_func_array( 255 | [$this, $method], 256 | [$default, $this->attributes] 257 | ); 258 | } 259 | 260 | return $default; 261 | } 262 | 263 | /** 264 | * 默认数据 265 | * @method defaultArray 266 | * 267 | * @return array 268 | * @author luffyzhao@vip.126.com 269 | */ 270 | protected function defaultArray(): array 271 | { 272 | return []; 273 | } 274 | 275 | /** 276 | * 转数组. 277 | * 278 | * @method toArray 279 | * 280 | * @return array 281 | * 282 | * @author luffyzhao@vip.126.com 283 | */ 284 | public function toArray(): array 285 | { 286 | return array_merge($this->handle(), $this->defaultArray()); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/Services/ServiceAbstract.php: -------------------------------------------------------------------------------- 1 | request = $request; 26 | } 27 | 28 | /** 29 | * 转换 30 | * @return mixed 31 | */ 32 | abstract public function handle(); 33 | 34 | /** 35 | * @return array 36 | */ 37 | public function toArray() 38 | { 39 | return $this->request->toArray(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Sign/Drivers/Md5.php: -------------------------------------------------------------------------------- 1 | signKey = $signKey; 23 | } 24 | 25 | /** 26 | * 签名. 27 | * 28 | * @method sign 29 | * 30 | * @param array $data 31 | * 32 | * @return string 33 | * 34 | * @author luffyzhao@vip.126.com 35 | */ 36 | public function sign(array $data): string 37 | { 38 | // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 39 | return md5( 40 | $this->createLinkstring($this->sortKeys($data)).$this->getSignKey() 41 | ); 42 | } 43 | 44 | /** 45 | * 验证签名. 46 | * 47 | * @method verify 48 | * 49 | * @param array $data 50 | * @param string $sign 51 | * 52 | * @return bool 53 | * 54 | * @author luffyzhao@vip.126.com 55 | */ 56 | public function verify(array $data, string $sign): bool 57 | { 58 | $strSign = md5( 59 | $this->createLinkstring($this->sortKeys($data)).$this->getSignKey() 60 | ); 61 | 62 | return $strSign == $sign; 63 | } 64 | 65 | private function getSignKey(): string 66 | { 67 | return $this->signKey; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Sign/SignAbstract.php: -------------------------------------------------------------------------------- 1 | map( 29 | function ($item) { 30 | if (is_object($item) || is_array($item)) { 31 | return $this->sortKeys((array)$item); 32 | } 33 | return $item; 34 | } 35 | )->all(); 36 | ksort($items, SORT_REGULAR); 37 | return $items; 38 | } 39 | /** 40 | * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串. 41 | * 42 | * @method createLinkString 43 | * 44 | * @param array $data 45 | * 46 | * @return string 47 | * 48 | * @author luffyzhao@vip.126.com 49 | */ 50 | protected function createLinkString(array $data, $pix = ''): string 51 | { 52 | $sign = ''; 53 | if (!empty($data)) { 54 | foreach ($data as $key => $val) { 55 | $key = ('' === $pix ? $key : $pix."[{$key}]"); 56 | if (is_object($val) || is_array($val)) { 57 | $val = (array)$val; 58 | if (!empty($val)) { 59 | $sign .= $this->createLinkString((array)$val, $key).'&'; 60 | } else { 61 | $sign .= $key.'=&'; 62 | } 63 | } else { 64 | $sign .= $key.'='.urlencode($val).'&'; 65 | } 66 | } 67 | } 68 | //去掉最后一个&字符 69 | $sign = trim($sign, '&'); 70 | //如果存在转义字符,那么去掉转义 71 | if (get_magic_quotes_gpc()) { 72 | $sign = stripslashes($sign); 73 | } 74 | return $sign; 75 | } 76 | } -------------------------------------------------------------------------------- /src/Sign/SignManager.php: -------------------------------------------------------------------------------- 1 | request = $request; 39 | } 40 | 41 | /** 42 | * @param array $data 43 | * @param string $signType 44 | * 45 | * @return array 46 | * @throws SignException 47 | */ 48 | public function sign(array $data, $signType = 'md5'): array 49 | { 50 | $data = collect($data)->put('timestamp', Carbon::now()->timestamp) 51 | ->forget(['sign', 'sign_type'])->toArray(); 52 | 53 | $data['sign'] = $this->signDriver($signType)->sign($data); 54 | $data['sign_type'] = $signType; 55 | 56 | return $data; 57 | } 58 | 59 | 60 | /** 61 | * 验证加签数据 62 | * 63 | * @param array $data 64 | * 65 | * @return bool 66 | * @throws SignException 67 | */ 68 | public function validate(array $data) 69 | { 70 | // 时间验证 71 | if (!(isset($data['timestamp']) && $this->validateTimestamp($data['timestamp']))) { 72 | return false; 73 | } 74 | 75 | // sign 和 sign_type 必须 76 | if (!isset($data['sign']) || !isset($data['sign_type'])) { 77 | return false; 78 | } 79 | 80 | $verifyData = collect($data)->forget(['sign', 'sign_type'])->toArray(); 81 | 82 | 83 | return $this->signDriver($data['sign_type'])->verify($verifyData, $data['sign']); 84 | } 85 | 86 | /** 87 | * 验证时间. 88 | * 89 | * @method validateTimestamp 90 | * 91 | * @param $timestamp 92 | * 93 | * @return bool 94 | * 95 | * @author luffyzhao@vip.126.com 96 | */ 97 | private function validateTimestamp(int $timestamp) 98 | { 99 | return !empty($timestamp) 100 | && Carbon::createFromTimestamp($timestamp)->diffInRealSeconds() 101 | <= (int)Config::get( 102 | 'ltools.sign.time_out', 103 | 60 104 | ); 105 | } 106 | 107 | /** 108 | * 获取加签对象 109 | * 110 | * @method signObj 111 | * 112 | * @param $signType 113 | * 114 | * @return Md5|Rsa 115 | * 116 | * @throws SignException 117 | * 118 | * @author luffyzhao@vip.126.com 119 | */ 120 | private function signDriver($signType) 121 | { 122 | switch (strtoupper($signType)) { 123 | case 'MD5': 124 | $signObj = new Md5($this->signKey); 125 | break; 126 | break; 127 | default: 128 | throw new SignException('sign type must be filled in'); 129 | } 130 | 131 | return $signObj; 132 | } 133 | 134 | /** 135 | * @return mixed 136 | * @author luffyzhao@vip.126.com 137 | */ 138 | public function getSignKey() 139 | { 140 | return $this->signKey; 141 | } 142 | 143 | /** 144 | * @param mixed $signKey 145 | * @return SignManager 146 | * @author luffyzhao@vip.126.com 147 | */ 148 | public function setSignKey($signKey): self 149 | { 150 | $this->signKey = $signKey; 151 | return $this; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Traits/CodeTrait.php: -------------------------------------------------------------------------------- 1 |