软件系统定制开发使用thinkphp6搭建后端api接口流程

1、下载tp6

首先搭建wamp,或lamp软件系统定制开发的环境安装composer,通过composer安装tp6,thinkphp软件系统定制开发官网已经不再支持直接下载。

composer create-project topthink/think tp6
  • 1

软件系统定制开发在下载好的tp6目录通过cmd软件系统定制开发命令窗口输入(软件系统定制开发如果绑定了域名,软件系统定制开发直接用域名方面,忽略这段)

php think run
  • 1

软件系统定制开发在浏览器中输入127.0.0.1:8000,软件系统定制开发访问到如下页面就安装成功了

2、软件系统定制开发打开错误调试

1.找到config/app.php下的show_error_msg ,改成true

// 软件系统定制开发显示错误信息'show_error_msg'   => true,
  • 1
  • 2

2.软件系统定制开发找到下面根目录下的.example.env文件,软件系统定制开发重命名此文件,把.example删掉

软件系统定制开发查看里面的代码,会发现,它打开了app_debug调试

3、软件系统定制开发隐藏入口文件

软件系统定制开发如果什么都不填,软件系统定制开发默认访问的就是index控制器,在config/app.php软件系统定制开发文件中有这样的定义,软件系统定制开发你也可以修改默认的控制器

// 默认应用'default_app'      => 'index',
  • 1
  • 2

还有,不管访问任何控制器,如果没有填方法,它都会访问控制器中的index方法,如果index方法不存在,则提示错误信息-方法不存在。
通过在项目根目录中运行的php think run开启的web服务,tp6帮我们做了隐藏入口文件的操作,所以你可以通过第三种方式访问。但是我们这一节要说的就是隐藏入口,怎么能用tp6自带的web服务呢。所以要自己来。
我们在开发时,往往会在本地搭建WNMP等这样的一套web解决方案,这就需要我们自己去隐藏入口文件index.php

为什么要隐藏入口文件?

  1. 因为像这样子http://127.0.0.1:4321/index.php/index/index访问方法,这个index.php很不好看。
  2. 多余。
  3. 危险

实现隐藏index.php很简单,只需要找到public目录下的.htaccess文件,添加如下代码就可以了。
Apache版本:

<IfModule mod_rewrite.c> #如果mode_rewrite.c模块存在 则执行以下命令  Options +FollowSymlinks -Multiviews  RewriteEngine On #开启 rewriteEngine  # !-d 不是目录或目录不存在  RewriteCond %{REQUEST_FILENAME} !-d   # !-f 不是文件或文件不存在  RewriteCond %{REQUEST_FILENAME} !-f   RewriteRule ^(.*)$ index.php [QSA,PT,L]  # 参数解释  # ^(.*)$: 匹配所有的路口映射  # QSA: (Query String Appending)表示保留参数入get传值?xxx==xx;  # PT: 把这个URL交给Apache处理;  # L: 作为最后一条,遇到这条将不再匹配这条之后的规则</IfModule>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Nginx版本:

location / {  if (!-e $request_filename) {    rewrite  ^(.*)$  /index.php?s=/$1  last;  }}
  • 1
  • 2
  • 3
  • 4
  • 5

4、解决跨域问题

在应用开发中,前后端都是分开独立开发的,而前后端通常都会自己搭建一个web服务,运行在不同的端口上,在前端访问后端的接口时,会报跨域的错误。而这种跨域问题通常是要有后端来处理的,tp6有专门的中间件来做这个事情,真是太方便了,只需要在app目录下的middleware.php中添加该中间件,就实现了跨域访问。

<?php// 全局中间件定义文件return [    // 全局请求缓存    // \think\middleware\CheckRequestCache::class,    // 多语言加载    // \think\middleware\LoadLangPack::class,    // Session初始化    // \think\middleware\SessionInit::class,    // 跨域解决    \think\middleware\AllowCrossDomain::class,];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5、路由解决版本控制

在app目录中的container控制器中新建两个文件夹v1,v2,在其中都创建User.php文件

v1/User.php

<?phpnamespace app\controller\v1;use app\BaseController;class User extends BaseController {    public function login() {        return '这是v1接口';    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

v2/User.php

<?phpnamespace app\controller\v2;use app\BaseController;class User extends BaseController {    public function login() {        return '这是v2接口';    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意上面两个文件的命名空间,就第一行代码,在哪个文件夹下,就写到哪里。
现在方法有了,我们还无法访问,需要使用路由,让路由帮我们找对应的方法。

至于路由的概念去文档自己看。我这里主要用路由组的方式,我觉得这个比资源路由好用,灵活。

在根目录下的route目录下的app.php文件代码如下:

// +----------------------------------------------------------------------// | ThinkPHP [ WE CAN DO IT JUST THINK ]// +----------------------------------------------------------------------// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.// +----------------------------------------------------------------------// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )// +----------------------------------------------------------------------// | Author: liu21st <liu21st@gmail.com>// +----------------------------------------------------------------------use think\facade\Route;// api版本控制$v = request()->header('Api-Version');// 默认api版本为v1if ($v == null) {    $v = 'v1';}Route::group('user', function () {    Route::post('login', 'login');})->prefix($v.'.user/')->pattern(['id' => 'd+']);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

以上代码进行控制api版本的方式是,请求发起者在header中传递要访问的api的版本,这里获取到对应的版本,访问对应的方法。

鉴于以上我使用的是post请求,且要传递header,所以使用ApiPost / postman进行测试。
访问v1版本的接口时:

访问v12版本的接口时:

6、jwt token验证

thinkphp常用的扩展插件请参考:

composer require thans/tp-jwt-auth
  • 1

安装完成后,该插件所在的位置在根目录下的vendor/thans/tp-jwt-auth
还会在根目录下的config目录下生成jwt.php文件来记录一些配置信息

看这里都是读取的env中的参数,所以咱也在根目录下的.env文件中配置参数。
在根目录下打开cmd窗口,执行

php think jwt:create
  • 1

会帮你在.env文件中生成密钥secret,红色框中的是新增的内容

token的有效期为60秒,为了方便我们测试,我就不改了,如果你要改,可以在.env中添加,这样就改成了半小时

这个插件有三种方式【header,token,param】传递token,我就使用其中一个,也是最常用的一种,就是在【header】中传递token信息,这个插件默认验证header中的token信息需要传递的参数名为authorization,而在header中直接传递该参数tp6是获取不到的,需要做一些设置,
在根目录中的public目录下的.htacccess文件中添加(Nginx好像不需要添加也能正常获取到Authorization)

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
  • 1
<IfModule mod_rewrite.c> #如果mode_rewrite.c模块存在 则执行以下命令  Options +FollowSymlinks -Multiviews  RewriteEngine On #开启 rewriteEngine  # !-d 不是目录或目录不存在  RewriteCond %{REQUEST_FILENAME} !-d   # !-f 不是文件或文件不存在  RewriteCond %{REQUEST_FILENAME} !-f   RewriteRule ^(.*)$ index.php [QSA,PT,L]  # 参数解释  # ^(.*)$: 匹配所有的路口映射  # QSA: (Query String Appending)表示保留参数入get传值?xxx==xx;  # PT: 把这个URL交给Apache处理;  # L: 作为最后一条,遇到这条将不再匹配这条之后的规则  # 增加下面这项,否则在header中会获取不到Authorization  SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0</IfModule>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

那么现在开始测试:
(1).生成token
我就在之前创建的v1/User.php控制器中写了

<?phpnamespace app\controller\v1;use app\BaseController;use thans\jwt\facade\JWTAuth;class User extends BaseController {    public function login() {        $token = JWTAuth::builder(['uid' => 1, 'name' => '测试1']);        return $token;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在ApiPost / postman中测试

(2).验证token
我使用的是路由中间件的方式验证token,
① 写一个中间件
在根目录下的app目录中创建middleware目录,在其下创建CheckToken.php文件
app/middleware/CheckToken.php

<?phpnamespace app\middleware;use thans\jwt\exception\JWTException;use thans\jwt\facade\JWTAuth;class CheckToken {    public function handle($request, \Closure $next) {        // OPTIONS请求直接返回        if ($request->isOptions()){            return response();        }        try {            JWTAuth::auth();        }catch (JWTException $e){            return json($e->getMessage());        }        return $next($request);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

② 起别名
给该中间件起个别名,在根目录下的config/middleware.php文件中

<?php// 中间件配置return [    // 别名或分组    'alias'    => [        'CheckToken' => app\middleware\CheckToken::class    ],    // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行    'priority' => [],];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

③ 在路由文件中使用中间件 route/app.php

Route::group('user', function () {    Route::post('login', 'login');    Route::post('userInfo', 'getUserInfo')->middleware('CheckToken');})->prefix($v.'.user/')->pattern(['id' => 'd+']);
  • 1
  • 2
  • 3
  • 4

④ 创建对应的方法
在第三步中我们创建了一个getUserInfo()方法,现在在User.php文件中创建

public function getUserInfo() {   return json(['id'=>1, 'name'=> '啦啦啦']);}
  • 1
  • 2
  • 3

⑤ 验证一下
刚刚创建的token必然过期了,咱重新获取一条
现在验证一下,请求userinfo方法,并在header中添加参数Authorization,
注意:token值需要加上bearer ,bearer后的空格也要的。

过了一分钟后,我们再来试一试

可以看到token验证提示,该通过过期了,这个插件成功了,并没有继续往下走,把之前的信息返回。

(3).刷新token,会将旧token加入黑名单

JWTAuth::refresh();//刷新token,会将旧token加入黑名单
  • 1

(4).注销或拉黑token

JWTAuth::invalidate($token);
  • 1

这个拉黑的具体操作就是把你要注销的token保存在本地的cookie中,默认的保存时间是14天,14天后cookie会自己删除的,你可以在根目录下的runtime目录下的cache目录中找到对应的文件,我就不测试这个方法了,我感觉这个操作好像没什么必要。
(5).验证Token是否为黑名单

JWTAuth::validate($token);
  • 1

7、统一的参数返回形式

实际开发中,后端返回给前端的参数往往都是这样的。

所以我们需要对参数返回形式做个统一的处理
在app目录下的common.php中定义的方法全局都可调用,所以在这个文件中定义此方法。

<?phpuse think\Response;// 应用公共文件// 统一返回数据格式function result($data = [], string $msg = 'error', int $code = 200, string $type = 'json') {    $result = [        'code' => $code,        'msg' => $msg,        'data' => $data    ];    // 调用Response的create方法,指定code可以改变请求的返回状态码    return Response::create($result, $type)->code($code);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

哪里用的时候直接调用即可
以下为常用状态码:
200 请求成功
204 请求成功,未返回实体,比如option请求
400 错误的请求
401 认证失败,这个一般在token验证那里
403 拒绝访问
404 请求的资源不存在
413 请求实体太大
422 参数验证错误
500 服务器错误

8、异常捕捉

tp6异常捕获

(1)参数验证错误捕捉
我们先写一个参数验证的类,在app目录下创建validate目录,创建User.php文件
app/validate/User.php

<?phpnamespace app\validate;use think\Validate;class User extends Validate {    protected $rule = [        'name' => 'require|max:25',        'age' => 'number|between:1,120',        'email' => 'email'    ];    protected $message = [        'name.require' => '名称必须',        'name.max'     => '名称最多不能超过25个字符',        'age.number'   => '年龄必须是数字',        'age.between'  => '年龄只能在1-120之间',        'email'        => '邮箱格式错误',    ];}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

tp6的异常捕捉分为两种,自动和手动的,手动的就是通过try{}catch{}捕捉。tp6的异常捕捉大多是自动的,不过,比如我们现在要操作的参数验证错误就需要自己去捕捉来抛出异常,我们此节的目的是统一捕捉这个错误,我就不用手动的了。
我们就在异常处理类的render方法中添加这个捕捉抛出就可以了。

// 1.参数验证错误if($e instanceof ValidateException){     return result($e->getError(), '参数验证不通过', 422);}
  • 1
  • 2
  • 3
  • 4

现在在方法中一下,看看能否捕获。
app/controller/v1/User.php


如果验证通过了,就会正常的走下去,则会显示我return的测试内容

(2)未匹配到资源或方法的异常捕获

<?phpnamespace app;use ParseError; // 语法错误use TypeError;use InvalidArgumentException;use think\db\exception\DataNotFoundException;use think\db\exception\ModelNotFoundException;use think\db\exception\PDOException; // 数据库连接错误use think\db\exception\DbException; // 数据库模型访问错误,比如方法不存在use think\exception\RouteNotFoundException;use think\exception\ClassNotFoundException;use think\exception\FuncNotFoundException;use think\exception\FileException;use think\exception\Handle;use think\exception\HttpException;use think\exception\HttpResponseException;use think\exception\ValidateException;use think\Response;use Throwable;/** * 应用异常处理类 */class ExceptionHandle extends Handle {    /**     * 不需要记录信息(日志)的异常类列表     * @var array     */    protected $ignoreReport = [        HttpException::class,        HttpResponseException::class,        ModelNotFoundException::class,        DataNotFoundException::class,        ValidateException::class,    ];    /**     * 记录异常信息(包括日志或者其它方式记录)     *     * @access public     * @param Throwable $exception     * @return void     */    public function report(Throwable $exception): void {        // 使用内置的方式记录异常日志        parent::report($exception);    }    /**     * 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 HttpException && $request->isAjax()) {            return response($e->getMessage(), $e->getStatusCode());        }        // 使用了错误的数据类型 或 缺失参数        if ($e instanceof InvalidArgumentException || $e instanceof ErrorException) {            $fileUrlArr = explode(DIRECTORY_SEPARATOR, $e->getFile());            $data = [                'err_msg' => $e->getMessage(),                'file' => $fileUrlArr[count($fileUrlArr) - 1],                'line' => $e->getLine()            ];            return result($data, '参数错误', 413);        }        // 1.参数验证错误        if ($e instanceof ValidateException) {            return result($e->getError(), '参数验证不通过', 422);        }        // 2.方法(控制器、路由、http请求)、资源(多媒体文件,如视频、文件)未匹配到,        // 一旦在定义的路由规则中匹配不到,它就会直接去匹配控制器,但是因为在控制器中做了版本控制v1,v2这样的,所以它是无法获取对应控制器的        // 所以都会直接走了HttpException的错误        // 感觉好像也无所谓,反正是做api接口的,只不过这样就不好准确的提示信息了        // 到底这个请求时控制器找不到呢?还是方法找不到?还是请求类型(get,post)不对?        if (($e instanceof ClassNotFoundException || $e instanceof RouteNotFoundException) || ($e instanceof HttpException && $e->getStatusCode() == 404)) {            $data = [                'err_msg' => $e->getMessage(),                'tip_1' => '请检查路径是否填写正确',                'tips_2' => '请检查请求类型是否正确',            ];            return result($data, '方法或资源未找到,请检查', 404);        }        // 3.语法错误        if ($e instanceof ParseError) {            $fileUrlArr = explode(DIRECTORY_SEPARATOR, $e->getFile());            $data = [                'err_msg' => $e->getMessage(),                'file' => $fileUrlArr[count($fileUrlArr) - 1],                'line' => $e->getLine()            ];            return result($data, '服务器异常-语法错误', 411);        }        // 4.数据库错误        if ($e instanceof PDOException || $e instanceof DbException) {            $fileUrlArr = explode(DIRECTORY_SEPARATOR, $e->getFile());            $data = [                'err_msg' => $e->getMessage(),                'file' => $fileUrlArr[count($fileUrlArr) - 1],                'line' => $e->getLine()            ];            return result($data, '服务器异常-数据库错误', 412);        }        // 其他错误交给系统处理        return parent::render($request, $e);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

9、自动生成api文档

(1)安装插件

composer require hg/apidoc
  • 1

(2)下载对应的前端页面
请根据你安装的apidoc版本 点击下载 对应的前端文件

Apidoc版本GithubGitee(国内推荐)
v3.1.0 - v3.1.5
v3.0.0 - v3.0.8

下载完成后解压,将apidoc文件夹拷贝到你的项目 public 目录下

打开浏览器访问 http://你的域名/apidoc/ ,出现接口文档页面,表示安装成功。

(3)使用
具体配置你还得看文档,我就直接照着最简单的做了,
我就试一个,将app/controller/v1/User.php写了注释,它会读注释生成接口文档

① 引入注释
app/controller/v1/User.php

<?phpnamespace app\controller\v1;use app\BaseController;use thans\jwt\facade\JWTAuth;use app\validate\User as UserValidate;// 添加这句,注释写法为 @Apidoc参数名(...)use hg\apidoc\annotation as Apidoc;/** * @ApidocTitle("V1") * @ApidocGroup("base") */class User extends BaseController {    /**     * @ApidocTitle("登录")     * @ApidocUrl("v1.user/login")     * @ApidocTag("测试 基础")     * @ApidocParam("username", type="string",require=true, desc="用户名" )     * @ApidocParam("password", type="string",require=true, desc="密码" )     * @ApidocReturned("id", type="int", desc="新增用户的id")     */    public function login() {        //数据验证,batch开启批量验证        validate(UserValidate::class)->batch(true)->check([            'name' => 'dongsir',            'email' => 'dongsir@qq.com'        ]);        return result(null, '成功', 200);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

② 查看效果

关于这个多应用/多版本的配置项,去apidoc的文档去看吧,在config/apidoc.php修改apps的配置就可以了,然后就可以通过右上角的选择框切换版本了
// 设置应用/版本(必须设置)

  'apps'           => [        [            'title'=>'演示示例',            'path'=>'app',            'folder'=>'controller',            'items'=>[                ['title'=>'V1.0','path'=>'appcontroller1','folder'=>'v1'],                ['title'=>'V2.0','path'=>'appcontroller2','folder'=>'v2']            ]        ],    ],
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发