从零开始搭建一个 demo 项目
简介
我之前遇到这样一个问题,平时都是对已有的项目进行维护或者添加一些新功能,偶尔有一天,需要我自己独立负责一个项目的时候,就有点“无从下手”,对,就是无从下手!零碎的知识点不知道该怎么把它们拼到一起,当最终完成时又会感慨:“哎呀,当时我为什么不那样做呢?我那时候没考虑到这个情况,我应该这样做的,等等”。
于是,我就想,为什么不做一个demo项目方便我或者他人使用呢? 因为需求是不同的,所以这个demo项目封装的只是一些通用的,常用到的内容,如有做的不对的地方,请点击右下方的纠正按钮,我们一起来完善它。
开发前的准备
适用项目范围
适用于小、中型项目,不涉及到高并发等情况,只是作为新手入门使用。
项目偏重点为api接口部分。
版本相关
环境搭建
我们默认您已经安装好了所需的 lnmp环境,环境搭建请看Laravel 开发环境部署
创建项目
通过Laravel安装器
composer global require laravel/installer
确保将 Composer’s system-wide vendor 目录放置在你的系统环境变量 $PATH 中,如果ok,您可以使用laravel new 项目名
创建一个新的项目。
通过composer
composer create-project --prefer-dist laravel/laravel 项目名
从github拉取项目
您可以通过下面命令直接拉取本项目:
git clone https://github.com/Jouzeyu/api-demo.git
相关配置
您可以在.env
文件中配置您的数据库连接,然后运行php artisan migrate
命令迁移数据库。
API 登录
说明
我们这里使用官方给推荐的Passport OAuth 认证,当然你也可以使用Dingo API。如果你的项目只是几个人使用且不需要刷新令牌的话,也可以尝试使用Laravel的 API 认证。
安装
composer require laravel/passport
运行迁移
php artisan migrate
运行迁移后将在数据库中自动创建相关表,如果提示错误,请优先查看您的
.env
文件中的数据库连接是否正确。
生成秘钥和客户端
接下来,运行 passport:install
命令来创建生成安全访问令牌时所需的加密密钥,同时,这条命令也会创建用于生成访问令牌的「个人访问」客户端和「密码授权」客户端:
php artisan passport:install
配置及使用
第一步:在User模型中引用 HasApiTokens
:
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
}
第二步:在 app/Providers/AuthServiceProvider
中的 boot
方法中调用 Passport::routes
方法。
<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
//'App\Model' => 'App\Policies\ModelPolicy'
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
最后一步:在 config/auth.php
配置文件中,将 api 的 driver
选项替换为 passport
:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
项目相关:完成API登录(其他部分)
1. 在app/Http/Controllers/Api
下创建Controller.php
,内容如下:
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
}
2. 创建 Auth控制器并修改如下:
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\User;
use Illuminate\Support\Facades\Hash;
class AuthController extends Controller
{
/**
* Create user
*
* @param [string] name
* @param [string] email
* @param [string] password
* @return [string] message
*/
public function register(Request $request)
{
$request->validate([
'name' => 'required|string',
'email' => 'required|string|email|unique:users',
'password' => 'required|string'
]);
$user = new User([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
$user->save();
return response()->json([
'message' => '用户创建成功'
], 201);
}
/**
* Login user and create token
*
* @param [string] email
* @param [string] password
* @param [boolean] remember_me
* @return [string] access_token
* @return [string] token_type
* @return [string] expires_at
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
'remember_me' => 'boolean'
]);
$credentials = request(['email', 'password']);
if(!Auth::attempt($credentials))
return response()->json([
'message' => '用户名或者密码错误'
], 401);
$user = $request->user();
$tokenResult = $user->createToken('Personal Access Token');
$token = $tokenResult->token;
if ($request->remember_me)
$token->expires_at = Carbon::now()->addWeeks(1);
$token->save();
return response()->json([
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse(
$tokenResult->token->expires_at
)->toDateTimeString()
]);
}
/**
* Get the authenticated User
*
* @return [json] user object
*/
public function userInfo(Request $request)
{
return response()->json($request->user());
}
}
3. api路由部分:
Route::group([
'prefix' => 'v1'
], function () {
Route::post('login', 'Api\AuthController@login');
Route::post('register', 'Api\AuthController@register');
Route::group([
'middleware' => 'auth:api'
], function() {
Route::get('user_info', 'Api\AuthController@userInfo');
});
});
项目相关:测试结果
注册结果:
登录结果:
置换详细信息结果:
统一 Restful API 响应处理
说明
私下和公司前端聊过,怎样的Restful API 响应对他们调用起来更加友好,前端说只要统一就好,但是经过尝试,对接,我发现后端捕获所有异常,并添加到返回数据中更加适合前后端分离项目。所以我们这里采用的也是这种方式。
封装统一返回信息
首先我们在app/Api
文件夹下新建一个Helpers
文件夹并创建一个名为ApiResponse.php
的文件来存放我们要封装的内容:
<?php
namespace App\Api\Helpers;
use Symfony\Component\HttpFoundation\Response as FoundationResponse;
use Response;
trait ApiResponse
{
/**
* @var int
*/
protected $statusCode = FoundationResponse::HTTP_OK;
/**
* @return mixed
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* @param $statusCode
* @return $this
*/
public function setStatusCode($statusCode,$httpCode=null)
{
$httpCode = $httpCode ?? $statusCode;
$this->statusCode = $statusCode;
return $this;
}
/**
* @param $data
* @param array $header
* @return mixed
*/
public function respond($data, $header = [])
{
return Response::json($data,$this->getStatusCode(),$header);
}
/**
* @param $status
* @param array $data
* @param null $code
* @return mixed
*/
public function status($status, array $data, $code = null){
if ($code){
$this->setStatusCode($code);
}
$status = [
'status' => $status,
'code' => $this->statusCode
];
$data = array_merge($status,$data);
return $this->respond($data);
}
/**
* @param $message
* @param int $code
* @param string $status
* @return mixed
*/
/*
* 格式
* data:
* code:422
* message:xxx
* status:'error'
*/
public function error($message, $code = FoundationResponse::HTTP_BAD_REQUEST,$status = 'error'){
return $this->setStatusCode($code)->message($message,$status);
}
/**
* @param $message
* @param string $status
* @return mixed
*/
public function message($message, $status = "success"){
return $this->status($status,[
'message' => $message
]);
}
/**
* @param string $message
* @return mixed
*/
public function internalError($message = "Internal Error!"){
return $this->error($message,FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
}
/**
* @param string $message
* @return mixed
*/
public function created($message = "created")
{
return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
->message($message);
}
/**
* @param $data
* @param string $status
* @return mixed
*/
public function success($data, $status = "success"){
return $this->status($status,compact('data'));
}
/**
* @param string $message
* @return mixed
*/
public function notFond($message = 'Not Fond!')
{
return $this->error($message,Foundationresponse::HTTP_NOT_FOUND);
}
}
如何使用
修改基类
<?php
namespace App\Http\Controllers\Api;
use App\Api\Helpers\ApiResponse;
use App\Http\Controllers\Controller as BaseController;
class Controller extends BaseController
{
use ApiResponse;
// 引用封装好的错误提示模板
}
其他控制器调用
1. 返回正确资源信息
return $this->success($users);
2. 返回自定义 http 状态码的正确信息
return $this->setStatusCode(201)->success($users);
3. 返回错误信息
return $this->error('用户注册失败');
5. 返回自定义 http 状态码的错误信息
return $this->error('用户登录失败',401);
声明:这块部分参考自手摸手教你让 Laravel 开发 API 更得心应手
项目相关:完善其他部分
1. 创建Users控制器,方便测试:
<?php
namespace App\Http\Controllers\Api;
use App\User;
use Illuminate\Http\Request;
class UsersController extends Controller
{
public function test(Request $request){
$user=User::where('id',1)->first();
return $this->success($user);
}
}
2. 注册路由
Route::group([
'prefix' => 'v1'
], function () {
Route::get('test', 'Api\UsersController@test');//这里,不需要登录认证
……
});
项目相关:测试结果
处理接口返回值
问题抛出
为了安全等原因,我们都会限制其返回值。如你所见,在上一块中返回User信息时,返回的是所有的数据,其中就包含了敏感信息password
,常见的敏感信息还包括微信的openid
等,如何避免就是这一块的主要内容。
API资源
简介
你往往需要一个转换层来联结你的 Eloquent 模型和实际返回给用户的 JSON 响应,也就是我们常说的ViewModel
层。
创建用户资源
php artisan make:resource Api/UserResource
ok,我们可以看到在http目录下多了一个resource目录,展开后编辑UserResource.php
如下:
<?php
namespace App\Http\Resources\Api;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* 这里指定了返回值
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id'=>$this->id,
'name' => $this->name,
'email' => $this->email,
'created_at'=>(string)$this->created_at,
'updated_at'=>(string)$this->updated_at
];
}
}
如何使用
打开我们的User测试控制器,将:
return $this->success($user);
替换为:
return $this->success(new UserResource($user));
注意:该方法只适合返回单一用户,否则将无法使用分页。如果是用户列表,你可以使用
return UserResource::collection($user);
项目相关:测试结果
实现枚举的三种方式
现在有一个需求,给用户添加状态,比如冻结,正常。这是很普遍的需求,我们一般是在数据表中多一个status字段,用数字代表身份,但是短时间内我们知道数字所代表的含义,时间一长就忘记了,而且前端人员也不清楚。怎么办呢,我们就用到了枚举。
单独的枚举目录
创建Enum目录
在app
下创建一个 Enum 目录,里面存放我们的枚举文件,例如UserEnum.php
:
<?php
namespace App\Enum;
class UserEnum
{
// 状态类别
const NORMAL = 1; //正常
const FREEZE = 2; //冻结
public static function getStatusName($status){
switch ($status){
case self::NORMAL:
return '正常';
case self::FREEZE:
return '冻结';
default:
return '正常';
}
}
}
使用
修改 UserResource.php
:
return [
'id'=>$this->id,
'name' => $this->name,
'email' => $this->email,
'status' => UserEnum::getStatusName($this->status),
'created_at'=>(string)$this->created_at,
'updated_at'=>(string)$this->updated_at
];
提示:冻结状态就不应该能够登陆了,这里的判断逻辑请自行编写
配置文件
因为篇幅较长,大家可以看我之前的博客如何优雅地使用帮助类文件 helpers.php
嵌套太模型中
同理,大家可以看我之前的博客为 type 等字段「保驾护航」
由于每个人喜欢的方式不同,故枚举这块不计入demo项目中。
接口筛选
说明
前面我们已经完善的差不多了,但是还差一个重要的模块,那就是接口筛选。达到的目的就是前端通过不同的参数,来拿到相对应的筛选后的数据。我们这里用的是 laravel-query-builder
拓展包。
安装
composer require spatie/laravel-query-builder
发布配置
php artisan vendor:publish --provider="Spatie\QueryBuilder\QueryBuilderServiceProvider" --tag="config"
如何使用
1. 首先确保模型中允许通过了所需字段,例如User模型中:
protected $fillable = [
'name', 'email', 'password',
];
2. 在控制器中使用:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Resources\Api\UserResource;
use App\User;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\QueryBuilder;
class UsersController extends Controller
{
public function test(Request $request){
$users = QueryBuilder::for(User::class)
->allowedFilters(['name'])
->get();
return UserResource::collection($users);
}
}
3. 前端筛选
http://api-demo.test/api/v1/test?filter[name]=jouzeyu
注意:引入的是
use Spatie\QueryBuilder\QueryBuilder;
,而不是其他。我们使用allowedFilters
来声明允许筛选的字段。这里我们只是初步介绍一下,更多内容请看文档
其他配置
语言包安装
composer require caouecs/laravel-lang:~4.0
debug调试工具栏
composer require barryvdh/laravel-debugbar --dev
提示:
--dev
代表仅在本地安装
laravel-ide-helper
composer require --dev barryvdh/laravel-ide-helper
解决跨域问题
安装
composer require barryvdh/laravel-cors
配置
在app/Http/Kernel.php
中添加:
protected $middleware = [
// ...
\Barryvdh\Cors\HandleCors::class,
];
总结
demo 项目暂时就告一段落了,也许他并不完美,但还是希望对刚刚入门的朋友有所启发。如果你有更好的方案,欢迎在下方评论,感谢。
本作品采用《CC 协议》,转载必须注明作者和本文链接
加油 :+1:
很有帮助,很实用,点赞!
点个赞,对于laravel 想要多学习的人来说,还是很有必要的
总结的很棒
超赞 正好在看 vue 做前端 前后端分离 API 这块 感谢楼主的分享
经验+1 :+1:
windows homestead 启动后,下一步的开发 laravel 应该装在哪

不知道 composer global require laravel/installer 在 homestead 中执行,还是在 windows 执行
正好在做一个api项目前期准备工作 学习了!
:+1:有用
登录后接口为什么不用返回 refresh_token ,不用做刷新 token 机制吗?