Skip to content

服务端开发指南

本篇将介绍基于 ThinkPHP 8 的项目服务端的基础目录结构、执行流程、以及常用基础模块类库(控制器、中间件、列表类等)的规范与用法,帮助您快速熟悉和开展后端的二次开发。


目录结构

服务端所有的核心逻辑存放在项目的 server/ 文件夹内:

text
├─📂 server                   // 服务端根目录(管理后台、接口)
│  ├─📂 app                   // 核心应用目录
│  │  ├─📂 adminapi           // 管理后台接口模块
│  │  │  ├─📂 config          // 模块独立配置
│  │  │  ├─📂 controller      // 控制器
│  │  │  ├─📂 http            // 中间件等
│  │  │  ├─📂 listener        // 事件监听
│  │  │  ├─📂 lists           // 列表分页类
│  │  │  ├─📂 logic           // 核心逻辑层
│  │  │  ├─📂 service         // 可复用的业务独立服务层
│  │  │  ├─📂 validate        // 验证器类
│  │  ├─📂 api                // Web端/移动端接口模块
│  │  ├─📂 common             // 多模块公共方法与基类
│  ├─📂 public                // WEB对外访问目录
│  │  ├─📄 index.php          // 框架入库文件
│  │  ├─📂 admin              // 已编译的后台前端发布代码入口
│  ├─📄 .env                  // 数据库配置文件与环境变量

运行流程

项目采用彻底的前后端分离设计,服务端只处理数据处理并返回 RESTful 类型的 JSON 数据接口。默认的接口访问 URI 路由映射模式为:

http://服务器或本地域名/模块名称/控制器名称/控制器方法访问

一套基础的生命执行流程如下:

  1. 接口请求:前端携附带 Token 等发来路由请求。
  2. 进入应用模块:框架匹配到对应的 app/模块名 (如 adminapi)。
  3. 经过中间件:顺序通过模块或全局设定的拦截中间件(如身份防伪、限流)。
  4. 进入控制器:在控制器对传参进行接收分发。
  5. 进入验证类/逻辑层/列表类/服务层:如果有业务新增数据,先走一遍 validate 类校验安全,随后转交 logic 等层面执行复杂计算。如果是获取查询列表页,调用专门的 lists 类型实例操作。
  6. 响应返回:将返回给前端的数据结构进行统一封装,抛接出结果体。

流程图示:

text
接口请求 --> 进入应用模块 --> 经过安全校验中间件 --> 进入控制器 --> [ 数据验证类验参 --> 逻辑层\列表层\基础服务层 ] --> 控制器拼接执行成功标记响应 -> 返回最终格式结果

模块划分

根据 ThinkPHP 8.0 开发规范,每个具体应用的逻辑单独置于 server/app/某个模块 目录下区隔开。现阶段项目分为以下关键内置模块:

  • adminapi:给 PC 端管理后台(Vue Admin)访问请求的数据接口。
  • api:对外公共移动端或前台侧(Nuxt Web端)访问数据的 API 接口。

控制器 (Controller)

访问

控制器的代码应当编写在模块下对应的 controller 文件夹(例 server/app/adminapi/controller/ 下)。您可以自由再往下根据功能细分文件夹嵌套使用。 访问形式为 "http://域名/模块名称/控制器文件名称/具体写的方法名"。如果是带有二级目录,可以为 "http://域名/模块名称/具体包名.控制器文件名称/方法名" 等框架允许规范。

继承与属性

大多数开发下,我们需要继承项目预留的基础控制器类。 例如在管理后台接口的开发时继承 BaseAdminController(具体类名请根据您的核心组件包为准),系统会在内部执行登录验证并在子类暴露出管理员的核心身份属性。你可以直接用指针提取使用:

php
<?php
namespace app\adminapi\controller;

// 请引入实际项目中定义的基础控制器命名空间路径
use core\base\BaseAdminController;

class TestController extends BaseAdminController
{
    // 需要登录身份才能执行的接口
    public function index()
    {
        $admin_id = $this->adminId;   // 当前发出请求的管理员ID
        $profile = $this->adminInfo;  // 当前管理员的所有权限、属性等详细数组
    }
}

登录与白名单控制

如果是无需判断 JWT Token 也必须放行的接口操作,譬如"账号登录功能主接口"或者基础配置获取,我们可以在控制器内定义一个 $notNeedLogin 数组来忽略安全核验:

php
<?php
namespace app\adminapi\controller;
use core\base\BaseAdminController;

class LoginController extends BaseAdminController
{
    // 写入这里的方法名,将不被基础控制器的安全中间件阻挡
    public array $notNeedLogin = ['account', 'logout'];

    public function account()
    {
        //…… 登录验证密码分发 Token 相关处理
    }
}

统一响应

为规范前后端对接的数据契约标准,成功和失败的固定包体格式大致必须包裹如下:

json
{
  "code": 1,
  "show": 0,
  "msg": "执行通过,返回对应数据",
  "data": {
    "lists": [],
    "count": 0,
    "page_no": 1,
    "page_size": 15,
    "extend": []
  }
}

故您可以通过基类提供的语法糖(便捷封装方法)返回:

php
<?php
namespace app\adminapi\controller;
use core\base\BaseAdminController;

class TestController extends BaseAdminController
{
    public function index()
    {
        return $this->success('成功语录');       // 表示仅执行业务成功
        return $this->fail('操作异常');          // 返回警告或拦截停止的代码
        return $this->data(['key' => 'val']);  // 带纯数据的主体返回
        return $this->dataLists();             // 与"列表类"方法混用,返回含有独立分页规范的列表数据
    }
}

验证类 (Validate)

在系统接收 Request 请求的各种零散参数后,如果马上组装进数据库是非常致命的(存在字段攻击、遗漏),我们要求使用验证器统一强过滤参数。

实现步骤

  1. validate 目录新建业务专属验证继承类 BaseValidate
  2. 控制器中实例化后,直接通过封装方法如 goCheck($scene) 调用。

定义验证规则示例:

php
<?php
namespace app\adminapi\validate\auth;

use core\base\BaseValidate;

class AdminValidate extends BaseValidate
{
    protected $rule = [
        'name' => 'require|max:20',
    ];

    protected $message = [
        'name.require' => '名称不能为空',
        'name.max' => '名称不得超过20个字符',
    ];

    // 新增时指定针对性的场景方法名
    public function sceneAdd()
    {
        return $this->only(['name']);
    }
}

控制器中执行验参拦截并接收安全参数:

php
public function add()
{
    // post()表示针对请求过来的 body 类型参数做拦截校验
    // goCheck("add") 结合在验证类写好的 sceneAdd 定义规则强验,如果不符合上面 `$message` 报错信息会直接向前端顶穿退出;符合则返回安全合规的关联数组数据供逻辑进一步组装使用。
    $params = (new AdminValidate())->post()->goCheck('add');
    
    // ...往下再调用 logic 追加数据库写入逻辑...
}

分页列表类 (Lists)

处理大量的列表、数据导出、排序,如果仍将长代码写在控制器里则不利于阅读。项目引入了 Lists 规范。

实现步骤

  1. 在模块下找到 lists 目录,并新建列表类并继承基础功能如 BaseAdminDataLists(带有具体使用分页规范与返回格式);如果对数据还需要搜索要求,就实现接口(implements ListsSearchInterface)重写特定方法。
  2. 控制器中将实例以参数塞进 $this->dataLists()

开发示例

php
<?php
namespace app\adminapi\lists\auth;

use core\base\BaseAdminDataLists;
use core\lists\ListsSearchInterface;

class AdminLists extends BaseAdminDataLists implements ListsSearchInterface
{
    // 定义可以向前端放开的搜索匹配条件字段规则
    public function setSearch(): array
    {
        return [
            '%like%' => ['name', 'account'],
        ];
    }

    // 重写获取当前切割片查询后的列表数据条目
    public function lists(): array
    {
        // $this->searchWhere, $this->limitOffset等来自于基类里系统底层帮你计算完毕得到的结果直接使用即可。
        return Admin::where($this->searchWhere)
            ->limit($this->limitOffset, $this->limitLength)
            ->select()
            ->toArray();
    }

    // 根据条件记录数据表此时满足条件的全部数据行总个数总和
    public function count(): int
    {
        return Admin::where($this->searchWhere)->count();
    }
}

极其简化的控制器引入列表与查询逻辑:

php
public function lists()
{
    // 将整个查询过程委托给对应的 List类实例处理并以统一 Json 格式对外分发。
    return $this->dataLists(new AdminLists());
}