Commit 09f9dfb7 authored by hangjun83's avatar hangjun83

后端框架初始化提交

parent 5a844386
#标准树(有三种不同的树:未注册的树x主要用于本地或私有环境,个人树prs主要用于非商业发行的项目,供应商树vndvnd主要用于可公开获得和分发的项目。您使用的标准树将取决于您正在开发的项目。)
API_STANDARDS_TREE=x
#亚型(子类型通常是应用程序或项目的缩写全为小写。)
API_SUBTYPE=rhawn
#前缀
API_PREFIX=/
#域(也就是项目网址)
API_DOMAIN=localhost
#默认API版本
API_VERSION=v1
#API名称
API_NAME="rhawn tools"
#条件请求状态(默认情况下,条件请求处于启用状态,因为它将在可能的情况下利用客户端缓存功能来缓存API请求。
API_CONDITIONAL_REQUEST=false
#严格模式(严格模式将要求客户端发送Accept标头,而不是默认使用配置文件中指定的版本。这意味着您将无法通过Web浏览器浏览API。如果启用了严格模式并且使用了无效的Accept头,
#那么API将抛出未处理的内容Symfony\Component\HttpKernel\Exception\BadRequestHttpException,您应该适当地对其进行处理。)
API_STRICT=true
#默认响应格式
API_DEFAULT_FORMAT=json
#调试模式
API_DEBUG=true
# lumen 配置文件
APP_NAME=Lumen
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=UTC
APP_LOCALE=en
LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=
LOG_QUERY=false
LOG_REQUEST=false
# 数据库配置
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=rhawn_tools
DB_USERNAME=root
DB_PASSWORD=123456
#MONGODB_HOST=127.0.0.1
#MONGODB_PORT=27017
#MONGODB_USERNAME=homestead
#MONGODB_PASSWORD=secret
#MONGODB_DATABASE=homestead
#MONGODB_AUTHENTICATION_DATABASE=admin
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
JWT_SECRET=
MIT License
Copyright (c) 2020 Jiannei
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# 罗恩 lumen api项目️
查找了网上很多的API 相关的开发规范文档,参考了不少大佬们总结的经验,决定尝试使用最新版本的 Lumen(当下最新版本是 Lumen 8.x)来构建一个**基础功能**完备,**规范统一**,能够**快速**应用于实际的 API 项目开发启动模板。同时,也希望通过**合理的**架构设计使其适用于中大型项目。
少许的依赖安装,遵循 Laravel 的思维进行扩展,不额外增加「负担」。
开箱即用,加速 Api 开发。
![StyleCI build status](https://github.styleci.io/repos/267924989/shield)
![Test](https://github.com/Jiannei/lumen-api-starter/workflows/Test/badge.svg?branch=master)
[中文文档](https://github.com/Jiannei/lumen-api-starter/blob/master/README.md)
### 社区讨论传送
- [是时候使用 Lumen 8 + API Resource 开发项目了!](https://learnku.com/articles/45311)
- [一篇 RESTful API 路由设计的最佳实践](https://learnku.com/articles/45526)
- [教你更优雅地写 API 之「规范响应数据」](https://learnku.com/articles/52784)
- [教你更优雅地写 API 之「枚举使用」](https://learnku.com/articles/53015)
- [教你更优雅地写 API 之「记录日志」](https://learnku.com/articles/53669)
Lumen学习交流群:1105120693(QQ)
## 概览
### 现已支持
- 适配 Laravel 7 中新增的 HttpClient 客户端(已升级到 Laravel 8)
- RESTful 规范的路由定义和 HTTP 响应结构
- 使用 Laravel Api Resource
- 支持自定义**业务操作应码**以及**业务操作描述**(多语言支持,根据配置中的 APP_LOCAL 配置返回)
- Jwt-auth 方式授权(支持将授权用户缓存到 redis,减少 user 表查询频次)
- 更为便捷地使用枚举/常量:方便地对枚举进行判断校验;请求中包含枚举参数可以自动转换为对应枚举实例
- 支持日志记录到 MongoDB:
- 异步队列记录日志,包括所有请求日志、SQL 日志、异常日志、业务日志’;
- 每次请求关联了 UNIQUE_ID,可以通过 UNIQUE_ID 查询出单次请求产生的全部日志
- 请求日志包含单次请求执行时间记录
- 支持以每日、每月以及每年按表进行拆分
- 扩展 l5-repository,支持 cursor 方式分页
- 合理有效地「Repository & Service」架构设计 😏
### 计划支持
其他规划讨论中...
### 目录结构一览
```
├── app
│   ├── Console
│   │   ├── Commands
│   │   └── Kernel.php // Schedule 调度
│   ├── Contracts // 定义 interface
│   │   └── Repositories
│   ├── Events
│   │   ├── Event.php
│   │   └── ExampleEvent.php
│   ├── Exceptions // 异常处理
│   │   └── Handler.php
│   ├── Http
│   │   ├── Controllers // Controller 任务分发给不同 Service 处理,返回响应给客户端
│   │   ├── Middleware
│   │   └── Resources // Api Resource 数据转换
│   ├── Jobs // 异步任务
│   │   ├── ExampleJob.php
│   │   └── Job.php
│   ├── Listeners
│   │   └── ExampleListener.php
│   ├── Policies // 权限校验
│   │   └── PostPolicy.php
│   ├── Providers
│   │   ├── AppServiceProvider.php
│   │   ├── AuthServiceProvider.php
│   │   ├── EloquentUserProvider.php // 定制的 EloquentUserProvider,缓存授权用户信息
│   │   ├── EventServiceProvider.php
│   │   └── RepositoryServiceProvider.php // repository 模式架构中,将 interface 与 repository 进行对象绑定
│   ├── Repositories // Repository 层:数据仓库层
│   │   ├── Criteria // 数据查询条件的组装拼接;(可以将公共的或者复杂的查询条件放在这个地方)
│   │   ├── Eloquent // 定义针对某个数据表(或存在关联关系的数据表)的数据维护逻辑;不处理业务(动态数据;实质的 Repository;基于 Eloquent\Model 的封装 )
│   │   ├── Enums // 枚举集合(静态数据)
│   │   ├── Models // Laravel 原始的 Eloquent\Model:定义数据表特性、数据表之间的关联关系等;不处理业务
│   │   ├── Presenters // 配合 Transformer 使用
│   │   ├── Transformers // 响应前的数据转换,作用与 Api Resource 类似,但是功能更丰富
│   │   └── Validators // Eloquent 数据维护前的校验,与表单验证功能类似
│   ├── Services // Service 层:处理实际业务;调用 Repository
│   │   ├── PostService.php
│   │   └── UserService.php
│   └── Support // 对框架的扩展,或者实际项目中需要封装一些与业务无关的通用功能集
│   ├── Serializers // league/fratcal 的 ArraySerializer 扩展,支持简单分页数据格式转换
│   ├── Traits // Class 中常用的辅助功能集
│   └── helpers.php // 全局会用到的辅助函数
```
## Repository & Service 模式架构
在添加这部分描述的时候,联想到了 Vue 中的 Vuex,熟悉 Vuex 的同学可以类比一下。
```
Controller => dispatch,校验请求后分发业务处理
Service => action,具体的业务实现
Repository => state、mutation、getter,具体的数据维护
```
### 实际案例
为了更好地理解 Repository & Service 模式,对 Laravel 中文社区的教程 2 中的 Larabbs 项目使用该模式进行了重构,实际开发过程可以参考其中的分层设计。
[larabbs](https://github.com/Jiannei/larabbs)
### 职责说明
**Controller 岗位职责**
1. 校验是否有必要处理请求,是否有权限和是否请求参数合法等。无权限或不合法请求直接 response 返回格式统一的数据
2. 将校验后的参数或 Request 传入 Service 中具体的方法,安排 Service 实现具体的功能业务逻辑
3. Controller 中可以通过`__construct()`依赖注入多个 Service。比如 `UserController` 中可能会注入 `UserService`(用户相关的功能业务)和 `EmailService`(邮件相关的功能业务)
4. 使用统一的 `$this->response`调用`sucess``fail`方法来返回统一的数据格式
5. (可选)使用 Laravel Api Resource 的同学可能在 Controller 中还会有转换数据的逻辑。比如,`return Response::success(new UserCollection($resource));``return Response::success(new UserResource($user));`
**Service 岗位职责**
1. 实现项目中的具体**功能**业务。所以 Service 中定义的方法名,应该是用来**描述功能或业务**的(动词+业务描述)。比如`handleListPageDisplay``handleProfilePageDisplay`,分别对应用户列表展示和用户详情页展示的需求。
2. 处理 Controller 中传入的参数,进行**业务判断**
3.(可选)根据业务需求配置相应的 Criteria 和 Presenter 后(不需要的可以不用配置,或者将通用的配置到 Repository 中)
4. 调用 Repository 处理**数据的逻辑**
5. Service 可以不注入 Repository,或者只注入与处理当前业务**存在数据关联**的 Repository。比如,`EmailService`中或许就只有调用第三方 API 的逻辑,不需要更新维护系统中的数据,就不需要注入 Repository;`OrderService`中实现了订单出库逻辑后,还需要生成相应的财务结算单据,就需要注入 `OrderReposoitory``FinancialDocumentRepository`,财务单据中的原单号关联着订单号,存在着数据关联。
6. Service 中不允许调用其他 Service,保持职责单一,如有需要,应该考虑 Controller 中调用
**Repository 岗位职责**
1. 只负责**数据维护**的逻辑,数据怎么查询、更新、创建、删除,以及**相关联**的数据如何维护。所以 Repository 中定义的方法名,应该是用来描述**数据是以怎样的形式去维护的**。比如 `searchUsersByPage``searchUsersById``insertUser`
2. Repository 只绑定**一个** model,**只允许**维护与当前 Repository 绑定的 Model 数据,**最多允许**维护与绑定的 Model 存在关联关系的 Model。比如,订单 OrderRepository 中会涉及到更新订单商品 OrderGoodsRepository 的数据。
3. Repository 中可以配置条件查询(Criteria)、数据校验(Validator)和数据转换显示(Presenter),通常是将通用的配置在 Repository,不通用的独立出相应文件。
4. Repository 本质是在 Laravel ORM Model 中的一层封装,可以完全不用担心使用 Repository 等同于放弃了 ORM 灵活性。原先常用的 ORM 方法**并没有移除**,只是位置从 Controller 中换到了 Repository 而已。
5. Repository 中的 `$this->model` 实际就是绑定的 Model 实例,所以就有了这样的写法`$this->model::all()`,与原先的 ORM 写法`User::all()`是完全等价的。
6. Repository 中不允许引入其他 Repository
**Model 岗位职责**
经过前面的 Service 和 Repository 「分层」,剥离了可能存在于 Model 中的很多逻辑,比如校验参数,拼接查询,处理业务和转换数据结构等。所以,现如今的 Model 只需要相对简单地数据定义就可以了。比如,对数据表的定义,字段的映射,以及数据表之间关联关系等,提供给 Repository 中使用就够了。
### Repository 模式中涉及到的一些名词理解
完整的执行顺序:`Criteria -> Validator -> Presenter`
**Enums**:
这个是 lumen-api-starter 新增的部分,用来定义应用系统中常量的数据。
**Criteria**[l5-repository criteria](https://github.com/andersao/l5-repository#example-the-criteria)
作用类似 Eloquent Model 中的 Scope 查询,把常用的查询提取出来,但是比 Scope 更强大。
可以省去 Model 中大量的根据请求参数判断并拼接查询条件的代码,与此同时,能够做到将多种数据之间存在的**通用**筛选条件剥离出来。
比如 `make:repository`创建生成的 Repository 中默认包含以下代码,就是给 Repository 默认配置了一个 RequestCriteria,就可以直接使用下面的方式来过滤数据,是不是非常方便?!
```php
public function boot()
{
$this->pushCriteria(app(RequestCriteria::class));
}
```
**Presenter**[L5-repository presenters](https://github.com/andersao/l5-repository#presenters)
可选,使用 Api Resource 的同学可以略过。需要安装 `composer require league/fractal`,Dingo Api 中的 transformer 也是使用了这个扩展包。
作用类似 Laravel 的 Api Resource,或者可以说 Api Resource 是 Transformer 的轻量实现。
L5-repository 认为你将数据表结构的**数据转换**后是为了用来**展示**的,所以它将数据转换相关的逻辑独立出来,称为 Presenter。本质是整合了 fractal 中的 transformer 功能。
Transformer 的优秀之处这里暂不做讨论,因为这里的主角是 Presenter。[传送门](https://fractal.thephpleague.com/)
先对比一下几种数据转换方式:
- Dingo Api 中 transformer 的使用方式
在 Controller 中调用 Response 中的 item 返回数据时传入 transformer 来转换数据
```php
return $this->item($user, new UserTransformer, ['key' => 'user']);
```
- Laravel 中 Api Resource 的使用方式
在 Controller 中调用 Resource 或者 ResourceCollection 转换数据
```php
//return Response::success(new UserResource($user));// 使用 lumen-api-starter 统一 code\status\message\data
return new UserResource($user);// 未统一响应结构
```
- L5-repository 中 transformer 的使用方式(为了避免混淆,这里讲的是独立出文件的形式,当然也有可以直接在 model 或 repository 中定义的方式,更详细的使用请参考 l5-repository 的说明)
需要先定义 transformer,然后在 Presenter 中「注册」,最后在调用 Repository 时使用。
举例:
定义 UserTransformer
```
// app/Repositories/Transformers/UserTransformer.php
<?php
namespace App\Repositories\Transformers;
use App\Repositories\Models\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'nickname' => $user->name,
'email' => $user->email,
];
}
}
```
「注册」到 UserPresenter
```php
// app/Repositories/Presenters/UserPresenter.php
<?php
namespace App\Repositories\Presenters;
use App\Repositories\Transformers\UserTransformer;
use League\Fractal\TransformerAbstract;use Prettus\Repository\Presenter\FractalPresenter;
class UserPresenter extends FractalPresenter
{
/**
* Prepare data to present
*
* @return TransformerAbstract
*/
public function getTransformer()
{
return new UserTransformer();
}
}
```
在调用 repository 的时候使用
```php
// app/Services/UserService.php
public function listPage(Request $request)
{
$this->repository->pushCriteria(new UserCriteria($request));
$this->repository->setPresenter(UserPresenter::class);
return $this->repository->searchUsersByPage();
}
```
看得出 Dingo Api 和 Api Resource 都是在最后响应数据的环节来转换数据,而 Repository 模式中认为**但凡是与数据有关的处理逻辑都应该被「装进 Repository中」**,应用系统中的其他部分不需要关心数据如何去查询(Criteria),如何去校验(Validator),以及如何去转换后提供显示(Presenter)。其他部分做好相应的职责就行,但凡与数据打交道的地方都交给 Repository。
### 规范
* 命名规范:
- controller:
- 类名:名词,复数形式,描述是对整个资源集合进行操作;当没有集合概念的时候。换句话说,当资源只有一个的情况下,使用单数资源名称也是可以的——即一个单一的资源。例如,如果有一个单一的总体配置资源,你可以使用一个单数名称来表示
- 方法名:动词+名词,体现资源操作。如,store\destroy
- service:
- 类名:名词,单数。比如`UserService``EmailService``OrderService`
- 方法名:`动词+名词`,描述能够实现的业务需求。比如:`handleRegistration`表示实现用户注册功能。
- repository
- 类名:名词,单数。`make:repository`命令可以直接生成。
- 方法名:动词+名词,描述数据的维护(CRUD)。 比如:`searchUsersByPage`
- 可能会出现的动词:createXXX(add);searchXXX;queryXXX、findXXX、fetch(get);updateXXX;deleteXXX(destroy);组合形式:with\Join...,如 searchOrdersLeftJoinGodds
- 通常情况 Database、Cache、Redis、Carbon 操作只能出现在 repository
* 使用规范:待补充
## Packages
- [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) (可选,需要使用 Laravel 7 新增的 HttpClient 时安装)
- [jenssegers/mongodb](https://github.com/jenssegers/laravel-mongodb) (可选,需要使用记录日志到 MongoDB 时安装)
- [tymon/jwt-auth](https://github.com/tymondesigns/jwt-auth) (默认支持 JWT 授权)
- [illuminate/redis](https://github.com/illuminate/redis) (默认使用 Redis 来缓存)
- [spatie/laravel-permission](https://github.com/spatie/laravel-permission) (使用这个包来管理分配用户权限)
- [prettus/l5-repository](https://github.com/andersao/l5-repository) (默认使用 Repository 模式)
- [league/fractal](https://github.com/thephpleague/fractal) (可选,需要用到 transformer 时安装)
## 其他
依照惯例,如对您的日常工作有所帮助或启发,欢迎三连 `star + fork + follow`
如果有任何批评建议,通过邮箱(longjian.huang@foxmail.com)的方式(如果我每天坚持看邮件的话)可以联系到我。
总之,欢迎各路英雄好汉。
## 参考
* [RESTful API 最佳实践](https://learnku.com/articles/13797/restful-api-best-practice)
* [RESTful 服务最佳实践](https://www.cnblogs.com/jaxu/p/7908111.html)
* [DingoApi](https://github.com/dingo/api)
* [overtrue/laravel-query-logger](https://github.com/overtrue/laravel-query-logger)
* [BenSampo/laravel-enum](https://github.com/BenSampo/laravel-enum)
* [spatie/laravel-enum](https://github.com/spatie/laravel-enum)
## License
The Lumen Api Starter is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
*/
protected function schedule(Schedule $schedule)
{
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Events;
use Illuminate\Queue\SerializesModels;
abstract class Event
{
use SerializesModels;
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Events;
class ExampleEvent extends Event
{
/**
* Create a new event instance.
*/
public function __construct()
{
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Exceptions;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
use Jiannei\Response\Laravel\Support\Traits\ExceptionTrait;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class Handler extends ExceptionHandler
{
use ExceptionTrait;
/**
* A list of the exception types that should not be reported.
*
* @var array
*/
protected $dontReport = [
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
HttpResponseException::class,
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param Throwable $exception
*
* @throws Exception
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param Request $request
* @param Throwable $exception
* @return Response|JsonResponse
*
* @throws Throwable
*/
public function render($request, Throwable $exception)
{
return parent::render($request, $exception);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Http\Middleware;
use App\Repositories\Enums\ResponseCodeEnum;
use Closure;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use App\Support\Traits\Helpers;
class Authenticate
{
use Helpers;
/**
* The authentication guard factory instance.
*
* @var Auth
*/
protected $auth;
/**
* Create a new middleware instance.
*
* @param Auth $auth
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param string|null $guard
* @return mixed
*
* @throws AuthorizationException
*/
public function handle($request, Closure $next, $guard = null)
{
/**
* 如果路由是登出或者是修改密码的时候,需要对token进行处理,否则jwt处理时会出错
*/
if(Str::endsWith(strtolower((string) $request->getPathInfo()), 'logout') ||
Str::endsWith(strtolower((string) $request->getPathInfo()), 'resetpassword')
){
$token = trim(str_ireplace('bearer', '', $request->header('authorization')));
$decodeToken = $this->decodeToken($token);
if(empty($decodeToken) || (!is_array($decodeToken) && !$decodeToken['hash'])){
throw new UnauthorizedHttpException('JWTAuth', 'Unable to authenticate with invalid token.');
}
/*$chains = app('tymon.jwt.parser')->getChain();
$chains[0]->setHeaderName($decodeToken['hash']);
app('tymon.jwt.parser')->setChain($chains);*/
$request->headers->set('authorization','bearer'.$decodeToken['hash']);
}
return $next($request);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
public function toArray($request)
{
return $this->collection->map(function ($item) {
return [
'nickname' => $item->name,
'email' => $item->email,
];
})->all();
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'nickname' => $this->name,
'email' => $this->email,
];
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Http\V1\Auth;
use App\Repositories\Enums\ResponseCodeEnum;
use Illuminate\Http\Request;
use Jiannei\Response\Laravel\Support\Facades\Response;
use App\Http\V1\Controller;
use App\Services\AuthService;
use App\Services\RoleAndPermissionsService;
class AuthorizationController extends Controller
{
protected $authService = null;
/**
* Create a new AuthController instance.
* @param \App\Services\AuthService $authService
*/
public function __construct(AuthService $authService)
{
$this->authService = $authService;
}
/**
* @param \Illuminate\Http\Request $request
*/
public function login(Request $request)
{
$message = [
'username.required' => "请输入用户名",
'password.required' => "请输入密码",
];
$this->validateRequest($request, $message);
try{
$response = $this->authService->login($request);
return Response::success([
'token' => $response,
], '登陆成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
/**
* @param \Illuminate\Http\Request $request
*/
public function logout(Request $request)
{
try{
$this->authService->logout($request);
return Response::success(null,'请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
public function addUser(Request $request)
{
$message = [
'username.required' => "用户登陆账号必填",
'password.required' => "密码必填",
'type.required' => "用户类型必选",
'nickname.required' => "用户名称必填",
'email.required' => "邮箱必填",
];
$this->validateRequest($request, $message);
try{
$this->authService->saveOrUpdateUser($request);
return Response::success(null,'请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
public function editUser(Request $request)
{
$message = [
'type.required' => "用户类型必选",
'nickname.required' => "用户名称必填",
'email.required' => "邮箱必填",
];
$this->validateRequest($request, $message);
try{
$this->authService->saveOrUpdateUser($request);
return Response::success(null,'请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
public function changeUserStatus(Request $request, $id)
{
if(!$id){
return Response::fail('缺少id参数',500);
}
try{
$this->authService->updateUserStatus($request,$id);
return Response::success(null,'请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
public function delUserByIds(Request $request)
{
$message = [
'ids.required' => "用户id必传",
];
$this->validateRequest($request, $message);
try{
$this->authService->deleteUser($request);
return Response::success(null,'请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
/**
* 获取用户信息
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
*/
public function info(Request $request)
{
try{
$user = $this->authService->getUserInfo();
$service = app(RoleAndPermissionsService::class);
$service->getRolesToUser();
return Response::success([
'userinfo' => $user,
], '请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
public function listByPage(Request $request)
{
try{
$service = app(AuthService::class);
$roles = $service->listByPage($request);
return Response::success($service->formatKeysfromArray($roles,'toCamelCase'), '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
/**
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
*/
public function resetPassword(Request $request)
{
$message = [
'oldpassword.required' => "请输入旧密码",
'newpassword.required' => "请输入新密码",
];
$this->validateRequest($request, $message);
try{
$response = $this->authService->resetPassword($request);
return Response::success([], '更新成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
/**
* 获取菜单列表
* @param Request $request
* @return \Illuminate\Http\JsonResponse|void
*/
public function getMenuList(Request $request)
{
try{
$service = app(RoleAndPermissionsService::class);
$menuList = $service->getUserRoleMenus();
return Response::success([
'data' => $service->getMenusTree($menuList,'page'),
], '请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
/**
* 获取所有菜单列表
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
*/
public function getAllMenuList(Request $request)
{
try{
$service = app(RoleAndPermissionsService::class);
$menuList = $service->getAllMenus();
$treeMenu = $service->getMenusTree($menuList);
return Response::success([
'data' => $service->formatKeysfromArray($treeMenu,'toCamelCase'),
], '请求成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
/**
* 更新菜单
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
*/
public function editMenus(Request $request)
{
$message = [
'id.required' => "菜单id必选",
'name.required' => "菜单名称必填",
'path.required' => "菜单路径必填",
'icon.required' => "菜单icon必填",
'parentId.required' => "上级菜单必选",
'menuType.required' => "菜单类型必选",
'component.required' => "菜单前端组件必填",
'status.required' => "菜单状态必选",
'sort.required' => "菜单排序必填",
];
$this->validateRequest($request, $message);
if($request->get('selected') != true){
return Response::fail('该菜单未被选中编辑');
}
try{
$service = app(RoleAndPermissionsService::class);
$service->editMenu($request);
return Response::success([], '更新成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
public function getDefaultButtonPermission(Request $request)
{
try{
$service = app(RoleAndPermissionsService::class);
$buttonPermissions = $service->getButtonDefaultPermission();
return Response::success($buttonPermissions, '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
public function addSubMenus(Request $request)
{
$message = [
'type.required' => "菜单类型必选",
'parentId.required' => "菜单上统计菜单必填",
'level.required' => "菜单深度必填",
'sort.required' => "菜单排序必填",
'status.required' => "菜单状态必选",
'path.required' => "菜单路径必填",
'title.required' => "菜单名称必填",
];
$this->validateRequest($request, $message);
if('page' == $request->get('type') && $request->get('level') == 2){
if(empty($request->get('icon'))){
return Response::fail('菜单图标必选');
}
if(empty($request->get('component'))){
return Response::fail('菜单前端组件必填');
}
}
if('button' == $request->get('type') && $request->get('level') == 3){
if(empty($request->get('buttonType'))){
return Response::fail('菜单行为必选');
}
}
try{
$service = app(RoleAndPermissionsService::class);
$service->addSubMenu($request);
return Response::success([], '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
public function deleteMenus(Request $request)
{
$message = [
'ids.required' => "菜单id必选",
];
$this->validateRequest($request, $message);
try{
$service = app(RoleAndPermissionsService::class);
$service->deleteMenus($request);
return Response::success([], '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
/**
* 获取角色列表(页面列表显示)
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
*/
public function getAllByPage(Request $request)
{
try{
$service = app(RoleAndPermissionsService::class);
$roles = $service->getAllRolesToPage($request);
return Response::success($service->formatKeysfromArray($roles,'toCamelCase'), '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
public function getAllList(Request $request)
{
try{
$service = app(RoleAndPermissionsService::class);
$roles = $service->getAllRolesList($request);
return Response::success($service->formatKeysfromArray($roles,'toCamelCase'), '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),$exception->getCode());
}
}
public function addRole(Request $request)
{
$message = [
'name.required' => "角色名称必填",
'remark.required' => "角色描述必填",
];
$this->validateRequest($request, $message);
try{
$service = app(RoleAndPermissionsService::class);
$service->saveRole($request);
return Response::success([], '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
/**
* 编辑权限
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
*/
public function editRole(Request $request)
{
$message = [
'id.required' => "角色id必填",
'name.required' => "角色名称必填",
'remark.required' => "角色描述必填",
];
$this->validateRequest($request, $message);
try{
$service = app(RoleAndPermissionsService::class);
$service->saveRole($request);
return Response::success([], '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
/**
* 分配角色权限
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
*/
public function editRolePermission(Request $request)
{
$message = [
'roleId.required' => "角色id必填",
];
$this->validateRequest($request, $message);
try{
$service = app(RoleAndPermissionsService::class);
$service->syncPermissionToRole($request);
return Response::success([], '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
public function delByIds(Request $request)
{
$message = [
'ids.required' => "角色id必填",
];
$this->validateRequest($request, $message);
try{
$service = app(RoleAndPermissionsService::class);
$service->deleteRoleByIds($request);
return Response::success([], '操作成功');
}catch(\Exception $exception){
return Response::fail($exception->getMessage(),500);
}
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Http\V1;
use Jiannei\Response\Laravel\Support\Facades\Response;
use Jiannei\Response\Laravel\Support\Traits\ExceptionTrait;
use Laravel\Lumen\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
use ExceptionTrait;
/**
* request 参数验证
* @param $request
* @param $message
* @return bool|\Illuminate\Http\JsonResponse
*/
protected function validateRequest($request, $message){
$validateArr = [];
collect($message)->map(function($value,$key) use (&$validateArr){
$keys = explode('.',$key);
$validateArr[$keys[0]] = $keys[1];
});
$validator = Validator($request->all(), $validateArr, $message);
if ($validator->fails()) {
foreach($validator->errors()->getMessages() as $error) {
return Response::fail($error[0]);
}
}
return true;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Jobs;
class ExampleJob extends Job
{
/**
* Create a new job instance.
*/
public function __construct()
{
}
/**
* Execute the job.
*/
public function handle()
{
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
abstract class Job implements ShouldQueue
{
/*
|--------------------------------------------------------------------------
| Queueable Jobs
|--------------------------------------------------------------------------
|
| This job base class provides a central location to place any logic that
| is shared across all of your jobs. The trait included with the class
| provides access to the "queueOn" and "delay" queue helper methods.
|
*/
use InteractsWithQueue;
use Queueable;
use SerializesModels;
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Listeners;
use App\Events\ExampleEvent;
class ExampleListener
{
/**
* Create the event listener.
*/
public function __construct()
{
}
/**
* Handle the event.
*
* @param \App\Events\ExampleEvent $event
*/
public function handle(ExampleEvent $event)
{
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Observers;
use App\Repositories\Enums\CacheEnum;
use App\Repositories\Models\AdminUsers;
use Illuminate\Support\Facades\Cache;
class UserObserver
{
public function updated(AdminUsers $user)
{
Cache::forget(CacheEnum::getCacheKey(CacheEnum::AUTHORIZATION_USER));
}
public function deleted(AdminUsers $user)
{
Cache::forget(CacheEnum::getCacheKey(CacheEnum::AUTHORIZATION_USER));
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Policies;
use App\Repositories\Enums\PermissionEnum;
use App\Repositories\Models\Post;
use App\Repositories\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any posts.
*
* @param User $user
* @return mixed
*/
public function viewAny(User $user)
{
if ($user->can(PermissionEnum::ROUTE_POSTS_VIEW_ANY()->name)) {
return true;
}
return false;
}
/**
* Determine whether the user can view the post.
*
* @param User|null $user
* @param Post $post
* @return mixed
*/
public function view(?User $user, Post $post)
{
if ($post->published) {
return true;
}
// visitors cannot view unpublished items
if ($user === null) {
return false;
}
// admin overrides published status
if ($user->can(PermissionEnum::ROUTE_POSTS_VIEW()->name)) {
return true;
}
// authors can view their own unpublished posts
return $user->isOwnerOf($post);
}
/**
* Determine whether the user can create posts.
*
* @param User $user
* @return mixed
*/
public function create(User $user)
{
if ($user->can(PermissionEnum::ROUTE_POSTS_CREATE()->name)) {
return true;
}
return false;
}
/**
* Determine whether the user can update the post.
*
* @param User $user
* @param Post $post
* @return mixed
*/
public function update(User $user, Post $post)
{
if ($user->isOwnerOf($post) || $user->can('edit all posts')) {
return true;
}
return false;
}
/**
* Determine whether the user can delete the post.
*
* @param User $user
* @param Post $post
* @return mixed
*/
public function delete(User $user, Post $post)
{
if ($user->isOwnerOf($post) || $user->can(PermissionEnum::ROUTE_POSTS_DELETE()->name)) {
return true;
}
return false;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Providers;
use App\Observers\UserObserver;
use App\Repositories\Models\AdminUsers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register()
{
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->registerObservers(); // 注册观察者
}
protected function registerObservers(): void
{
AdminUsers::observe(UserObserver::class);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Providers\Auth;
use App\Repositories\Enums\CacheEnum;
use App\Support\Traits\Helpers;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class AdminUsersProvider implements UserProvider
{
use Helpers;
protected $model = null;
public function __construct($app, $model)
{
if(!$model instanceof \Illuminate\Database\Eloquent\Model){
$class = '\\'.ltrim($model, '\\');
$this->model = new $class;
}else{
$this->model = $model;
}
}
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
{
// 只做根据identifier的值做查询用户操作
$user = $this->model->newQuery()
->where($this->model->getAuthIdentifierName(), $identifier)
->first();
if($user->status === 0 ){
return throw new AuthenticationException('用户已被禁用,请联系管理员.');
}
return $user;
/*return Cache::remember($cacheKey, $cacheExpireTime, function () use ($identifier) {
return $this->model->newQuery()
->where($this->model->getAuthIdentifierName(), $identifier)
->first();
});*/
}
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials)) {
return throw new UnauthorizedHttpException('auth params error','参数缺少',null,ResponseCodeEnum::CLIENT_PARAMETER_ERROR);
}
$query = $this->model->newQuery();
foreach ($credentials as $key => $value) {
if (Str::contains($key, 'password')) {
$value = $this->model->encryptPassword($value);
//continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
return $query->first();
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$encodePassword = $this->model->encryptPassword($credentials['password']);
if($encodePassword !== $user->password){
return throw new AuthenticationException('用户登陆密码错误,请重新输入.');
}
if($user->status === 0 ){
return throw new AuthenticationException('用户已被禁用,请联系管理员.');
}
return true;
}
/**
* Retrieve a user by their unique identifier and "remember me" token.
*
* @param mixed $identifier
* @param string $token
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifier, $token)
{
return null;
}
/**
* Update the "remember me" token for the given user in storage.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $token
* @return void
*/
public function updateRememberToken(UserContract $user, $token)
{
}
}
<?php
namespace App\Providers\Auth\Jwt;
use Dingo\Api\Auth\Provider\Authorization;
use Exception;
use Dingo\Api\Routing\Route;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use App\Repositories\Enums\ResponseCodeEnum;
use App\Support\Traits\Helpers ;
use Tymon\JWTAuth\JWTAuth;
class AdminAuthProvider extends Authorization
{
use Helpers;
/**
* The JWTAuth instance.
*
* @var \Tymon\JWTAuth\JWTAuth
*/
protected $auth;
public function __construct(JWTAuth $auth){
$this->auth = $auth;
}
/**
* Authenticate request with a JWT.
*
* @param \Illuminate\Http\Request $request
* @param \Dingo\Api\Routing\Route $route
* @return mixed
*/
public function authenticate(Request $request, Route $route)
{
try{
$token = $this->getToken($request);
$decodeToken = $this->decodeToken($token);
if(empty($decodeToken) || (!is_array($decodeToken) && !$decodeToken['hash'])){
throw new UnauthorizedHttpException('JWTAuth', 'Unable to authenticate with invalid token.');
}
if (! $user = $this->auth->setToken($decodeToken['hash'])->authenticate()) {
throw new UnauthorizedHttpException('JWTAuth', 'Unable to authenticate with invalid token.');
}
}catch(Exception $exception){
if($exception instanceof UnauthorizedHttpException){
throw $exception;
}
throw new UnauthorizedHttpException('AdminAuth', $exception->getMessage(),null, ResponseCodeEnum::CLIENT_PARAMETER_ERROR);
}
return $user;
}
/**
* Get the JWT from the request.
*
* @param \Illuminate\Http\Request $request
* @return string
*
* @throws \Exception
*/
protected function getToken(Request $request)
{
try {
$this->validateAuthorizationHeader($request);
$token = $this->parseAuthorizationHeader($request);
} catch (Exception $exception) {
if (! $token = $request->query('token', false)) {
throw $exception;
}
}
return $token;
}
/**
* Parse JWT from the authorization header.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function parseAuthorizationHeader(Request $request)
{
return trim(str_ireplace($this->getAuthorizationMethod(), '', $request->header('authorization')));
}
/**
* Get the providers authorization method.
*
* @return string
*/
public function getAuthorizationMethod()
{
return 'bearer';
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Providers;
use App\Repositories\Enums\PermissionEnum;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
use App\Providers\Auth\AdminUsersProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* Boot the authentication services for the application.
*/
public function boot()
{
//Gate::before(PermissionEnum::gateBeforeCallback());
$this->app['auth']->provider('superadmin', function ($app, array $config) {
return new AdminUsersProvider($app, $config['model']);
});
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Providers;
use Prettus\Repository\Providers\LumenRepositoryServiceProvider;
class RepositoryServiceProvider extends LumenRepositoryServiceProvider
{
/**
* Bootstrap services.
*/
public function boot()
{
$this->app->bind(
\App\Repositories\Contracts\UsersRepository::class,
\App\Repositories\Eloquent\UsersRepositoryEloquent::class
);
$this->app->bind(
\App\Repositories\Contracts\MenusRepository::class,
\App\Repositories\Eloquent\MenusRepositoryEloquent::class
);
$this->app->bind(
\App\Repositories\Contracts\RolesRepository::class,
\App\Repositories\Eloquent\RolesRepositoryEloquent::class
);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Contracts;
use Prettus\Repository\Contracts\RepositoryInterface;
/**
* Interface UserRepository.
*/
interface MenusRepository extends RepositoryInterface
{
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Contracts;
use Prettus\Repository\Contracts\RepositoryInterface;
/**
* Interface UserRepository.
*/
interface RolesRepository extends RepositoryInterface
{
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Contracts;
use Prettus\Repository\Contracts\RepositoryInterface;
/**
* Interface UserRepository.
*/
interface UsersRepository extends RepositoryInterface
{
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Criteria;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
abstract class Criteria implements CriteriaInterface
{
/**
* @var Request
*/
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function apply($model, RepositoryInterface $repository)
{
$model = $this->before($model);
$model = $model->where(function ($query) {
/** @var Builder $query */
$this->condition($query);
});
return $this->after($model);
}
/**
* Splice the query according to the parameters in the request.
*
* @param Builder $query
*/
protected function condition(Builder $query): void
{
}
protected function before($model)
{
return $model;
}
protected function after($model)
{
return $model;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Criteria;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Prettus\Repository\Contracts\RepositoryInterface;
use Prettus\Repository\Criteria\RequestCriteria as BaseRequestCriteria;
class RequestCriteria extends BaseRequestCriteria
{
/**
* Apply criteria in query repository.
*
* @param Builder|Model $model
* @param RepositoryInterface $repository
* @return mixed
*
* @throws \Exception
*/
public function apply($model, RepositoryInterface $repository)
{
$cursor = $this->request->get(config('repository.criteria.params.cursor', 'cursor'));
if ($cursor) {
$keyType = $model->getKeyType();
$key = $model->getKeyName();
$cursor = in_array($keyType, ['int', 'integer']) ? (int) $cursor : $cursor;
$sortedBy = $this->request->get(config('repository.criteria.params.sortedBy', 'sortedBy'), 'asc');
$sortedBy = ! empty($sortedBy) ? Str::lower($sortedBy) : 'asc';
$model = ($sortedBy === 'asc') ? $model->where($key, '>', $cursor) : $model->where($key, '<', $cursor);
}
return parent::apply($model, $repository);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Criteria;
use Illuminate\Database\Eloquent\Builder;
use App\Repositories\Criteria\Criteria;
class UserCriteria extends Criteria
{
protected function condition(Builder $query): void
{
if ($name = $this->request->get('name')) {
$query->where('name', '=', $name);
}
if ($email = $this->request->get('email')) {
$query->where('email', 'like', "%$email%");
}
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Eloquent;
use App\Repositories\Presenters\Presenter;
use Illuminate\Database\Eloquent\Builder;
use Prettus\Repository\Eloquent\BaseRepository as BaseRepositoryEloquent;
abstract class BaseRepository extends BaseRepositoryEloquent
{
/**
* @var Presenter
*/
protected $presenter;
/**
* Retrieve all data of repository, simple paginated.
*
* @param null|int $limit
* @param array $columns
* @return mixed
*/
public function cursorPaginate($current, $limit = null, $columns = ['*'])
{
$this->applyCriteria();
$this->applyScope();
$limit = is_null($limit) ? config('repository.pagination.limit', 15) : (int) $limit;
$results = $this->model->select($columns)->limit($limit)->get();
if ($this->model instanceof Builder) {
$primaryKey = $this->model->getModel()->getKeyName();
} else {
$primaryKey = $this->model->getKeyName();
}
$count = $results->count();
$next = $count === $limit ? optional($results->last())->{$primaryKey} : null;
$prev = request('prev');
$this->presenter->makeCursor((int) $current, $prev ? (int) $prev : '', $next, $count);
$this->resetModel();
return $this->parserResult($results);
}
}
<?php
namespace App\Repositories\Eloquent;
use App\Repositories\Contracts\MenusRepository;
use App\Repositories\Criteria\RequestCriteria;
use App\Repositories\Models\Menus;
use App\Repositories\Eloquent\BaseRepository;
use Prettus\Validator\Contracts\ValidatorInterface;
/**
* Class UserRepositoryEloquent.
*/
class MenusRepositoryEloquent extends BaseRepository implements MenusRepository
{
protected $fieldSearchable = [
'menu_name',
];
/**
* 定义validator的检索规则
* @var \string[][]
*/
public $rules = [
ValidatorInterface::RULE_CREATE => [
'menu_name' => 'required',
'menu_path' => 'required',
'parent_id' => 'required',
'menu_type' => 'required',
'status' => 'required',
'sort' => 'required',
'is_show' => 'required',
'sys_default' => 'required'
],
ValidatorInterface::RULE_UPDATE => [
'menu_name' => 'required',
'menu_path' => 'required',
'parent_id' => 'required',
'menu_type' => 'required',
'status' => 'required',
'sort' => 'required',
'is_show' => 'required',
'sys_default' => 'required'
]
];
/**
* Specify Model class name.
*
* @return string
*/
public function model()
{
return Menus::class;
}
/**
* Boot up the repository, pushing criteria.
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
*/
public function boot()
{
$this->pushCriteria(app(RequestCriteria::class));
}
}
<?php
namespace App\Repositories\Eloquent;
use App\Repositories\Contracts\RolesRepository;
use App\Repositories\Criteria\RequestCriteria;
use App\Repositories\Eloquent\BaseRepository;
use Prettus\Validator\Contracts\ValidatorInterface;
use Spatie\Permission\Models\Role;
/**
* Class UserRepositoryEloquent.
*/
class RolesRepositoryEloquent extends BaseRepository implements RolesRepository
{
protected $fieldSearchable = [
'name',
];
/**
* 定义validator的检索规则
* @var \string[][]
*/
public $rules = [
ValidatorInterface::RULE_CREATE => [
'name' => 'required',
],
ValidatorInterface::RULE_UPDATE => [
'name' => 'required',
]
];
/**
* Specify Model class name.
*
* @return string
*/
public function model()
{
return Role::class;
}
/**
* Boot up the repository, pushing criteria.
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
*/
public function boot()
{
$this->pushCriteria(app(RequestCriteria::class));
}
/**
* @param $params
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model
*/
public function saveRole($params)
{
$saveParams = [
'name' => $params['name'],
'remark' => $params['remark'],
'guard_name' => config('auth.defaults.guard'),
];
if(isset($params['id'])){
$saveParams['updated_at'] = date('Y-m-d H:i:s',time());
//$saveParams['id'] = $params['id'];
$result = Role::query()->updateOrCreate(['id' => $params['id']],$saveParams);
}else{
$saveParams['created_at'] = date('Y-m-d H:i:s',time());
$saveParams['updated_at'] = date('Y-m-d H:i:s',time());
$result = Role::create($saveParams);
}
return $result;
}
}
<?php
namespace App\Repositories\Eloquent;
use App\Repositories\Contracts\UsersRepository;
use App\Repositories\Criteria\RequestCriteria;
use App\Repositories\Models\AdminUsers;
use App\Repositories\Validators\UserValidator;
use App\Repositories\Eloquent\BaseRepository;
use App\Support\Traits\Helpers;
use Prettus\Validator\Contracts\ValidatorInterface;
/**
* Class UserRepositoryEloquent.
*/
class UsersRepositoryEloquent extends BaseRepository implements UsersRepository
{
use Helpers;
protected $fieldSearchable = [
'username','email',
];
/**
* 定义validator的检索规则
* @var \string[][]
*/
public $rules = [
ValidatorInterface::RULE_CREATE => [
'username' => 'required|string',
'nickname' => 'required|string',
'password' => 'required|string|min:6',
'email' => 'required',
'token' => 'required',
'is_admin' => 'required',
'status' => 'required'
],
ValidatorInterface::RULE_UPDATE => [
'id' => 'required',
'nickname' => 'required|string',
'email' => 'required',
]
];
/**
* Specify Model class name.
*
* @return string
*/
public function model()
{
return AdminUsers::class;
}
/**
* Boot up the repository, pushing criteria.
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
*/
public function boot()
{
$this->pushCriteria(app(RequestCriteria::class));
}
public function createOrUpdateUser($params)
{
$saveParams = $params;
if(isset($saveParams['id']) && !empty($saveParams['id'])){
$saveParams['updated_at'] = date('Y-m-d H:i:s',time());
unset($saveParams['is_admin']);
unset($saveParams['token']);
unset($saveParams['username']);
unset($saveParams['status']);
return $this->update($saveParams,$saveParams['id']);
}else{
$saveParams['is_admin'] = 0;
$saveParams['status'] = 1;
$saveParams['token'] = $this->randomFromDevice(16);
$saveParams['password'] = $this->encryptPassword($saveParams['password']);
$saveParams['lastlogin'] = 0;
$saveParams['created_at'] = date('Y-m-d H:i:s',time());
$saveParams['updated_at'] = date('Y-m-d H:i:s',time());
return $this->create($saveParams);
}
}
public function updateUserStatus($id, $status)
{
if($id){
$saveParams = [];
$saveParams['status'] = $status;
$saveParams['id'] = $id;
$user = $this->where('id',$id)->first()->toArray();
collect($this->rules['update'])->map(function($rule,$key) use ($user,&$saveParams){
if(isset($user[$key]) && !empty($user[$key])){
$saveParams[$key] = $user[$key];
}
});
$saveParams['status'] = $status;
$saveParams['id'] = $id;
$saveParams['updated_at'] = date('Y-m-d H:i:s',time());
return $this->update($saveParams,$id);
}
return true;
}
public function deleteUser($ids)
{
$users = $this->whereIn('id',$ids)->get();
if($users){
collect($users)->map(function($user){
$user->delete();
});
}
return true;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Enums;
use Illuminate\Support\Carbon;
use Jiannei\Enum\Laravel\Repositories\Enums\CacheEnum as BaseCacheEnum;
class CacheEnum extends BaseCacheEnum
{
const AUTHORIZATION_USER = 'authorizationUser';
protected static function authorizationUser($options)
{
$exp = auth('api')->payload()->get('exp'); // token 剩余有效时间
return Carbon::now()->diffInSeconds(Carbon::createFromTimestamp($exp));
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Enums;
use Jiannei\Enum\Laravel\Repositories\Enums\LogEnum as BaseLogEnum;
class LogEnum extends BaseLogEnum
{
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Enums;
use Closure;
use Illuminate\Support\Str;
use Jiannei\Enum\Laravel\Contracts\LocalizedEnumContract;
use Jiannei\Enum\Laravel\Enum;
class PermissionEnum extends Enum implements LocalizedEnumContract
{
// 系统级别内置的权限:不允许分配权限给普通用户;(比如,只能超级管理员才能拥有)
const SYSTEM = '000000';
const SYSTEM_ACTIVITY_LOG_CLEAN = '000001';
const SYSTEM_CACHE_CLEAR = '000002';
// 路由级别权限:路由分组、路由操作;允许分配权限给普通用户,或分配给角色;ROUTE 开头
// 定义规范:viewAny、view、create、update、delete...
// https://learnku.com/docs/laravel/8.x/authorization/9382#f98610
const ROUTE = '100000';
const ROUTE_USERS = '100100';
const ROUTE_USERS_CREATE = '100110';
const ROUTE_USERS_DELETE = '100111';
const ROUTE_USERS_UPDATE = '100112';
const ROUTE_USERS_VIEW = '100113';
const ROUTE_USERS_VIEW_ANY = '100114';
const ROUTE_POSTS = '100200';
const ROUTE_POSTS_CREATE = '100210';
const ROUTE_POSTS_DELETE = '100211';
const ROUTE_POSTS_UPDATE = '100212';
const ROUTE_POSTS_VIEW = '100213';
const ROUTE_POSTS_VIEW_ANY = '100214';
// 数据级别权限:数据表权限,数据行权限,数据列权限
const DATA = '200000';
const DATA_USERS = '200100';
const DATA_USERS_ROW = '200101';
const DATA_USERS_COLUMN_EMAIL = '200110';
const DATA_POSTS = '200200';
const DATA_POSTS_ROW = '200201';
const DATA_POSTS_BODY = '200210';
public $name;
public $code;
public $type;
public $description;
public function __construct($enumValue, bool $strict = true)
{
parent::__construct($enumValue, $strict);
$this->name = Str::lower(str_replace('_', ':', $this->key));
$this->code = $enumValue;
$this->type = Str::lower(current(explode('_', $this->key)));
}
/**
* 根据常量定义构建权限.
*
* @return array
*/
public static function makePermissions(): array
{
$rawPermissions = self::getInstances();
$permissions = [];
collect($rawPermissions)->each(function ($item) use (&$permissions) {
$permissions[] = [
'name' => $item->name,
];
});
return $permissions;
}
/**
* 前置-授权拦截检查.
*
* @return Closure
*/
public static function gateBeforeCallback(): Closure
{
return function ($user, $ability) {
return $user->isSuperAdmin() ?: null;
};
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Enums;
use Jiannei\Enum\Laravel\Repositories\Enums\HttpStatusCodeEnum;
class ResponseCodeEnum extends HttpStatusCodeEnum
{
// 业务操作正确码:1xx、2xx、3xx 开头,后拼接 3 位
// 200 + 001 => 200001,也就是有 001 ~ 999 个编号可以用来表示业务成功的情况,当然你可以根据实际需求继续增加位数,但必须要求是 200 开头
// 举个栗子:你可以定义 001 ~ 099 表示系统状态;100 ~ 199 表示授权业务;200 ~ 299 表示用户业务...
const SERVICE_REGISTER_SUCCESS = 200101;
const SERVICE_LOGIN_SUCCESS = 200102;
// 客户端错误码:400 ~ 499 开头,后拼接 3 位
const CLIENT_PARAMETER_ERROR = 400001;
const CLIENT_CREATED_ERROR = 400002;
const CLIENT_DELETED_ERROR = 400003;
const CLIENT_VALIDATION_ERROR = 422001; // 表单验证错误
// 服务端操作错误码:500 ~ 599 开头,后拼接 3 位
const SYSTEM_ERROR = 500001;
const SYSTEM_UNAVAILABLE = 500002;
const SYSTEM_CACHE_CONFIG_ERROR = 500003;
const SYSTEM_CACHE_MISSED_ERROR = 500004;
const SYSTEM_CONFIG_ERROR = 500005;
// 业务操作错误码(外部服务或内部服务调用...)
const SERVICE_REGISTER_ERROR = 500101;
const SERVICE_LOGIN_ERROR = 500102;
const SERVICE_PASSWORD_UPDATE_ERROR = 500103;
const SERVICE_PASSWORD_ERROR = 500103;
const SERVICE_TOKEN_REFRESH_ERROR = 500105;
const SERVICE_USER_NOT_EXIST_ERROR = 500106;
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Enums;
use Illuminate\Support\Str;
use Jiannei\Enum\Laravel\Contracts\LocalizedEnumContract;
use Jiannei\Enum\Laravel\FlaggedEnum;
class RoleEnum extends FlaggedEnum implements LocalizedEnumContract
{
const GUEST = 1 << 0;
const ADMIN = 1 << 1;
const SUPER_ADMIN = self::GUEST | self::ADMIN;
public $name;
public function __construct($flags)
{
parent::__construct($flags);
$this->name = Str::ucfirst(Str::lower(Str::of($this->key)->replace('_', ' ')));
}
public static function makeRoles(): array
{
$rawRoles = self::getInstances();
collect($rawRoles)->each(function ($item, $key) use (&$roles) {
if ($item->value !== self::NONE) {
$roles[] = ['name' => $item->name];
}
});
return $roles;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Models;
use App\Repositories\Enums\RoleEnum;
use Database\Factories\UserFactory;
use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laravel\Lumen\Auth\Authorizable;
use Spatie\Permission\Traits\HasRoles;
use Tymon\JWTAuth\Contracts\JWTSubject;
class AdminUsers extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject
{
use Authenticatable, Authorizable, HasFactory, HasRoles;
protected $table = 'admin_users';
protected $guard_name = 'api';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'username', 'nickname', 'password', 'email', 'token', 'is_admin', 'status'
];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = [
];
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
public function isSuperAdmin() : bool
{
return $this->is_admin === 1 ? true : false;
}
/**
* 是否是非法禁止使用的用户 status == 0
* @return bool
*/
public function isUnusualUser() : bool
{
return $this->status === 0 ? true : false;
}
public function isOwnerOf(\Illuminate\Database\Eloquent\Model $model, string $key = 'user_id'): bool
{
if ($model instanceof User) {
return $this->id === $model->id;
}
return $this->id === $model->$key;
}
public function encryptPassword($hash) : string
{
$str = base64_encode($hash);
$encodePassword = md5(md5($str));
return $encodePassword;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Models;
use Jenssegers\Mongodb\Eloquent\Model;
class Log extends Model
{
protected $connection = 'mongodb';
protected $guarded = [];
protected $dates = [
'datetime',
];
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Models;
class Menus extends Model
{
protected $table = 'menus';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'menu_name','menu_path','menu_icon','parent_id','menu_type','component','status','sort','is_show','sys_default','created_by'
];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = [
];
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Models;
use App\Support\Traits\SerializeDate;
use Illuminate\Database\Eloquent\Model as EloquentModel;
abstract class Model extends EloquentModel
{
use SerializeDate;
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Presenters;
use App\Repositories\Transformers\PostTransformer;
class PostPresenter extends Presenter
{
/**
* Prepare data to present.
*
* @return \League\Fractal\TransformerAbstract
*/
public function getTransformer()
{
return new PostTransformer();
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Presenters;
use Exception;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use League\Fractal\Pagination\Cursor;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection;
use Prettus\Repository\Presenter\FractalPresenter;
abstract class Presenter extends FractalPresenter
{
protected $cursor = null;
public function makeCursor($current, $previous, $next, $count)
{
$this->cursor = new Cursor(...func_get_args());
}
/**
* Prepare data to present.
*
* @param $data
* @return mixed
*
* @throws Exception
*/
public function present($data)
{
if (! class_exists('League\Fractal\Manager')) {
throw new Exception(trans('repository::packages.league_fractal_required'));
}
if ($data instanceof EloquentCollection) {
$this->resource = $this->transformCollection($data);
} elseif ($data instanceof AbstractPaginator) {
$this->resource = $this->transformPaginator($data);
} else {
$this->resource = $this->transformItem($data);
}
return $this->fractal->createData($this->resource)->toArray();
}
/**
* @param $data
* @return \League\Fractal\Resource\Collection
*/
protected function transformCollection($data)
{
$resource = new Collection($data, $this->getTransformer(), $this->resourceKeyCollection);
if ($this->cursor) {
$resource->setCursor($this->cursor);
}
return $resource;
}
/**
* @param AbstractPaginator|LengthAwarePaginator|Paginator $paginator
* @return \League\Fractal\Resource\Collection
*/
protected function transformPaginator($paginator)
{
$collection = $paginator->getCollection();
$resource = new Collection($collection, $this->getTransformer(), $this->resourceKeyCollection);
if ($paginator instanceof Paginator) {
$items = $paginator->items();
$total = 0;
$perPage = $paginator->perPage();
$currentPage = $paginator->currentPage();
$options = array_merge(['hasMore' => $paginator->hasMorePages()], $paginator->getOptions());
$paginator = Container::getInstance()->makeWith(LengthAwarePaginator::class, compact(
'items', 'total', 'perPage', 'currentPage', 'options'
));
}
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return $resource;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Presenters;
use App\Repositories\Transformers\UserTransformer;
class UserPresenter extends Presenter
{
/**
* Prepare data to present.
*
* @return \League\Fractal\TransformerAbstract
*/
public function getTransformer()
{
return new UserTransformer();
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Transformers;
use App\Repositories\Enums\PermissionEnum;
use App\Repositories\Models\Post;
use Illuminate\Support\Str;
use League\Fractal\TransformerAbstract;
class PostTransformer extends TransformerAbstract
{
protected $defaultIncludes = [
'author',
];
public function transform(Post $post)
{
return [
'id' => $post->id,
'title' => $post->title,
'body' => $this->checkColumnPermission() ? $post->body : Str::limit($post->body, 120), // 没有文章详情查看权限时,返回截取的部分内容
'published' => $post->published,
];
}
protected function checkColumnPermission()
{
return auth('api')->user()->can(PermissionEnum::ROUTE_POSTS_VIEW()->name);
}
public function includeAuthor(Post $post)
{
$author = $post->author;
return $this->item($author, new UserTransformer());
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Transformers;
use App\Repositories\Enums\PermissionEnum;
use App\Repositories\Models\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
$data = [
'id' => $user->id,
'nickname' => $user->name,
'email' => $user->email,
];
if (! $this->checkColumnPermission()) {
$data['email'] = '**** ****';
}
return $data;
}
protected function checkColumnPermission()
{
return auth('api')->user()->can(PermissionEnum::DATA_USERS_COLUMN_EMAIL()->name);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Repositories\Validators;
use Prettus\Validator\Contracts\ValidatorInterface;
use Prettus\Validator\LaravelValidator;
class UserValidator extends LaravelValidator
{
protected $rules = [
ValidatorInterface::RULE_CREATE => [
'username' => 'required|string|max:100',
'email' => 'required|email|unique:users,email',
'password' => 'required|min:8',
],
ValidatorInterface::RULE_UPDATE => [
'password' => 'required|min:8',
],
];
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Services;
use App\Repositories\Contracts\UsersRepository;
use App\Repositories\Criteria\UserCriteria;
use App\Repositories\Presenters\UserPresenter;
use App\Repositories\Enums\ResponseCodeEnum;
use App\Support\Traits\Helpers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Spatie\Permission\Models\Role;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use App\Repositories\Models\AdminUsers;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class AuthService
{
use Helpers;
protected $userRepository = null;
public function __construct(UsersRepository $usersRepository)
{
$this->userRepository = $usersRepository;
}
/**
* @param Request $request
*/
public function login(Request $request)
{
$username = $request->input('username');
$password = $request->input('password');
$requestCriteria = [
'username' => $username,
'password' => $password
];
try{
if( !$user = auth()->attempt($requestCriteria) ){
throw new UnauthorizedHttpException('authroization error','用户登陆授权失败,用户名或密码错误',null,ResponseCodeEnum::CLIENT_PARAMETER_ERROR);
}
return $this->generateToken($user);
}catch(\Exception $exception){
throw new UnauthorizedHttpException('authroization error',$exception->getMessage(),null,ResponseCodeEnum::SERVICE_LOGIN_ERROR);
}
}
/**
* 获取登陆用户信息
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function getUserInfo()
{
$user = auth()->user();
//除去敏感信息
if(isset($user['password'])){
unset($user['password']);
}
if(isset($user['token'])){
unset($user['token']);
}
return $user;
}
/**
* 用户列表
* @param $request
* @return array
*/
public function listByPage($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
if(!$params['page_size'] || $params['page_size'] == 0){
$params['page_size'] = 10;
}
$users = $this->userRepository->paginate($params['page_size'],['id','username','nickname','email','token','is_admin','status','lastlogin','created_at','updated_at']);
$returnRecord = [];
if($users){
$allItems = $users->items();
$allItems = collect($allItems)->map(function($item){
$rolesName = [];
$roles = collect($item->roles->toArray())->map(function($role) use (&$rolesName){
$rolesName[] = $role['name'];
unset($role['pivot']);
return $role;
});
$item = $item->toArray();
unset($item['roles']);
$item['roles'] = $roles->toArray();
$item['type'] = $item['is_admin'];
$item['belongTo'] = implode(',',$rolesName);
return $item;
});
// 获取当前角色的所有权限
$returnRecord['data'] = $this->formatKeysfromArray($allItems);
$returnRecord['current_page'] = $users->currentPage();
$returnRecord['total'] = $users->total();
}
return $returnRecord;
}
/**
* 用户登出
* @param Request $request
*/
public function logout(Request $request)
{
auth()->logout();
}
/**
* 刷新token
* @return mixed
* @throws TokenInvalidException
*/
public function refreshToken()
{
$token = auth()->refresh();
if(!$token){
throw new TokenInvalidException('token刷新失败',ResponseCodeEnum::SERVICE_TOKEN_REFRESH_ERROR);
}
return $this->generateToken($token);
}
/**
* 更新或创建新用户
* @param $request
* @return bool
*/
public function saveOrUpdateUser($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
$rule = $this->userRepository->rules['create'];
$filterRuleParams = [];
if(isset($params['id']) && !empty($params['id'])){
$rule = $this->userRepository->rules['update'];
$filterRuleParams['id'] = $params['id'];
}
//整理请求过来的数据参数
collect($rule)->map(function($r,$key) use ($params, &$filterRuleParams){
if(isset($params[$key]) && !empty($params[$key])){
$filterRuleParams[$key] = $params[$key];
}else{
$filterRuleParams[$key] = '';
}
});
if($user = $this->userRepository->createOrUpdateUser($filterRuleParams)){
// 如果编辑用户时选择了所属角色
if(isset($params['role_ids']) && !empty($params['role_ids'])){
$ids = explode(',',$params['role_ids']);
$roles = Role::query()->whereIn('id',$ids)->get();
if(!$roles){
throw new \LogicException('角色不存在');
}
$belongRoles = $user->roles;
//角色
if(count($belongRoles->toArray()) > 0){
collect($belongRoles)->map(function($role) use ($user){
$user->removeRole($role);
$permissions = $role->permissions;
collect($permissions)->map(function($permission) use ($role){
$role->revokePermissionTo($permission);
});
});
}
collect($roles)->map(function($role) use ($user){
$user->assignRole($role);
/*echo "<pre>";
print_R($role->permissions->toArray());
exit;*/
});
}else{
$roles = $user->roles;
if(count($roles->toArray()) > 0){
collect($roles)->map(function($role) use ($user){
$user->removeRole($role);
});
}
}
}
return true;
}
/**
* 更新用户状态
* @param $request
* @param $id
*/
public function updateUserStatus($request, $id)
{
$status = 0;
if(Str::contains(strtolower((string) $request->getPathInfo()), 'disable')){
$status = 0;
}elseif(Str::contains(strtolower((string) $request->getPathInfo()), 'enable')){
$status = 1;
}
$this->userRepository->updateUserStatus($id,$status);
}
/**
* 物理删除用户
* @param $request
*/
public function deleteUser($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
$ids = explode(',',$params['ids']);
$users = $this->userRepository->whereIn('id',$ids)->get();
if(count($users->toArray()) != count($ids)){
throw new \LogicException('部分用户不存在');
}
if($this->userRepository->deleteUser($ids)){
//将用户的角色收回
collect($users)->map(function($user){
$roles = $user->roles;
collect($roles)->map(function($role) use ($user){
$user->removeRole($role);
});
});
}
}
/**
* 重置密码
* @param Request $request
* @return bool
*/
public function resetPassword(Request $request)
{
// 判断旧密码是否正确
$oldPassword = $request->input('oldpassword');
$newPassword = $request->input('newpassword');
$adminModel = app(AdminUsers::class);
$user = $this->userRepository->where('id',auth()->user()->id)->first();
if(!$user){
throw new UnauthorizedHttpException('user not exist. ','不存在该用户',null,ResponseCodeEnum::SERVICE_USER_NOT_EXIST_ERROR);
}
$encodeOldPassword = $adminModel->encryptPassword($oldPassword);
if($encodeOldPassword != $user->password){
throw new UnauthorizedHttpException('password is error. ','密码更新失败:旧密码不正确',null,ResponseCodeEnum::SERVICE_PASSWORD_ERROR);
}
$user->password = $adminModel->encryptPassword($newPassword);
if($encodeOldPassword == $user->password){
throw new UnauthorizedHttpException('password is error. ','新密码不能和旧密码一致',null,ResponseCodeEnum::SERVICE_PASSWORD_ERROR);
}
// 根据role规则重组 user
$updateUser = collect($this->userRepository->rules['update'])->map(function($rule,$key) use ($user){
return $user[$key];
});
$result = $this->userRepository->update($updateUser->toArray(),$user->id);
return true;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Services;
use App\Repositories\Enums\ResponseCodeEnum;
use App\Support\Traits\Helpers;
use App\Repositories\Contracts\MenusRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Spatie\Permission\Models\Role;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class MenusService
{
use Helpers;
protected $menuRepository = null;
public function __construct(MenusRepository $menuRepository)
{
$this->menuRepository = $menuRepository;
}
}
<?php /*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Services;
use App\Repositories\Models\Menus;
use App\Support\Traits\Helpers;
use Illuminate\Http\Request;
use Spatie\Permission\Exceptions\RoleDoesNotExist;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use App\Repositories\Contracts\MenusRepository;
use App\Repositories\Contracts\RolesRepository;
class RoleAndPermissionsService
{
use Helpers;
protected $user = null;
protected $menusRepository = null;
public function __construct(MenusRepository $menusRepository)
{
$this->user = auth()->user();
$this->menusRepository = $menusRepository;
}
/**
* 获取管理后台用户的所在的用户组
*/
public function getRolesToUser($user = null){
if($user == null){
$user = $this->user;
}
$this->getAllPermissions($user);
$role = $user->roles;
return $role;
}
public function getUserPermissions(){
$role = $this->getAllPermissions($this->user);
}
protected function getAllPermissions($user, $parseUserRole = true)
{
$role = $user->getpermissionsviaroles();
if($parseUserRole){
$newRoles = collect($user['roles'])->map(function($role) {
$roleArray = $role->toArray();
unset($roleArray['pivot']);
unset($roleArray['guard_name']);
$newPermission = collect($roleArray['permissions'])->map(function ($permission) {
// 隐藏系统默认权限
if ($permission['menu_id'] > 0) {
unset($permission['pivot']);
unset($permission['guard_name']);
//$newPermission[] = $permission;
return $permission;
}
});
$roleArray['permissions'] = array_values(array_filter($newPermission->toArray()));
return $roleArray;
});
unset($user['roles']);
$user['roles'] = $newRoles->toArray();
//将权限赋予当前用户
auth()->setUser($user);
}
return $role;
}
/**
* 获取系统默认级别的按钮权限类型
* @return array|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
*/
public function getButtonDefaultPermission()
{
$allPermission = Permission::query()->where(['menu_id' => 0, 'sys_default' => 1,'guard_name' => config('auth.defaults.guard')])->get();
if($allPermission){
collect($allPermission)->map(function($permission){
$permission->title = $permission->name;
});
$allPermission = $this->formatKeysfromArray($allPermission);
}
return $allPermission;
}
/**
* 获取用户授权菜单列表
* @return array
*/
public function getUserRoleMenus()
{
$permissions = $this->getAllPermissions($this->user);
$menuIds = collect($permissions)->map(function($permission){
if($permission instanceof \Spatie\Permission\Models\Permission){
$permission = $permission->toArray();
}
return $permission['menu_id'];
});
$menuModel = app(Menus::class);
$menuList = $menuModel
->whereIn($menuModel->getKeyName(),$menuIds)
->where('status',1)->get()->toArray();
$menuList = collect($menuList)->map(function($menu){
$menu['showAlways'] = true;
return $menu;
});
return empty($menuList) || NULL ? [] : $menuList;
}
/**
* 获取所有菜单列表
* @return array
* @throws \Psr\Container\ContainerExceptionInterface
*
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function getAllMenus()
{
$menuModel = app(Menus::class);
$allMenus = $menuModel->get();
// key_name转换
$parent_id = [];
$menusList = collect($allMenus)->map(function($menu) use (&$parent_id){
$newMenu = [
'name' => $menu->menu_name,
'title' => $menu->menu_name,
'path' => $menu->menu_path,
'icon' => $menu->menu_icon,
'expand' => true,
'checked' => false
];
if($menu->parent_id > 0 && $menu->menu_type != 'top'){
$parent_id[] = $menu->parent_id;
}
$menuArr = $menu->toArray();
unset($menuArr['menu_name']);
unset($menuArr['menu_path']);
unset($menuArr['menu_icon']);
return array_merge($newMenu,$menuArr);
});
$parentMenu = $menuModel->whereIn('id',$parent_id)->get();
$menusList = collect($menusList)->map(function($menu) use ($parentMenu){
$parent_id = $menu['parent_id'];
if($parent_id > 0){
$parentName = collect($parentMenu)->map(function($m) use (&$menu,$parent_id){
if($m->id == $parent_id){
$menu['parent_title'] = $m->menu_name;
}
});
}
return $menu;
});
return $menusList->toArray();
}
/**
* 获取菜单结构树
* level : all (全部) top (只显示顶部) page (只显示2级,不包括button级)
* @param $menus
* @return false|mixed
*/
public function getMenusTree($menus, $level = 'all')
{
$treeMenus = [];
collect($menus)->map(function($menu) use (&$treeMenus, $level){
if($menu['parent_id'] == -1 && 'top' == $menu['menu_type']){
$treeMenus[$menu['id']] = $menu;
$treeMenus[$menu['id']]['level'] = 0;
}else{
if('top' == $level){
return ;
}elseif('page' == $level){
if('page' == $menu['menu_type']) {
if($menu['parent_id'] == 0) {
$treeMenus[$menu['id']] = $menu;
$treeMenus[$menu['id']]['level'] = 1;
}else{
$treeMenus = $this->getChildrenTree($treeMenus,$menu);
}
}
}else{
if($menu['parent_id'] == 0) {
$treeMenus[$menu['id']] = $menu;
$treeMenus[$menu['id']]['level'] = 1;
}else{
$treeMenus = $this->getChildrenTree($treeMenus,$menu);
}
}
}
});
return array_values($treeMenus);
}
/**
* 递归树形结构菜单
* @param $treeMenus
* @param $currMenu
* @return array
*/
private function getChildrenTree($treeMenus,$currMenu) : array
{
$returnTreeMenus = collect($treeMenus)->map(function($tMenus) use ($currMenu){
if(!isset($tMenus['children']) || empty($tMenus['children'])){
if($currMenu['parent_id'] == $tMenus['id']){
$currMenu['level'] = (int)$tMenus['level'] + 1;
$tMenus['children'][] = $currMenu;
}
} // 如果有children的话,进行递归循环
else{
if($currMenu['parent_id'] == $tMenus['id']){
$currMenu['level'] = (int)$tMenus['level'] + 1;
$tMenus['children'][] = $currMenu;
}else{
$tMenus['children'] = $this->getChildrenTree($tMenus['children'],$currMenu);
}
}
return $tMenus;
});
return $returnTreeMenus->toArray();
}
/**
* 编辑菜单
* @param Request $request
* @return mixed
*/
public function editMenu(Request $request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
$menuModel = app(Menus::class);
$menu = $menuModel->where('id',$params['id'])->first();
if(!$menu){
throw new \LogicException('此菜单不存在!',500);
}
$parentMenu = $menuModel->where('id',$params['parent_id'])->first();
if(!$parentMenu){
throw new \LogicException('上级菜单不存在!',500);
}
if(!in_array($params['menu_type'],['top','page','button'])){
throw new \LogicException('菜单类型不正确!',500);
}
$params['menu_name'] = $params['title'];
$params['menu_path'] = $params['path'];
$params['menu_icon'] = $params['icon'];
// 根据role规则重组 user
$updateMenus = [];
collect($this->menusRepository->rules['update'])->map(function($rule,$key) use ($params, &$updateMenus){
if($key != 'id'){
$updateMenus[$key] = $params[$key];
}
});
$updateMenus['created_by'] = $this->user->id;
$result = $this->menusRepository->update($updateMenus,$params['id']);
return $result;
}
/**
* 添加子菜单
* @param $request
* @return bool
*/
public function addSubMenu($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
$menuModel = app(Menus::class);
$parentMenu = $menuModel->where('id',$params['parent_id'])->first();
if(!$parentMenu){
throw new \LogicException('上级菜单不存在!',500);
}
if('button' == $params['type']){
$permission = Permission::query()->where(['menu_id' => 0, 'sys_default' => 1,'guard_name' => config('auth.defaults.guard'), 'action' => $params['button_type']])->first();
if(!$permission){
throw new \LogicException('按钮动作不存在!',500);
}
}
$filterParams = [];
collect($this->menusRepository->rules['create'])->map(function($rule,$key) use ($params, &$filterParams){
if(isset($params[$key]) && !empty($params[$key])){
$filterParams[$key] = $params[$key];
} else{
$filterParams[$key] = '';
}
});
$filterParams['created_by'] = $this->user->id;
$filterParams['menu_icon'] = $params['icon'];
$filterParams['component'] = $params['component'];
$filterParams['status'] = intval($params['status']);
$filterParams = collect($filterParams)->map(function($value,$key) use ($params){
if($value == ''){
switch($key){
case 'menu_name' : $value = $params['title']; break;
case 'menu_path' : $value = $params['path']; break;
case 'menu_icon' : $value = $params['icon']; break;
case 'menu_type' : $value = $params['type']; break;
case 'is_show' : $value = 1; break;
case 'sys_default' : $value = 0; break;
}
}
return $value;
});
$result = $this->menusRepository->create($filterParams->toArray());
if($result){
$permission = [];
$permission['name'] = $result['menu_name'];
$permission['menu_id'] = $result['id'];
$permission['action'] = $params['button_type'];
$permission['permission_type'] = 'menu';
$permission['sys_default'] = 0;
$permission['remark'] = $result['menu_name'].'的相关权限';
$permission['guard_name'] = config('auth.defaults.guard');
//增加对应的按钮权限
$permissionResult = Permission::create($permission);
// 如果是管理员,自动绑定赋予权限
if($this->user->isSuperAdmin()){
if($permissionResult){
$this->assignRoleAndPermissionToUser($permissionResult,$this->user);
}
}
}
return true;
}
public function deleteMenus($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
$menuModel = app(Menus::class);
$menu_ids = explode(',',$params['ids']);
if(!$menus = $menuModel->whereIn('id',$menu_ids)->get()){
throw new \LogicException('选中的菜单不存在!');
}
//判断该菜单下面是否有其他子菜单
/*if($parentMenus = $menuModel->whereIn('parent_id',$menu_ids)->get()){
throw new \LogicException('选中的菜单中有子菜单存在,无法删除该菜单。');
}*/
//判断选中的菜单是否已被赋予权限给到用户组
//查询该菜单对应的权限数据
$menuPermission = Permission::query()->has->whereIn('menu_id',$menu_ids)->get();
Permission::
if($menuPermission){
$allRole = Role::all();
if($allRole){
collect($allRole)->map(function($role) use ($menuPermission){
$result = $role->permissions();
});
}
}
}
/**
* 同步指定权限给指定用户
* @param $permission
* @param $user
*/
public function assignRoleAndPermissionToUser($permission, $user)
{
$roles = $this->getRolesToUser($user);
if($roles == null){
throw new \LogicException('该用户没有所属用户组,请手动进行分组在分配权限!');
}
$roleArr = collect($roles)->map(function($role){
return $role['id'];
});
// 获取当前用户的组信息
$allRoles = Role::query()->whereIn( 'id' , $roleArr->toArray())->get();
if($allRoles){
collect($allRoles)->map(function($role) use ($permission,$user){
$role->givePermissionTo($permission);
$user->givePermissionTo($permission);
});
}
}
/**
* 获取所有角色并获取对应的所有权限(用于列表展示)
* @param $request
* @return array
*/
public function getAllRolesToPage($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
if(!$params['page_size'] || $params['page_size'] == 0){
$params['page_size'] = 10;
}
//$record = app(RolesRepository::class)->cursorPaginate($params['page_number'],$params['page_size'],['name,remark']);
$roles = app(RolesRepository::class)->paginate($params['page_size'],['id','name','remark','created_at','updated_at']);
if($roles){
$rolePermission = [];
$items = collect($roles->items())->map(function($role) use (&$rolePermission){
$permissions = $role->permissions->toArray();
$permissions = collect($permissions)->map(function($permission){
if($permission['sys_default'] == 1 && $permission['menu_id'] == 0){
unset($permission);
}else{
unset($permission['pivot']);
$permission['created_at'] = date('Y-m-d H:i:s',strtotime($permission['created_at']));
$permission['updated_at'] = date('Y-m-d H:i:s',strtotime($permission['updated_at']));
return $permission;
}
});
$rolePermission[$role->id] = array_values(array_filter($permissions->toArray()));
return $role;
});
$allItems = collect($items)->map(function($item) use ($rolePermission){
$itemArr = $item->toArray();
unset($itemArr['permissions']);
$itemArr['permissions'] = $rolePermission[$item['id']];
return $itemArr;
});
// 获取当前角色的所有权限
$returnRecord['data'] = $this->formatKeysfromArray($allItems);
$returnRecord['current_page'] = $roles->currentPage();
$returnRecord['total'] = $roles->total();
}
return $returnRecord;
}
/**
* 仅获取所有角色列表
* @param $request
* @return array
*/
public function getAllRolesList($request)
{
$roles = Role::all();
return $roles->toArray();
}
/**
* 保存用户组
* @param $request
* @return bool
*/
public function saveRole($request){
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
try{
$role = Role::query()->where('name', $params['name'])->first();
if(!empty($role)) {
$role = $role->toArray();
}
if(!isset($params['id']) || empty($params['id'])){
if(!empty($role)){
throw new \LogicException('角色名称不能重复',500);
}
}else{
if(!empty($role) && $role['id'] != $params['id']){
throw new \LogicException('角色名称不能重复',500);
}
}
}catch(\Exception $exception){
if (!$exception instanceof RoleDoesNotExist){
throw $exception;
}
}
app(RolesRepository::class)->saveRole($params);
return true;
}
/**
* 分配权限给相应角色
* @param $request
* @return bool
*/
public function syncPermissionToRole($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
//判断该角色是否存在
$role = Role::findById($params['role_id']);
if(!$role){
throw new \LogicException('角色不存在');
}
if(isset($params['perm_ids']) && !empty($params['perm_ids'])){
$permisson_ids = explode(',',$params['perm_ids']);
$permission = Permission::query()->whereIn('menu_id',$permisson_ids)
->where(['guard_name' => config('auth.defaults.guard'),'sys_default' => 0])->get();
if(count($permission) != count($permisson_ids)){
throw new \LogicException('部分权限不存在');
}
if(count($role->permissions->toArray()) > 0){
//先作废原先的权限
$role->revokePermissionTo($role->permissions);
}
$role->givePermissionTo($permission);
}else{
$role->revokePermissionTo($role->permissions);
}
return true;
}
/**
* 删除角色
* @param $request
*/
public function deleteRoleByIds($request)
{
$params = $this->formatKeysfromArray($request->all(),'toUnderScore');
//判断该角色是否存在
$roles = Role::query()->whereIn('id',explode(',',$params['ids']))->get();
if(!$roles){
throw new \LogicException('角色不存在');
}
collect($roles)->map(function($role){
if(count($role->permissions->toArray()) > 0){
$role->revokePermissionTo($role->permissions);
}
$role->delete();
});
}
}
<?php
namespace App\Support\Traits;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
trait Helpers
{
// 取随机码,用于生成session
protected function randomFromDevice($len)
{
$fp = @fopen('/dev/urandom', 'rb');
$result = '';
if ($fp !== FALSE) {
$result .= @fread($fp, $len);
@fclose($fp);
}
else
{
trigger_error('Can not open /dev/urandom.');
}
// convert from binary to string
$result = base64_encode($result);
// remove none url chars
$result = strtr($result, '+/', '-_');
return substr($result, 0, $len);
}
protected function generateToken($hashToken)
{
$expireAt = time() + 3600 * 24;
$appToken = env('JWT_SECRET');
$str = base64_encode(json_encode(['expire'=>$expireAt, 'hash'=>$hashToken])) ;
$sign = md5($str . $appToken);
return 'bearer'.base64_encode($sign . $str);
}
protected function decodeToken($token)
{
$str = base64_decode($token);
$sign = substr($str, 0, 32);
$dataStr = substr($str, 32);
$appToken = env('JWT_SECRET');
if($sign != md5($dataStr . $appToken))
{
throw new UnauthorizedHttpException("sign error");
}
$dataJson = base64_decode($dataStr);
$data = json_decode($dataJson, 1);
if($data['expire'] < time())
{
throw new UnauthorizedHttpException("token expired","身份验证失效,请重新登录");
}
return $data;
}
public static function print($arr){
echo "<pre>";
print_R($arr);
}
//驼峰命名转下划线命名
protected function toUnderScore($str)
{
$dstr = preg_replace_callback('/([A-Z]+)/',function($matchs)
{
return '_'.strtolower($matchs[0]);
},$str);
return trim(preg_replace('/_{2,}/','_',$dstr),'_');
}
//下划线命名到驼峰命名
protected function toCamelCase($str)
{
$array = explode('_', $str);
$result = $array[0];
$len=count($array);
if($len>1)
{
for($i=1;$i<$len;$i++)
{
$result.= ucfirst($array[$i]);
}
}
return $result;
}
/**
* 将多维数组中的key转换类型
* toCamelCase : 将下划线转换为驼峰格式 toUnderScore : 将驼峰转换为下划线分割
* @param $arr
* @param string $type
* @return array
*/
public function formatKeysfromArray($arr, $type = 'toCamelCase'){
if(!is_array($arr)){
$arr = $arr->toArray();
}
$newArr = [];
collect($arr)->map(function($value, $key) use ( &$newArr, $type){
if(!is_array($value)){
switch($type) {
case 'toCamelCase' :
$newKey = $this->toCamelCase($key);
break;
case 'toUnderScore' :
$newKey = $this->toUnderScore($key);
break;
default:
$newKey = $this->toCamelCase($key);
}
$newArr[$newKey] = $value;
}else{
$val = $this->formatKeysfromArray($value,$type);
$newArr[$key] = $val;
}
});
return $newArr;
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Support\Traits;
use Carbon\Carbon;
use DateTimeInterface;
trait SerializeDate
{
/**
* 为数组 / JSON 序列化准备日期。(Laravel 7).
*
* @param \DateTimeInterface $date
* @return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->format($this->dateFormat ?: Carbon::DEFAULT_TO_STRING_FORMAT);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
#!/usr/bin/env php
<?php
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| First we need to get an application instance. This creates an instance
| of the application / container and bootstraps the application so it
| is ready to receive HTTP / Console requests from the environment.
|
*/
$app = require __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(
'Illuminate\Contracts\Console\Kernel'
);
exit($kernel->handle(new ArgvInput, new ConsoleOutput));
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
require_once __DIR__.'/../vendor/autoload.php';
(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
dirname(__DIR__)
))->bootstrap();
date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| Here we will load the environment and create the application instance
| that serves as the central piece of this framework. We'll use this
| application as an "IoC" container and router for this framework.
|
*/
$app = new Laravel\Lumen\Application(
dirname(__DIR__)
);
$app->withFacades();
//$app->register(Jenssegers\Mongodb\MongodbServiceProvider::class);
$app->withEloquent();
/*
|--------------------------------------------------------------------------
| Register Container Bindings
|--------------------------------------------------------------------------
|
| Now we will register a few bindings in the service container. We will
| register the exception handler and the console kernel. You may add
| your own bindings here if you like or you can make another file.
|
*/
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
/*
|--------------------------------------------------------------------------
| Register Config Files
|--------------------------------------------------------------------------
|
| Now we will register the "app" configuration file. If the file exists in
| your configuration directory it will be loaded; otherwise, we'll load
| the default version. You may register other files below as needed.
|
*/
$app->configure('app');
$app->configure('auth');
$app->configure('broadcasting');
$app->configure('cache');
$app->configure('database');
$app->configure('filesystems');
$app->configure('logging');
$app->configure('queue');
$app->configure('services');
$app->configure('view');
$app->configure('repository');
$app->configure('enum');
$app->configure('permission');
$app->configure('response');
$app->alias('cache', \Illuminate\Cache\CacheManager::class);
/*
|--------------------------------------------------------------------------
| Register Middleware
|--------------------------------------------------------------------------
|
| Next, we will register the middleware with the application. These can
| be global middleware that run before and after each request into a
| route or middleware that'll be assigned to some specific routes.
|
*/
$app->middleware([
\Jiannei\Logger\Laravel\Http\Middleware\RequestLog::class,
\Jiannei\Response\Laravel\Http\Middleware\Etag::class,
]);
$app->routeMiddleware([
'superadmin' => App\Http\Middleware\Authenticate::class,
//'userRole' => App\Http\Middleware\UserPermissionMiddleware::class,
//'enum' => \Jiannei\Enum\Laravel\Http\Middleware\TransformEnums::class,
//'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
//'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
'throttle' => \Jiannei\Response\Laravel\Http\Middleware\ThrottleRequests::class,
]);
/*
|--------------------------------------------------------------------------
| Register Service Providers
|--------------------------------------------------------------------------
|
| Here we will register all of the application's service providers which
| are used to bind services into the container. Service providers are
| totally optional, so you are not required to uncomment this line.
|
*/
/*
* Application Service Providers...
*/
$app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\AuthServiceProvider::class);
$app->register(App\Providers\RepositoryServiceProvider::class);
//$app->register(App\Providers\EventServiceProvider::class);
/*
* Package Service Providers...
*/
$app->register(\Tymon\JWTAuth\Providers\LumenServiceProvider::class);
$app->register(\Illuminate\Redis\RedisServiceProvider::class);
//$app->register(\Spatie\Permission\PermissionServiceProvider::class);
$app->register(Dingo\Api\Provider\LumenServiceProvider::class);
$app->register(\Jiannei\Enum\Laravel\Providers\LumenServiceProvider::class);
$app->register(\Jiannei\Response\Laravel\Providers\LumenServiceProvider::class);
$app->register(\Jiannei\Logger\Laravel\Providers\ServiceProvider::class);
/*
|--------------------------------------------------------------------------
| Load The Application Routes
|--------------------------------------------------------------------------
|
| Next we will include the routes file so that they can all be added to
| the application. This will provide all of the URLs the application
| can respond to, as well as the controllers that may handle them.
|
*/
$app->router->group([
'namespace' => 'App\Http\Controllers',
], function ($app) {
//require __DIR__.'/../routes/web.php';
require_once __DIR__.'/route.php';
});
return $app;
<?php
/*$app->router->group(['namespace' => 'WechatBundle\Http\Controllers'], function ($app) {
require __DIR__.'/../routes/web.php';
});*/
//$api = app('api.router');
$api = app('Dingo\Api\Routing\Router');
require __DIR__.'/../routes/api/auth.php';
return $app;
{
"name": "laravel/lumen",
"description": "The Laravel Lumen Framework.",
"keywords": ["framework", "laravel", "lumen"],
"license": "MIT",
"type": "project",
"require": {
"php": "^7.3|^8.0",
"ext-json": "*",
"api-ecosystem-for-laravel/dingo-api": "^3.1",
"fruitcake/laravel-cors": "^3.0",
"guzzlehttp/guzzle": "^7.3",
"illuminate/redis": "^8.34",
"jiannei/laravel-enum": "^3.0",
"jiannei/laravel-logger": "^1.2",
"jiannei/laravel-response": "^4.0",
"laravel/lumen-framework": "^8.0",
"league/fractal": "^0.19.2",
"prettus/l5-repository": "^2.7",
"spatie/laravel-permission": "^5.1",
"tymon/jwt-auth": "^1.0.2"
},
"require-dev": {
"fzaninotto/faker": "^1.9.1",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^9.3"
},
"autoload": {
"files": [
"app/Support/helpers.php"
],
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
]
}
}
<?php
return [
/*
|--------------------------------------------------------------------------
| Standards Tree
|--------------------------------------------------------------------------
|
| Versioning an API with Dingo revolves around content negotiation and
| custom MIME types. A custom type will belong to one of three
| standards trees, the Vendor tree (vnd), the Personal tree
| (prs), and the Unregistered tree (x).
|
| By default the Unregistered tree (x) is used, however, should you wish
| to you can register your type with the IANA. For more details:
| https://tools.ietf.org/html/rfc6838
|
*/
'standardsTree' => env('API_STANDARDS_TREE', 'x'),
/*
|--------------------------------------------------------------------------
| API Subtype
|--------------------------------------------------------------------------
|
| Your subtype will follow the standards tree you use when used in the
| "Accept" header to negotiate the content type and version.
|
| For example: Accept: application/x.SUBTYPE.v1+json
|
*/
'subtype' => env('API_SUBTYPE', ''),
/*
|--------------------------------------------------------------------------
| Default API Version
|--------------------------------------------------------------------------
|
| This is the default version when strict mode is disabled and your API
| is accessed via a web browser. It's also used as the default version
| when generating your APIs documentation.
|
*/
'version' => env('API_VERSION', 'v1'),
/*
|--------------------------------------------------------------------------
| Default API Prefix
|--------------------------------------------------------------------------
|
| A default prefix to use for your API routes so you don't have to
| specify it for each group.
|
*/
'prefix' => env('API_PREFIX', null),
/*
|--------------------------------------------------------------------------
| Default API Domain
|--------------------------------------------------------------------------
|
| A default domain to use for your API routes so you don't have to
| specify it for each group.
|
*/
'domain' => env('API_DOMAIN', null),
/*
|--------------------------------------------------------------------------
| Name
|--------------------------------------------------------------------------
|
| When documenting your API using the API Blueprint syntax you can
| configure a default name to avoid having to manually specify
| one when using the command.
|
*/
'name' => env('API_NAME', null),
/*
|--------------------------------------------------------------------------
| Conditional Requests
|--------------------------------------------------------------------------
|
| Globally enable conditional requests so that an ETag header is added to
| any successful response. Subsequent requests will perform a check and
| will return a 304 Not Modified. This can also be enabled or disabled
| on certain groups or routes.
|
*/
'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true),
/*
|--------------------------------------------------------------------------
| Strict Mode
|--------------------------------------------------------------------------
|
| Enabling strict mode will require clients to send a valid Accept header
| with every request. This also voids the default API version, meaning
| your API will not be browsable via a web browser.
|
*/
'strict' => env('API_STRICT', false),
/*
|--------------------------------------------------------------------------
| Debug Mode
|--------------------------------------------------------------------------
|
| Enabling debug mode will result in error responses caused by thrown
| exceptions to have a "debug" key that will be populated with
| more detailed information on the exception.
|
*/
'debug' => env('API_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Generic Error Format
|--------------------------------------------------------------------------
|
| When some HTTP exceptions are not caught and dealt with the API will
| generate a generic error response in the format provided. Any
| keys that aren't replaced with corresponding values will be
| removed from the final response.
|
*/
'errorFormat' => [
'error' => [
'message' => ':message',
'errors' => ':errors',
'code' => ':code',
'status_code' => ':status_code',
'debug' => ':debug',
],
],
/*
|--------------------------------------------------------------------------
| API Middleware
|--------------------------------------------------------------------------
|
| Middleware that will be applied globally to all API requests.
|
*/
'middleware' => [
],
/*
|--------------------------------------------------------------------------
| Authentication Providers
|--------------------------------------------------------------------------
|
| The authentication providers that should be used when attempting to
| authenticate an incoming API request.
|
*/
'auth' => [
'jwt' => App\Providers\Auth\Jwt\AdminAuthProvider::class,
],
/*
|--------------------------------------------------------------------------
| Throttling / Rate Limiting
|--------------------------------------------------------------------------
|
| Consumers of your API can be limited to the amount of requests they can
| make. You can create your own throttles or simply change the default
| throttles.
|
*/
'throttling' => [
],
/*
|--------------------------------------------------------------------------
| Response Transformer
|--------------------------------------------------------------------------
|
| Responses can be transformed so that they are easier to format. By
| default a Fractal transformer will be used to transform any
| responses prior to formatting. You can easily replace
| this with your own transformer.
|
*/
'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class),
/*
|--------------------------------------------------------------------------
| Response Formats
|--------------------------------------------------------------------------
|
| Responses can be returned in multiple formats by registering different
| response formatters. You can also customize an existing response
| formatter.
|
*/
'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'),
'formats' => [
'json' => Dingo\Api\Http\Response\Format\Json::class,
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Lumen'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => env('APP_TIMEZONE', 'UTC'),
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => env('APP_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'api'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "token"
|
*/
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users', // 与下面的 providers 中的 users 是对应的
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'superadmin',
'model' => \App\Repositories\Models\AdminUsers::class,
],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| Here you may set the options for resetting passwords including the view
| that is your password reset e-mail. You may also set the name of the
| table that maintains all of the reset tokens for your application.
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'encrypted' => true,
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('BROADCAST_REDIS_CONNECTION', 'default'),
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
| Supported: "apc", "array", "database", "file", "memcached", "redis"
|
*/
'default' => env('CACHE_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [
'driver' => 'array',
],
'database' => [
'driver' => 'database',
'table' => env('CACHE_DATABASE_TABLE', 'cache'),
'connection' => env('CACHE_DATABASE_CONNECTION', null),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('CACHE_REDIS_CONNECTION', 'default'),
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can avoid collisions.
|
*/
'prefix' => env(
'CACHE_PREFIX',
Str::slug(env('APP_NAME', 'lumen'), '_').'_cache'
),
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => env('DB_PREFIX', ''),
],
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => env('DB_PREFIX', ''),
'strict' => env('DB_STRICT_MODE', true),
'engine' => env('DB_ENGINE', null),
'timezone' => env('DB_TIMEZONE', '+00:00'),
],
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', 5432),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => env('DB_PREFIX', ''),
'schema' => env('DB_SCHEMA', 'public'),
'sslmode' => env('DB_SSL_MODE', 'prefer'),
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 1433),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => env('DB_PREFIX', ''),
],
'mongodb' => [
'driver' => 'mongodb',
'host' => env('MONGODB_HOST', '127.0.0.1'),
'port' => env('MONGODB_PORT', 27017),
'username' => env('MONGODB_USERNAME', ''),
'password' => env('MONGODB_PASSWORD', ''),
'database' => env('MONGODB_DATABASE', ''),
'options' => [
// here you can pass more settings to the Mongo Driver Manager
// see https://www.php.net/manual/en/mongodb-driver-manager.construct.php under "Uri Options" for a list of complete parameters that you can use
'database' => env('MONGODB_AUTHENTICATION_DATABASE', 'admin'), // required with Mongo 3+
],
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer set of commands than a typical key-value systems
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'cluster' => env('REDIS_CLUSTER', false),
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
'queue' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_QUEUE_DB', 2),
],
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
'localization' => [
'key' => env('ENUM_LOCALIZATION_KEY', 'enums'),
],
// 你可以将请求参数中用到的枚举定义在下面,通过中间件,将会被自动转换成枚举类
'transformations' => [
// 参数名 => 对应的枚举类
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away!
|
*/
'default' => env('FILESYSTEM_DRIVER', 'local'),
/*
|--------------------------------------------------------------------------
| Default Cloud Filesystem Disk
|--------------------------------------------------------------------------
|
| Many applications store files both locally and in the cloud. For this
| reason, you may specify a default "cloud" driver here. This driver
| will be bound as the Cloud disk implementation in the container.
|
*/
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
| Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
],
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| JWT Authentication Secret
|--------------------------------------------------------------------------
|
| Don't forget to set this in your .env file, as it will be used to sign
| your tokens. A helper command is provided for this:
| `php artisan jwt:secret`
|
| Note: This will be used for Symmetric algorithms only (HMAC),
| since RSA and ECDSA use a private/public key combo (See below).
|
*/
'secret' => env('JWT_SECRET'),
/*
|--------------------------------------------------------------------------
| JWT Authentication Keys
|--------------------------------------------------------------------------
|
| The algorithm you are using, will determine whether your tokens are
| signed with a random string (defined in `JWT_SECRET`) or using the
| following public & private keys.
|
| Symmetric Algorithms:
| HS256, HS384 & HS512 will use `JWT_SECRET`.
|
| Asymmetric Algorithms:
| RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below.
|
*/
'keys' => [
/*
|--------------------------------------------------------------------------
| Public Key
|--------------------------------------------------------------------------
|
| A path or resource to your public key.
|
| E.g. 'file://path/to/public/key'
|
*/
'public' => env('JWT_PUBLIC_KEY'),
/*
|--------------------------------------------------------------------------
| Private Key
|--------------------------------------------------------------------------
|
| A path or resource to your private key.
|
| E.g. 'file://path/to/private/key'
|
*/
'private' => env('JWT_PRIVATE_KEY'),
/*
|--------------------------------------------------------------------------
| Passphrase
|--------------------------------------------------------------------------
|
| The passphrase for your private key. Can be null if none set.
|
*/
'passphrase' => env('JWT_PASSPHRASE'),
],
/*
|--------------------------------------------------------------------------
| JWT time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token will be valid for.
| Defaults to 1 hour.
|
| You can also set this to null, to yield a never expiring token.
| Some people may want this behaviour for e.g. a mobile app.
| This is not particularly recommended, so make sure you have appropriate
| systems in place to revoke the token if necessary.
| Notice: If you set this to null you should remove 'exp' element from 'required_claims' list.
|
*/
'ttl' => env('JWT_TTL', 60),
/*
|--------------------------------------------------------------------------
| Refresh time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token can be refreshed
| within. I.E. The user can refresh their token within a 2 week window of
| the original token being created until they must re-authenticate.
| Defaults to 2 weeks.
|
| You can also set this to null, to yield an infinite refresh time.
| Some may want this instead of never expiring tokens for e.g. a mobile app.
| This is not particularly recommended, so make sure you have appropriate
| systems in place to revoke the token if necessary.
|
*/
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
/*
|--------------------------------------------------------------------------
| JWT hashing algorithm
|--------------------------------------------------------------------------
|
| Specify the hashing algorithm that will be used to sign the token.
|
| See here: https://github.com/namshi/jose/tree/master/src/Namshi/JOSE/Signer/OpenSSL
| for possible values.
|
*/
'algo' => env('JWT_ALGO', 'HS256'),
/*
|--------------------------------------------------------------------------
| Required Claims
|--------------------------------------------------------------------------
|
| Specify the required claims that must exist in any token.
| A TokenInvalidException will be thrown if any of these claims are not
| present in the payload.
|
*/
'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
],
/*
|--------------------------------------------------------------------------
| Persistent Claims
|--------------------------------------------------------------------------
|
| Specify the claim keys to be persisted when refreshing a token.
| `sub` and `iat` will automatically be persisted, in
| addition to the these claims.
|
| Note: If a claim does not exist then it will be ignored.
|
*/
'persistent_claims' => [
// 'foo',
// 'bar',
],
/*
|--------------------------------------------------------------------------
| Lock Subject
|--------------------------------------------------------------------------
|
| This will determine whether a `prv` claim is automatically added to
| the token. The purpose of this is to ensure that if you have multiple
| authentication models e.g. `App\User` & `App\OtherPerson`, then we
| should prevent one authentication request from impersonating another,
| if 2 tokens happen to have the same id across the 2 different models.
|
| Under specific circumstances, you may want to disable this behaviour
| e.g. if you only have one authentication model, then you would save
| a little on token size.
|
*/
'lock_subject' => true,
/*
|--------------------------------------------------------------------------
| Leeway
|--------------------------------------------------------------------------
|
| This property gives the jwt timestamp claims some "leeway".
| Meaning that if you have any unavoidable slight clock skew on
| any of your servers then this will afford you some level of cushioning.
|
| This applies to the claims `iat`, `nbf` and `exp`.
|
| Specify in seconds - only if you know you need it.
|
*/
'leeway' => env('JWT_LEEWAY', 0),
/*
|--------------------------------------------------------------------------
| Blacklist Enabled
|--------------------------------------------------------------------------
|
| In order to invalidate tokens, you must have the blacklist enabled.
| If you do not want or need this functionality, then set this to false.
|
*/
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
/*
| -------------------------------------------------------------------------
| Blacklist Grace Period
| -------------------------------------------------------------------------
|
| When multiple concurrent requests are made with the same JWT,
| it is possible that some of them fail, due to token regeneration
| on every request.
|
| Set grace period in seconds to prevent parallel request failure.
|
*/
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
/*
|--------------------------------------------------------------------------
| Cookies encryption
|--------------------------------------------------------------------------
|
| By default Laravel encrypt cookies for security reason.
| If you decide to not decrypt cookies, you will have to configure Laravel
| to not encrypt your cookie token by adding its name into the $except
| array available in the middleware "EncryptCookies" provided by Laravel.
| see https://laravel.com/docs/master/responses#cookies-and-encryption
| for details.
|
| Set it to true if you want to decrypt cookies.
|
*/
'decrypt_cookies' => false,
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| Specify the various providers used throughout the package.
|
*/
'providers' => [
/*
|--------------------------------------------------------------------------
| JWT Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to create and decode the tokens.
|
*/
'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class,
/*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to authenticate users.
|
*/
'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
/*
|--------------------------------------------------------------------------
| Storage Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to store tokens in the blacklist.
|
*/
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Jiannei\Logger\Laravel\MongoLogger;
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/lumen.log'),
'level' => 'debug',
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/lumen.log'),
'level' => 'debug',
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Lumen Log',
'emoji' => ':boom:',
'level' => 'critical',
],
'papertrail' => [
'driver' => 'monolog',
'level' => 'debug',
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
],
'errorlog' => [
'driver' => 'errorlog',
'level' => 'debug',
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'mongo' => [
'driver' => 'custom', // 此处必须为 `custom`
'via' => MongoLogger::class, // 当 `driver` 设置为 custom 时,使用 `via` 配置项所指向的工厂类创建 logger
'channel' => env('LOG_MONGODB_CHANNEL', 'mongo'),
'level' => env('LOG_MONGODB_LEVEL', 'debug'), // 日志级别
'separate' => env('LOG_MONGODB_SEPARATE', false), // false,daily,monthly,yearly
'host' => env('LOG_MONGODB_HOST', config('database.connections.mongodb.host')),
'port' => env('LOG_MONGODB_PORT', config('database.connections.mongodb.port')),
'username' => env('LOG_MONGODB_USERNAME', config('database.connections.mongodb.username')),
'password' => env('LOG_MONGODB_PASSWORD', config('database.connections.mongodb.password')),
'database' => env('LOG_MONGODB_DATABASE', config('database.connections.mongodb.database')),
],
],
'enum' => \App\Repositories\Enums\LogEnum::class,
'query' => [
'enabled' => env('LOG_QUERY', false),
// Only record queries that are slower than the following time
// Unit: milliseconds
'slower_than' => 0,
],
'request' => [
'enabled' => env('LOG_REQUEST', false),
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
],
/*
* When set to true, the required permission names are added to the exception
* message. This could be considered an information leak in some contexts, so
* the default setting is false here for optimum safety.
*/
'display_permission_in_exception' => true,
/*
* When set to true, the required role names are added to the exception
* message. This could be considered an information leak in some contexts, so
* the default setting is false here for optimum safety.
*/
'display_role_in_exception' => true,
/*
* By default wildcard permission lookups are disabled.
*/
'enable_wildcard_permission' => false,
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* When checking for a permission against a model by passing a Permission
* instance to the check, this key determines what attribute on the
* Permissions model is used to cache against.
*
* Ideally, this should match your preferred way of checking permissions, eg:
* `$user->can('view-posts')` would be 'name'.
*/
'model_key' => 'name',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Lumen's queue API supports an assortment of back-ends via a single
| API, giving you convenient access to each back-end using the same
| syntax for every one. Here you may define a default connection.
|
*/
'default' => env('QUEUE_CONNECTION', 'sync'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection information for each server that
| is used by your application. A default configuration has been added
| for each back-end shipped with Lumen. You are free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => env('QUEUE_TABLE', 'jobs'),
'queue' => 'default',
'retry_after' => 90,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => 'localhost',
'queue' => 'default',
'retry_after' => 90,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('SQS_KEY', 'your-public-key'),
'secret' => env('SQS_SECRET', 'your-secret-key'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'your-queue-name'),
'region' => env('SQS_REGION', 'us-east-1'),
],
'redis' => [
'driver' => 'redis',
'connection' => env('QUEUE_REDIS_CONNECTION', 'default'),
'queue' => 'default',
'retry_after' => 90,
'block_for' => null,
],
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control which database and table are used to store the jobs that
| have failed. You may change them to any database / table you wish.
|
*/
'failed' => [
'database' => env('DB_CONNECTION', 'mysql'),
'table' => env('QUEUE_FAILED_TABLE', 'failed_jobs'),
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Repository Pagination Limit Default
|--------------------------------------------------------------------------
|
*/
'pagination' => [
'limit' => 15,
],
/*
|--------------------------------------------------------------------------
| Fractal Presenter Config
|--------------------------------------------------------------------------
|
Available serializers:
ArraySerializer
DataArraySerializer
JsonApiSerializer
*/
'fractal' => [
'params' => [
'include' => 'include',
],
'serializer' => \Jiannei\Response\Laravel\Support\Serializers\ArraySerializer::class,
],
/*
|--------------------------------------------------------------------------
| Cache Config
|--------------------------------------------------------------------------
|
*/
'cache' => [
/*
|--------------------------------------------------------------------------
| Cache Status
|--------------------------------------------------------------------------
|
| Enable or disable cache
|
*/
'enabled' => false,
/*
|--------------------------------------------------------------------------
| Cache Minutes
|--------------------------------------------------------------------------
|
| Time of expiration cache
|
*/
'minutes' => 30,
/*
|--------------------------------------------------------------------------
| Cache Repository
|--------------------------------------------------------------------------
|
| Instance of Illuminate\Contracts\Cache\Repository
|
*/
'repository' => 'cache',
/*
|--------------------------------------------------------------------------
| Cache Clean Listener
|--------------------------------------------------------------------------
|
|
|
*/
'clean' => [
/*
|--------------------------------------------------------------------------
| Enable clear cache on repository changes
|--------------------------------------------------------------------------
|
*/
'enabled' => true,
/*
|--------------------------------------------------------------------------
| Actions in Repository
|--------------------------------------------------------------------------
|
| create : Clear Cache on create Entry in repository
| update : Clear Cache on update Entry in repository
| delete : Clear Cache on delete Entry in repository
|
*/
'on' => [
'create' => true,
'update' => true,
'delete' => true,
],
],
'params' => [
/*
|--------------------------------------------------------------------------
| Skip Cache Params
|--------------------------------------------------------------------------
|
|
| Ex: http://prettus.local/?search=lorem&skipCache=true
|
*/
'skipCache' => 'skipCache',
],
/*
|--------------------------------------------------------------------------
| Methods Allowed
|--------------------------------------------------------------------------
|
| methods cacheable : all, paginate, find, findByField, findWhere, getByCriteria
|
| Ex:
|
| 'only' =>['all','paginate'],
|
| or
|
| 'except' =>['find'],
*/
'allowed' => [
'only' => null,
'except' => null,
],
],
/*
|--------------------------------------------------------------------------
| Criteria Config
|--------------------------------------------------------------------------
|
| Settings of request parameters names that will be used by Criteria
|
*/
'criteria' => [
/*
|--------------------------------------------------------------------------
| Accepted Conditions
|--------------------------------------------------------------------------
|
| Conditions accepted in consultations where the Criteria
|
| Ex:
|
| 'acceptedConditions'=>['=','like']
|
| $query->where('foo','=','bar')
| $query->where('foo','like','bar')
|
*/
'acceptedConditions' => [
'=',
'like',
],
/*
|--------------------------------------------------------------------------
| Request Params
|--------------------------------------------------------------------------
|
| Request parameters that will be used to filter the query in the repository
|
| Params :
|
| - search : Searched value
| Ex: http://prettus.local/?search=lorem
|
| - searchFields : Fields in which research should be carried out
| Ex:
| http://prettus.local/?search=lorem&searchFields=name;email
| http://prettus.local/?search=lorem&searchFields=name:like;email
| http://prettus.local/?search=lorem&searchFields=name:like
|
| - filter : Fields that must be returned to the response object
| Ex:
| http://prettus.local/?search=lorem&filter=id,name
|
| - orderBy : Order By
| Ex:
| http://prettus.local/?search=lorem&orderBy=id
|
| - sortedBy : Sort
| Ex:
| http://prettus.local/?search=lorem&orderBy=id&sortedBy=asc
| http://prettus.local/?search=lorem&orderBy=id&sortedBy=desc
|
| - searchJoin: Specifies the search method (AND / OR), by default the
| application searches each parameter with OR
| EX:
| http://prettus.local/?search=lorem&searchJoin=and
| http://prettus.local/?search=lorem&searchJoin=or
|
*/
'params' => [
'search' => 'search',
'searchFields' => 'searchFields',
'filter' => 'filter',
'orderBy' => 'orderBy',
'sortedBy' => 'sortedBy',
'with' => 'with',
'searchJoin' => 'searchJoin',
'withCount' => 'withCount',
'cursor' => 'cursor',
],
],
/*
|--------------------------------------------------------------------------
| Generator Config
|--------------------------------------------------------------------------
|
*/
'generator' => [
'basePath' => app()->path(),
'rootNamespace' => 'App\\',
'stubsOverridePath' => app()->path(),
'paths' => [
'models' => 'Repositories\\Models',
'repositories' => 'Repositories\\Eloquent',
'interfaces' => 'Contracts\\Repositories',
'transformers' => 'Repositories\\Transformers',
'presenters' => 'Repositories\\Presenters',
'validators' => 'Repositories\\Validators',
'controllers' => 'Http/Controllers',
'provider' => 'RepositoryServiceProvider',
'criteria' => 'Repositories\\Criteria',
],
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Set the http status code when the response fails
|--------------------------------------------------------------------------
|
| the reference options are false, 200, 500
|
| false, stricter http status codes such as 404, 401, 403, 500, etc. will be returned
| 200, All failed responses will also return a 200 status code
| 500, All failed responses return a 500 status code
*/
'error_code' => false,
// You can use enumerations to define the code when the response is returned,
// and set the response message according to the locale
//
// The following two enumeration packages are good choices
//
// https://github.com/Jiannei/laravel-enum
// https://github.com/BenSampo/laravel-enum
'enum' => \App\Repositories\Enums\ResponseCodeEnum::class, // \Jiannei\Enum\Laravel\Repositories\Enums\HttpStatusCodeEnum::class
// You can set some attributes (eg:code/message/header/options) for the exception, and it will override the default attributes of the exception
'exception' => [
\Illuminate\Validation\ValidationException::class => [
'code' => \App\Repositories\Enums\ResponseCodeEnum::HTTP_UNPROCESSABLE_ENTITY,
],
\Illuminate\Auth\AuthenticationException::class => [
],
\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class =>[
'message' => '',
],
\Illuminate\Database\Eloquent\ModelNotFoundException::class => [
'message' => '',
],
],
// Set the structure of the response data
'format' => [
'fields' => [
'status' => ['alias' => 'status', 'show' => true],
'code' => ['alias' => 'code', 'show' => true],
'message' => ['alias' => 'message', 'show' => true],
'error' => ['alias' => 'error', 'show' => true],
'data' => [
'alias' => 'data',
'show' => true,
'fields' => [
// When data is nested with data, such as returning paged data, you can also set an alias for the inner data
'data' => ['alias' => 'list', 'show' => true], // data/rows/list
'meta' => [
'alia' => 'meta',
'show' => true,
'fields' => [
'pagination' => [
'alias' => 'pagination',
'show' => true,
'fields' => [
'total' => ['alias' => 'total', 'show' => true],
'count' => ['alias' => 'count', 'show' => true],
'per_page' => ['alias' => 'per_page', 'show' => true],
'current_page' => ['alias' => 'current_page', 'show' => true],
'total_pages' => ['alias' => 'total_pages', 'show' => true],
'links' => [
'alias' => 'links',
'show' => true,
'fields' => [
'previous' => ['alias' => 'previous', 'show' => true],
'next' => ['alias' => 'next', 'show' => true],
],
],
],
],
],
],
],
],
],
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Stripe, Mailgun, SparkPost and others. This file provides a sane
| default location for this type of information, allowing packages
| to have a conventional place to find your various credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],
'ses' => [
'key' => env('SES_KEY'),
'secret' => env('SES_SECRET'),
'region' => env('SES_REGION', 'us-east-1'),
],
'sparkpost' => [
'secret' => env('SPARKPOST_SECRET'),
],
'stripe' => [
'model' => App\User::class,
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
'webhook' => [
'secret' => env('STRIPE_WEBHOOK_SECRET'),
'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
],
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
|--------------------------------------------------------------------------
| View Storage Paths
|--------------------------------------------------------------------------
|
| Most templating systems load templates from disk. Here you may specify
| an array of paths that should be checked for your views. Of course
| the usual Laravel view path has already been registered for you.
|
*/
'paths' => [
resource_path('views'),
],
/*
|--------------------------------------------------------------------------
| Compiled View Path
|--------------------------------------------------------------------------
|
| This option determines where all the compiled Blade templates will be
| stored for your application. Typically, this is within the storage
| directory. However, as usual, you are free to change this value.
|
*/
'compiled' => realpath(storage_path('framework/views')),
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Database\Factories;
use App\Repositories\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
protected $model = Post::class;
public function definition()
{
return [
'title' => $this->faker->sentence,
'body' => $this->faker->paragraph(3),
'published' => $this->faker->numberBetween(0, 1),
'user_id' => $this->faker->randomElement([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
];
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Database\Factories;
use App\Repositories\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
class UserFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = User::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'password' => Hash::make('password'),
];
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
$tableNames = 'admin_users';
Schema::create($tableNames, function (Blueprint $table) {
$table->id();
$table->string('username')->comment('登陆用户名');
$table->string('nickname')->comment('用户名称');
$table->string('password')->comment('密码');
$table->string('email')->nullable()->comment('邮箱');
$table->string('token')->nullable()->comment('token安全令牌');
$table->unsignedTinyInteger('is_admin')->default(0)->comment('是否是超级管理员 0:否 1:是');
$table->unsignedTinyInteger('status')->default(0)->comment('账号状态 0:关闭 1 开启');
$table->unsignedTinyInteger('lastlogin')->nullable()->comment('最后登陆时间');
$table->timestamps();
});
$columnNames = 'username';
Schema::table($tableNames,function(Blueprint $table) use ($tableNames,$columnNames){
DB::statement('ALTER TABLE '.$tableNames. ' ROW_FORMAT=DYNAMIC;');
$table->index([$columnNames]);
});
}
/**
* Reverse the migrations.
*/
public function down()
{
Schema::dropIfExists('admin_users');
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Class CreatePostsTable.
*/
class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->text('title');
$table->text('body');
$table->boolean('published');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('posts');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMenusTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('menus', function (Blueprint $table) {
$table->id();
$table->string('menu_name')->comment('菜单名称');
$table->string('menu_path')->comment('菜单路径');
$table->TinyInteger('parent_id')->comment('父级菜单id');
$table->enum('menu_type',['top','page','button'])->comment('菜单类型');
$table->string('menu_icon')->nullable()->comment('菜单图标');
$table->string('component')->nullable()->comment('页面组件');
$table->unsignedTinyInteger('status')->comment('菜单状态 0:未开启 1:开启');
$table->unsignedTinyInteger('is_show')->comment('菜单状态 0:不显示 1:显示');
$table->unsignedTinyInteger('sys_default')->default(0)->comment('是否是默认菜单');
$table->unsignedTinyInteger('sort')->comment('排序');
$table->unsignedInteger('created_by')->comment('创建人');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('menus');
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CreatePermissionTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
/*
* 权限表
* 所有的菜单、搜索、列表中的操作权限都在这里。
*/
Schema::create($tableNames['permissions'], function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedBigInteger('menu_id');
$table->string('action')->comment('权限的行为');
$table->enum('permission_type',['menu','data','button'])->nullable()->comment('权限类型');
$table->unsignedTinyInteger('sys_default')->default(0)->comment('是否系统默认');
$table->text('remark')->nullable()->comment('备注');
$table->string('guard_name')->comment('权限插件保留字段');
$table->timestamps();
});
$columns = ['name','guard_name'];
Schema::table($tableNames['permissions'],function(Blueprint $table) use ($tableNames,$columns){
DB::statement('ALTER TABLE '.$tableNames['permissions']. ' ROW_FORMAT=DYNAMIC;');
//外键关联menus表id
/*$table->foreign('menu_id')
->references('id')
->on('menus')
->onUpdate('CASCADE')
->onDelete('CASCADE');*/
$table->index($columns);
$table->unique($columns);
});
Schema::create($tableNames['roles'], function (Blueprint $table) {
$table->id();
$table->string('name')->comment('名称');
$table->text('remark')->nullable()->comment('备注');
$table->string('guard_name')->comment('权限插件保留字段');
$table->timestamps();
});
$columns = ['name','guard_name'];
Schema::table($tableNames['roles'],function(Blueprint $table) use ($tableNames,$columns){
DB::statement('ALTER TABLE '.$tableNames['roles']. ' ROW_FORMAT=DYNAMIC;');
$table->index($columns);
$table->unique($columns);
});
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) {
$table->unsignedBigInteger('permission_id');
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->foreign('permission_id')
->references('id')
->on($tableNames['permissions'])
->onDelete('cascade');
});
Schema::table($tableNames['model_has_permissions'],function(Blueprint $table) use ($tableNames,$columnNames){
DB::statement('ALTER TABLE '.$tableNames['model_has_permissions']. ' ROW_FORMAT=DYNAMIC;');
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->primary(['permission_id', $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
});
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) {
$table->unsignedBigInteger('role_id');
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->foreign('role_id')
->references('id')
->on($tableNames['roles'])
->onDelete('cascade');
});
Schema::table($tableNames['model_has_roles'],function(Blueprint $table) use ($tableNames,$columnNames){
DB::statement('ALTER TABLE '.$tableNames['model_has_roles']. ' ROW_FORMAT=DYNAMIC;');
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->primary(['role_id', $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
});
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) {
$table->unsignedBigInteger('permission_id');
$table->unsignedBigInteger('role_id');
$table->foreign('permission_id')
->references('id')
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign('role_id')
->references('id')
->on($tableNames['roles'])
->onDelete('cascade');
});
Schema::table($tableNames['role_has_permissions'],function(Blueprint $table) use ($tableNames,$columnNames){
DB::statement('ALTER TABLE '.$tableNames['role_has_permissions']. ' ROW_FORMAT=DYNAMIC;');
$table->primary(['permission_id', 'role_id'], 'role_has_permissions_permission_id_role_id_primary');
;
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run()
{
//$this->call(PermissionsSeeder::class);
if (app()->environment() === 'local') {
$this->call(AdminUsersSeeder::class);
$this->call(MenusSeeder::class);
$this->call(RolesSeeder::class);
$this->call(PermissionsSeeder::class);
}
}
}
<?php
use Database\Factories\PostFactory;
use Illuminate\Database\Seeder;
use App\Support\Traits\Helpers;
use App\Repositories\Models\AdminUsers;
class AdminUsersSeeder extends Seeder
{
use Helpers;
protected $userList = [
[
'username' =>'admin',
'nickname' =>'admin',
'password' => 'admin123456',
'email' => 'admin@admin.com',
'token' => '',
'status' => 1,
'is_admin' => 1,
'lastlogin' => 0,
'created_at' => '',
'updated_at' => ''
]
];
public function run()
{
collect($this->userList)->map(function($user){
$admin = app(AdminUsers::class);
$user['password'] = $admin->encryptPassword($user['password']);
$user['token'] = $this->randomFromDevice(16);
$user['created_at'] = date('Y-m-d H:i:s',time());
$user['updated_at'] = date('Y-m-d H:i:s',time());
AdminUsers::insert([$user]);
});
}
}
<?php
use Illuminate\Database\Seeder;
use App\Repositories\Models\Menus;
class MenusSeeder extends Seeder
{
protected $menuList = [
[
'menu_name' => '平台首页',
'menu_path' => '#',
'parent_id' => -1,
'menu_type' => 'top',
'menu_icon' => 'md-home',
'component' => '',
'status' => 1,
'is_show' => 1,
'sys_default' => 1,
'sort' => 1.0,
'created_by' => 0,
'created_at' => '',
'updated_at' => ''
],
[
'menu_name' => '系统管理',
'menu_path' => '#',
'parent_id' => 0,
'menu_type' => 'page',
'menu_icon' => 'ios-settings',
'component' => 'Main',
'status' => 1,
'is_show' => 1,
'sys_default' => 1,
'sort' => 1.0,
'created_by' => 0,
'created_at' => '',
'updated_at' => ''
],
];
protected $subMenusList = [
[
'menu_name' => '菜单权限管理',
'menu_path' => '/menus',
'parent_id' => 0,
'menu_type' => 'page',
'menu_icon' => 'md-menu',
'component' => 'sys/menu-manage/menuManage',
'status' => 1,
'is_show' => 1,
'sys_default' => 1,
'sort' => 1.10,
'created_by' => 0,
'created_at' => '',
'updated_at' => ''
],
[
'menu_name' => '用户权限管理',
'menu_path' => '/permission',
'parent_id' => 0,
'menu_type' => 'page',
'menu_icon' => 'md-contacts',
'component' => 'sys/role-manage/roleManage',
'status' => 1,
'is_show' => 1,
'sys_default' => 1,
'sort' => 1.11,
'created_by' => 0,
'created_at' => '',
'updated_at' => ''
],
];
public function run()
{
collect($this->menuList)->map(function($menu){
$menu['created_at'] = date('Y-m-d H:i:s',time());
$menu['updated_at'] = date('Y-m-d H:i:s',time());
$id = Menus::query()->insertGetId($menu);
if($id && $menu['parent_id'] == 0){
//初始化系统子菜单
collect($this->subMenusList)->map(function($subMenus) use ($id){
$subMenus['parent_id'] = $id;
$subMenus['created_at'] = date('Y-m-d H:i:s',time());
$subMenus['updated_at'] = date('Y-m-d H:i:s',time());
Menus::query()->insertGetId($subMenus);
});
}
});
$topMenus = Menus::where('parent_id','=', -1)->first()->toArray();
$pageMenus = Menus::where(['parent_id' => 0, 'menu_type' => 'page'])->first();
$newPageMenus = $pageMenus->toArray();
$newPageMenus['parent_id'] = $topMenus['id'];
$pageMenus->update($newPageMenus);
}
}
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use App\Repositories\Models\Menus;
use App\Repositories\Models\AdminUsers;
class PermissionsSeeder extends Seeder
{
protected $defaultPermissionList = [
[
'name' => '查看操作',
'menu_id' => 0,
'action' => 'view',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '查看操作权限',
'guard_name' => '',
],
[
'name' => '添加操作',
'menu_id' => 0,
'action' => 'add',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '添加操作权限',
'guard_name' => '',
],
[
'name' => '编辑操作',
'menu_id' => 0,
'action' => 'edit',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '编辑操作权限',
'guard_name' => '',
],
[
'name' => '删除操作',
'menu_id' => 0,
'action' => 'delete',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '删除操作权限',
'guard_name' => '',
],
[
'name' => '清空操作',
'menu_id' => 0,
'action' => 'clear',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '清空操作权限',
'guard_name' => '',
],
[
'name' => '启用操作',
'menu_id' => 0,
'action' => 'enable',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '启用操作权限',
'guard_name' => '',
],
[
'name' => '禁用操作',
'menu_id' => 0,
'action' => 'disable',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '禁用操作权限',
'guard_name' => '',
],
[
'name' => '搜索操作',
'menu_id' => 0,
'action' => 'search',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '搜索操作权限',
'guard_name' => '',
],
[
'name' => '上传操作',
'menu_id' => 0,
'action' => 'upload',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '上传操作权限',
'guard_name' => '',
],
[
'name' => '导出操作',
'menu_id' => 0,
'action' => 'export',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '导出操作权限',
'guard_name' => '',
],
[
'name' => '导入操作',
'menu_id' => 0,
'action' => 'input',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '导入操作权限',
'guard_name' => '',
],
[
'name' => '分配权限操作',
'menu_id' => 0,
'action' => 'editPermission',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '分配权限操作权限',
'guard_name' => '',
],
[
'name' => '设为默认操作',
'menu_id' => 0,
'action' => 'setDefault',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '设为默认操作权限',
'guard_name' => '',
],
[
'name' => '其他操作',
'menu_id' => 0,
'action' => 'other',
'permission_type' => 'button',
'sys_default' => 1,
'remark' => '其他操作权限',
'guard_name' => '',
],
];
public function run()
{
collect($this->defaultPermissionList)->map(function($permission){
$permission['guard_name'] = config('auth.defaults.guard');
$permission['created_at'] = date('Y-m-d H:i:s',time());
$permission['updated_at'] = date('Y-m-d H:i:s',time());
Permission::create($permission);
});
// 将初始化菜单进行权限绑定
$menu = app(Menus::class)->query();
$menuList = $menu->where('status',1)->get()->toArray();
collect($menuList)->map(function($menu){
$permission = [];
$permission['name'] = $menu['menu_name'];
$permission['menu_id'] = $menu['id'];
$permission['action'] = '';
$permission['permission_type'] = 'menu';
$permission['sys_default'] = 0;
$permission['remark'] = $menu['menu_name'].'的相关权限';
$permission['guard_name'] = config('auth.defaults.guard');
Permission::create($permission);
});
//给用户组分配权限
$allPermission = Permission::where('guard_name', config('auth.defaults.guard'))->get();
$role = Role::all()->first();
$role->givePermissionTo($allPermission);
//给用户指定用户组
$admin = app(AdminUsers::class)->where('is_admin',1)->first();
$admin->assignRole($role);
$admin->syncPermissions($allPermission);
}
}
<?php
use Illuminate\Database\Seeder;
use App\Repositories\Models\Menus;
use Spatie\Permission\Models\Role;
class RolesSeeder extends Seeder
{
protected $rolesList = [
[
'name' => '管理员',
'remark' => '超级管理员组,拥有最高权限',
'guard_name' => '',
'created_at' => '',
'updated_at' => ''
],
];
public function run()
{
collect($this->rolesList)->map(function($role){
$role['guard_name'] = config('auth.defaults.guard');
$role['created_at'] = date('Y-m-d H:i:s',time());
$role['updated_at'] = date('Y-m-d H:i:s',time());
Role::create($role,$role['guard_name']);
//$id = Menus::query()->insertGetId($menu);
});
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use App\Repositories\Enums\PermissionEnum;
use App\Repositories\Enums\RoleEnum;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Contracts\Permission as PermissionContract;
use Spatie\Permission\Contracts\Role as RoleContract;
use Spatie\Permission\PermissionRegistrar;
class PermissionsSeeder extends Seeder
{
public function run()
{
app(PermissionRegistrar::class)->forgetCachedPermissions();
$permissionClass = app(PermissionContract::class);
$roleClass = app(RoleContract::class);
DB::statement('SET FOREIGN_KEY_CHECKS = 0'); // 禁用外键约束
DB::table($roleClass->getTable())->truncate();
DB::table($permissionClass->getTable())->truncate();
DB::statement('SET FOREIGN_KEY_CHECKS = 1'); // 启用外键约束
$roles = RoleEnum::makeRoles();
foreach ($roles as $item) {
$roleClass->create($item);
}
$permissions = PermissionEnum::makePermissions();
foreach ($permissions as $item) {
$permissionClass::create($item);
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value="database/database.sqlite"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
</php>
</phpunit>
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
$app = require __DIR__.'/../bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$app->run();
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use App\Repositories\Enums\ResponseCodeEnum;
return [
ResponseCodeEnum::class => [
// 成功
ResponseCodeEnum::HTTP_OK => 'Success.', // 自定义 HTTP 状态码返回消息
ResponseCodeEnum::HTTP_UNAUTHORIZED => 'Unauthorized.',
// 业务操作成功
ResponseCodeEnum::SERVICE_REGISTER_SUCCESS => 'Register success.',
ResponseCodeEnum::SERVICE_LOGIN_SUCCESS => 'Login success.',
// 客户端错误
ResponseCodeEnum::CLIENT_PARAMETER_ERROR => 'Parameter error.',
ResponseCodeEnum::CLIENT_CREATED_ERROR => 'Created error.',
ResponseCodeEnum::CLIENT_DELETED_ERROR => 'Deleted error.',
// 服务端错误
ResponseCodeEnum::SYSTEM_ERROR => 'System error.',
ResponseCodeEnum::SYSTEM_UNAVAILABLE => 'System unavailable.',
// 业务操作失败:授权业务
ResponseCodeEnum::SERVICE_REGISTER_ERROR => 'Register error.',
ResponseCodeEnum::SERVICE_LOGIN_ERROR => 'Login error,',
],
];
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use App\Repositories\Enums\PermissionEnum;
use App\Repositories\Enums\ResponseCodeEnum;
use App\Repositories\Enums\RoleEnum;
return [
// 响应状态码
ResponseCodeEnum::class => [
// 成功
ResponseCodeEnum::HTTP_OK => '操作成功', // 自定义 HTTP 状态码返回消息
ResponseCodeEnum::HTTP_NOT_FOUND => '数据未找到', // 自定义 HTTP 状态码返回消息
ResponseCodeEnum::HTTP_UNAUTHORIZED => '授权失败',
// 业务操作成功
ResponseCodeEnum::SERVICE_REGISTER_SUCCESS => '注册成功',
ResponseCodeEnum::SERVICE_LOGIN_SUCCESS => '登录成功',
// 客户端错误
ResponseCodeEnum::CLIENT_PARAMETER_ERROR => '参数错误',
ResponseCodeEnum::CLIENT_CREATED_ERROR => '数据已存在',
ResponseCodeEnum::CLIENT_DELETED_ERROR => '数据不存在',
ResponseCodeEnum::CLIENT_VALIDATION_ERROR => '表单验证错误',
// 服务端错误
ResponseCodeEnum::SYSTEM_ERROR => '服务器错误',
ResponseCodeEnum::SYSTEM_UNAVAILABLE => '服务器正在维护,暂不可用',
ResponseCodeEnum::SYSTEM_CACHE_CONFIG_ERROR => '缓存配置错误',
ResponseCodeEnum::SYSTEM_CACHE_MISSED_ERROR => '缓存未命中',
ResponseCodeEnum::SYSTEM_CONFIG_ERROR => '系统配置错误',
// 业务操作失败:授权业务
ResponseCodeEnum::SERVICE_REGISTER_ERROR => '注册失败',
ResponseCodeEnum::SERVICE_LOGIN_ERROR => '登录失败',
],
// 角色
RoleEnum::class => [
RoleEnum::SUPER_ADMIN => '超级管理员',
RoleEnum::GUEST => '游客',
RoleEnum::ADMIN => '管理员',
],
// 权限
PermissionEnum::class => [
PermissionEnum::SYSTEM_ACTIVITY_LOG_CLEAN => '清理活动日志',
PermissionEnum::SYSTEM_CACHE_CLEAR => '清理缓存',
PermissionEnum::ROUTE_USERS => '用户管理',
PermissionEnum::ROUTE_USERS_CREATE => '创建用户',
PermissionEnum::ROUTE_USERS_DELETE => '删除用户',
PermissionEnum::ROUTE_USERS_UPDATE => '更新用户资料',
PermissionEnum::ROUTE_USERS_VIEW => '查询用户资料',
PermissionEnum::ROUTE_USERS_VIEW_ANY => '查询用户列表',
PermissionEnum::ROUTE_POSTS => ' 文章管理',
PermissionEnum::ROUTE_POSTS_CREATE => '发布文章',
PermissionEnum::ROUTE_POSTS_DELETE => '删除文章',
PermissionEnum::ROUTE_POSTS_UPDATE => '更新文章',
PermissionEnum::ROUTE_POSTS_VIEW => '查看文章',
PermissionEnum::ROUTE_POSTS_VIEW_ANY => '查询文章列表',
],
];
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/
$api->version('v1', function($api) {
$api->group(['namespace'=>'App\Http\V1\Auth','middleware' => ['api.auth','superadmin'], 'providers' => 'jwt'], function($api) {
//用户相关
$api->post('/adminapi/user/add', ['uses'=>'AuthorizationController@addUser']);
$api->post('/adminapi/user/edit', ['uses'=>'AuthorizationController@editUser']);
$api->get('/adminapi/user/info', ['uses'=>'AuthorizationController@info']);
$api->post('/adminapi/auth/resetPassword', ['uses'=>'AuthorizationController@resetPassword']);
$api->get('/adminapi/user/listByPage', ['uses'=>'AuthorizationController@listByPage']);
$api->post('/adminapi/user/disable/{id}', ['uses'=>'AuthorizationController@changeUserStatus']);
$api->post('/adminapi/user/enable/{id}', ['uses'=>'AuthorizationController@changeUserStatus']);
$api->post('/adminapi/user/delByIds', ['uses'=>'AuthorizationController@delUserByIds']);
//菜单相关
$api->get('/adminapi/permission/menu/list', ['uses'=>'AuthorizationController@getMenuList']);
$api->post('/adminapi/permission/menu/edit', ['uses'=>'AuthorizationController@editMenus']);
$api->post('/adminapi/permission/menu/sub_add', ['uses'=>'AuthorizationController@addSubMenus']);
$api->post('/adminapi/permission/menu/del', ['uses'=>'AuthorizationController@deleteMenus']);
$api->get('/adminapi/permission/menu/all', ['uses'=>'AuthorizationController@getAllMenuList']);
//用户组相关
$api->get('/adminapi/permission/role/getAllByPage', ['uses'=>'AuthorizationController@getAllByPage']);
$api->get('/adminapi/permission/role/getAllList', ['uses'=>'AuthorizationController@getAllList']);
$api->post('/adminapi/permission/role/add', ['uses'=>'AuthorizationController@addRole']);
$api->post('/adminapi/permission/role/edit', ['uses'=>'AuthorizationController@editRole']);
$api->post('/adminapi/permission/role/delByIds', ['uses'=>'AuthorizationController@delByIds']);
$api->post('/adminapi/permission/role/editRolePermission', ['uses'=>'AuthorizationController@editRolePermission']);
$api->get('/adminapi/permission/dictData/defaultButtonPermission', ['uses'=>'AuthorizationController@getDefaultButtonPermission']);
});
//用户登陆
$api->group(['namespace'=>'App\Http\V1\Auth'], function($api) {
$api->post('/adminapi/auth/login', ['uses'=>'AuthorizationController@login']);
});
//用户登出
$api->group(['namespace'=>'App\Http\V1\Auth','middleware' => ['superadmin']], function($api) {
$api->get('/adminapi/auth/logout', ['uses'=>'AuthorizationController@logout']);
});
});
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Jiannei\Response\Laravel\Support\Facades\Response;
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/
Route::get('/', function () {
return app()->version();
});
Route::get('author', function () {
$response = Http::withOptions(['timeout' => 3])->get('https://api.github.com/users/Jiannei');
$response->throw();
return Response::success($response->json());
});
// 测试路由
Route::group(['prefix' => 'test'], function () {
Route::get('configurations', 'ExampleController@configurations');
Route::get('logs', ['uses' => 'ExampleController@logs', 'middleware' => 'throttle:5,1']);
Route::put('roles', 'ExampleController@syncRoles');
Route::put('permissions', 'ExampleController@syncPermissions');
Route::get('posts', 'PostsController@index');
});
Route::group(['prefix' => 'adminapi','middleware' => ['auth:api','userRole']], function () {
Route::put('test', 'ExampleController@configurations');
});
// 用户管理
Route::post('users', 'UsersController@store');
Route::get('users/{id}', 'UsersController@show');
Route::get('users', 'UsersController@index');
Route::get('users/pagination/simple', 'UsersController@simple');
Route::get('users/pagination/cursor', 'UsersController@cursor');
// 授权管理
Route::post('authorization', 'AuthorizationController@store');
Route::delete('authorization', 'AuthorizationController@destroy');
Route::put('authorization', 'AuthorizationController@update');
Route::get('authorization', 'AuthorizationController@show');
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Tests\Feature;
use Laravel\Lumen\Testing\DatabaseMigrations;
use Tests\TestCase;
class ExampleTest extends TestCase
{
//use DatabaseMigrations;
/**
* A basic test example.
*/
public function testExample()
{
$this->get('/');
$this->assertEquals(
$this->app->version(),
$this->response->getContent()
);
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Tests;
use Laravel\Lumen\Testing\TestCase as BaseTestCase;
use Tests\Traits\CreatesApplication;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Tests\Traits;
trait CreatesApplication
{
/**
* Creates the application.
*
* @return \Laravel\Lumen\Application
*/
public function createApplication()
{
return require __DIR__.'/../../bootstrap/app.php';
}
}
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Tests\Unit;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$this->assertTrue(true);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment