客户端与服务端进行数据交互的通道, 大部分APP接口都是通过HTTP协议通信的
客户端传递参数
服务端获取参数
JSON是一种轻量级的数据交换格式
接口输出三要素
封装接口JSON格式输出
在app下新建公共common文件夹, 在common新建lib文件夹用来存放一些公共类库文件
/**
* 老王
*
**/
namespace app\common\lib;
use app\common\lib\error\ApiErrDesc;
Class ResponseJson{
/**
* @param null $data
* @param int $httpCode
* @return \think\response\Json
*/
public static function success($data = null, $httpCode = 200){
$status = ApiErrDesc::SUCCESS[0];
$message = ApiErrDesc::SUCCESS[1];
return $this->jsonResponse($status, $message, $data, $httpCode);
}
/**
* @param $status
* @param $message
* @param null $data
* @param int $httpCode
* @return \think\response\Json
*/
public static function error($status, $message, $data = null, $httpCode = 500){
return $this->jsonResponse($status, $message, $data, $httpCode);
}
/**
* @param $status
* @param $message
* @param $data
* @param int $httpCode
* @return \think\response\Json
*/
private function jsonResponse($status, $message, $data, $httpCode = 200){
$result = [
'status' => $status, // 业务状态码
'message' => $message,
'result' => $data
];
return json($result, $httpCode);
}
}
控制器调用
/**
* 老王
*
**/
namespace app\api\controller;
use app\BaseController;
use app\common\lib\ResponseJson;
class Test extends BaseController
{
public function index(){
ResponseJson::success(['test' => 'test']);
ResponseJson::error(1, '未知错误');
}
}
/**
* 老王
*
**/
namespace app\common\lib;
use app\common\lib\error\ApiErrDesc;
Trait ResponseJson{
/**
* @param null $data
* @param int $httpCode
* @return \think\response\Json
*/
public function success($data = null, $httpCode = 200){
$status = ApiErrDesc::SUCCESS[0];
$message = ApiErrDesc::SUCCESS[1];
return $this->jsonResponse($status, $message, $data, $httpCode);
}
/**
* @param $status
* @param $message
* @param null $data
* @param int $httpCode
* @return \think\response\Json
*/
public function error($status, $message, $data = null, $httpCode = 500){
return $this->jsonResponse($status, $message, $data, $httpCode);
}
/**
* @param $status
* @param $message
* @param $data
* @param int $httpCode
* @return \think\response\Json
*/
private function jsonResponse($status, $message, $data, $httpCode = 200){
$result = [
'status' => $status, // 业务状态码
'message' => $message,
'result' => $data
];
return json($result, $httpCode);
}
}
控制器调用
/**
* 老王
*
**/
namespace app\api\controller;
use app\BaseController;
use app\common\lib\ResponseJson;
class Test extends BaseController
{
use ResponseJson;
public function index(){
$this->success(['test' => 'test']);
$this->error(1, '未知错误');
}
}
web是基于浏览器, 可以采用该方式, 但是APP不是基于浏览器cooike和session的机制, 需要采用JWT
JWT原理即服务端认证以后, 生成一个JSON对象, 返回给客户端, 后续客户端所有的请求都必须带上这个JSON对象, 而服务端依靠这个JSON来认定用户身份
完整的JWT格式输出是以 . 分割的三段Base64编码
组成
JWT的官网 JWT
找到PHP的库, 利用composer (PHP用来管理依赖关系的工具) 下载包文件lcobucci/jwt
切换到项目目录执行composer
composer require lcobucci/jwt
在app/common/auth文件下, 创建JWT封装类库文件
/**
* 老王
*
**/
namespace app\common\lib\auth;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\ValidationData;
/**
* 单例模式
* 一次请求中所有使用到jwt的地方都是一个用户
* Class JwtAuth
* @package app\common\lib\auth
* 1. 接口鉴权
* 2. 获取用户身份
*/
class JwtAuth
{
/**
* @var
*/
private $token;
/**
* @var
*/
private $decodeToken;
/**
* claim iss
* @var string
*/
private $iss = 'chao.com';
/**
* claim aud
* @var string
*/
private $aud = 'tp6_server_app';
/**
* 身份 uid
* @var string
*/
private $uid;
/**
* @var string
*/
private $secret = 'TP6&*chao1992#$LJL*&^&*9089';
/**
* @var null
*/
private static $instance = null;
/*
* 私有化 构造函数
*/
private function __construct()
{
}
/*
*
*/
private function __clone()
{
// TODO: Implement __clone() method.
}
/**
* 单例模式 获取jwtAuth句柄
* @return JwtAuth|null
*/
public static function getInstance()
{
if (is_null(self::$instance)){
self::$instance = new self();
}
return self::$instance;
}
/**
* @param $uid
* @return $this
* 设置身份信息
*/
public function setUid($uid){
$this->uid = $uid;
return $this;
}
/**
* @param $token
* 设置token
*/
public function setToken($token){
$this->token = $token;
return $this;
}
/**
* @param $token
*/
public function decode(){
if (!$this->decodeToken){
// 把字符串转成Token对象
$this->decodeToken = (new Parser())->parse((string)$this->token);
$this->uid = $this->decodeToken->getClaim('uid');
}
return $this->decodeToken;
}
/**
* 校验signature, 判断token是否过期或者被篡改
* @return bool
*/
public function verify(){
$signer = new Sha256();
$privateKey = new Key($this->secret);
$result = $this->decode()->verify($signer, $privateKey);
return $result;
}
/**
* 校验参数
* @return bool
*/
public function validate(){
$data = new ValidationData();
$data->setIssuer($this->iss);
$data->setAudience($this->aud);
return $this->decode()->validate($data);
}
/**
* @return $this
*/
public function encode(){
$signer = new Sha256();
$privateKey = new Key($this->secret);
$time = time(); // 颁发时间
$this->token = (new Builder())
->withHeader('alg', 'HS256')
->issuedBy($this->iss)
->permittedFor($this->aud)
->issuedAt($time)
->expiresAt($time + 3600) // 过期时间
->withClaim('uid', $this->uid) // 自定义参数
->getToken($signer, $privateKey);
return $this;
}
/**
* 获取token
* @return string
*/
public function getToken(){
return (string)$this->token;
}
/**
* @return string
*/
public function getUid(){
return $this->uid;
}
}
控制器调用
/**
* 老王
*
**/
namespace app\api\controller;
use app\BaseController;
use app\common\lib\auth\JwtAuth;
use app\common\lib\ResponseJson;
class Test extends BaseController
{
use ResponseJson;
public function index(){
// 获取jwtAuth的句柄
$jwtAuth = JwtAuth::getInstance();
$token = $jwtAuth->setUid(1)->encode()->getToken();
$this->success(['token' => $token]);
}
}
中间件主要用于拦截或者过滤应用的HTTP请求, 并进行必要的业务处理
在app/api模块下, 新建middleware文件夹, 然后在该文件夹下创建类
/**
* 老王
*
**/
namespace app\api\middleware;
use app\api\exception\ApiException;
use app\common\lib\auth\JwtAuth;
use app\common\lib\error\ApiErrDesc;
use app\common\lib\ResponseJson;
/**
* Class CheckAuth
* @package app\api\middleware
* 中间件
*/
class CheckAuth
{
/**
* @param $request
* @param \Closure $next
*/
public function handle($request, \Closure $next){
$token = $request->header('token');
if ($token){
// 校验
$jwtAuth = JwtAuth::getInstance();
$jwtAuth->setToken($token);
if ($jwtAuth->validate() && $jwtAuth->verify()){
return $next($request);
}else{
throw new ApiException(ApiErrDesc::ERR_LOGIN);
}
}else{
throw new ApiException(ApiErrDesc::ERR_PARAMS);
}
}
}
注册中间件
在app/api模块下, 新建middleware.php文件
// 该模块下的中间件定义文件, 对该模块下的所有控制器都有效, 在middleware下写好中间件后, 需要在该文件下配置绑定
// 如果使得该中间件只针对某一个控制器有效, 可以借助路由来设置, 不需要在该文件下配置绑定, 直接在middleware下写好中间件然后通过路由绑定->middleware()
return [
app\api\middleware\CheckAuth::class
];
错误码是用来描述当前接口处理的结果, 是前后端共同的约束
格式:
一般以配置文件( config/status.php )或者类的常量来定义错误码, 统一管理便于维护
在app/common/lib文件夹下新建error, 并在该文件夹下创建类库
/**
* 老王
*
**/
namespace app\common\lib\error;
/**
* Class ApiErrDesc
* @package app\common\lib\error
* APi返回码类库
*/
class ApiErrDesc
{
/**
* Api通用错误码
* error_code < 1000
*/
const SUCCESS = [1, 'Success'];
const UNKNOWN_ERR = [0, '未知错误'];
const ERR_URL = [2, '请求接口不存在'];
const ERR_PARAMS = [100, '参数错误'];
/**
* 用户登录相关的错误码
* error_code 1000-1100
*/
const UNKNOWN_USER = [1001, '用户不存在'];
const ERR_PASSWORD = [1002, '密码错误'];
const ERR_LOGIN = [1000, '登录过期'];
}
try…catch 可以捕获异常
set_exception_handler set_error_handler设置用户自定义的异常处理函数, 用于没有用try/catch块来捕获的异常
api模块是与客户端交互的接口类库, 出现异常需要按照接口的输出格式返回给客户端, 便于客户端进行操作, TP6中app/ExceptionHandler.php是系统自带的应用异常处理类, 通过页面形式输出错误信息, 但这种并不适用api接口
所以需要在app/api/exception文件夹下新建异常处理的类
自定义接收异常并格式化输出类
/**
* 老王
*
**/
namespace app\api\exception;
use think\exception\Handle;
use think\Response;
use Throwable;
use app\common\lib\ResponseJson;
use app\common\lib\error\ApiErrDesc;
/**
* Class ApiHandler
* @package app\api\exception
* api模块下的异常处理类
*/
class ApiHandle extends Handle
{
protected $httpCode = 500;
/**
* Render an exception into an HTTP response.
*
* @access public
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
if ($e instanceof ApiException){
$status = $e->getCode();
$message = $e->getMessage();
}else{
$status = $e->getCode();
if(!$status || $status < 0){
$status = ApiErrDesc::UNKNOWN_ERR[0];
}
$message = $e->getMessage() ?: ApiErrDesc::UNKNOWN_ERR[1];
}
// 添加自定义异常处理机制
if (method_exists($e, 'getStatusCode')){
$this->httpCode = $e->getStatusCode();
}
return ResponseJson::error($status, $message, null, $this->httpCode);
}
}
自定义抛出异常的类
/**
* 老王
*
**/
namespace app\api\exception;
use think\Exception;
use Throwable;
class ApiException extends Exception
{
public function __construct(array $apiErrConst, Throwable $previous = null)
{
$message = $apiErrConst[1];
$code = $apiErrConst[0];
parent::__construct($message, $code, $previous);
}
}
后边在控制器中返回错误错误信息, 就可以直接调用ApiException抛出异常, 再由ApiHandle中的handle方法接管异常信息, 并格式化输出