Published on

核心协议:写给前端工程师的 ThinkPHP 后端开发指南

Authors

1. PHP 基础语法速查(对比 JavaScript)

如果你有 JavaScript 基础,PHP 的语法上手很快。以下是最常见的差异对照:

| 概念         | JavaScript                  | PHP                                |
|-------------|-----------------------------|------------------------------------|
| 变量声明     | let name = "kiro"           | $name = "kiro";                    |
| 字符串拼接   | `Hello ${name}`             | "Hello {$name}"'Hello '.$name |
| 数组         | [1, 2, 3]                   | [1, 2, 3](同样用方括号)            |
| 对象/关联数组 | { key: "value" }            | ['key' => 'value']                 |
| 箭头函数     | (x) => x * 2               | fn($x) => $x * 2PHP 7.4+|
| 匿名函数     | function(x) { return x }   | function($x) { return $x; }       |
| 类访问成员   | obj.method()                | $obj->method()                     |
| 静态方法调用  | Class.method()              | Class::method()                    |
| 导入模块     | import X from './x'         | use app\v18\model\Users;           |
| 空值合并     | value ?? 'default'          | $value ?? 'default'(完全一样)      |
| 类型判断     | typeof x === 'string'       | is_string($x)                      |
| 打印调试     | console.log(x)              | var_dump($x)writeLog($x, 'tag')|

PHP 特有的重要概念

<?php
// 1. 命名空间 —— 类似 JS 的模块系统
namespace app\v18\controller;  // 声明当前文件属于哪个"模块"
use app\v18\model\Users;       // 引入其他"模块"的类(类似 import)

// 2. 类和继承
class CommunityController extends BaseController
{
    // $this 指向当前实例(和 JS 的 this 一样)
    // -> 访问对象属性和方法(JS 用 .)
    $this->request->header('Access-Token');

    // :: 访问静态方法(JS 用 Class.method())
    JwtAuth::decodeToken($token);
    Users::where('uid', 1)->find();
}

// 3. 数组操作(PHP 的数组 = JS 的数组 + 对象)
$arr = [1, 2, 3];                    // 索引数组(类似 JS 数组)
$map = ['name' => 'kiro', 'age' => 1]; // 关联数组(类似 JS 对象)

// 常用数组函数
array_column($list, 'id');           // 类似 list.map(item => item.id)
array_flip($arr);                    // 值变成键,用于快速查找(类似 new Set())
array_merge($arr1, $arr2);           // 类似 [...arr1, ...arr2]
in_array($value, $arr);             // 类似 arr.includes(value)
implode(',', $arr);                  // 类似 arr.join(',')
explode(',', $str);                  // 类似 str.split(',')

2. ThinkPHP 核心概念(对比前端框架)

| ThinkPHP 概念      | 前端类比                        | 说明                              |
|--------------------|---------------------------------|----------------------------------|
| 路由 Route         | React Router / Vue Router       | URL 映射到处理函数                 |
| 控制器 Controller  | 页面组件 + 事件处理器             | 接收请求、调用服务、返回响应        |
| 模型 Model         | 数据层 / ORM(如 Prisma)        | 一个类对应一张数据库表              |
| 中间件 Middleware  | axios 拦截器                     | 请求到达控制器前/后的处理管道       |
| 验证器 Validate    | 表单校验(yup / zod)            | 参数校验规则                       |
| 服务层 Service     | 业务逻辑 hooks / composables     | 复杂业务逻辑的封装                 |
| 多应用 Multi-App   | 微前端 / monorepo                | 按版本隔离代码(v1, v2, ...v18)   |

项目目录结构

app/
├── v18/                          # API v18 版本(每个版本是独立的"应用"│   ├── controller/               # 控制器(处理请求)
│   │   └── CommunityController.php
│   ├── model/                    # 模型(数据库表映射)
│   │   ├── CommunityShare.php
│   │   ├── CommunityLike.php
│   │   ├── CommunityCategory.php
│   │   ├── History.php
│   │   └── Users.php
│   ├── route/                    # 路由定义
│   │   └── app.php
│   └── validate/                 # 验证器(可选)
├── common/                       # 公共模块(JWT、队列等)
│   └── JwtAuth.php
├── middleware/                    # 全局中间件
│   └── JwtAuthMiddleware.php
├── BaseController.php            # 控制器基类
└── common.php                    # 全局辅助函数(writeLog、getConfig 等)

3. 请求生命周期:一个请求是怎么跑起来的

当前端发起 GET /v18/api/community/feed?page=1&limit=20 时,ThinkPHP 内部的处理流程如下:

浏览器/App 发起 HTTP 请求
┌─ public/index.php(入口文件)
ThinkPHP 框架启动,加载配置
│       ↓
│   根据 URL 前缀 /v18 识别为 v18 应用
│       ↓
│   加载 app/v18/route/app.php 路由文件
│       ↓
│   匹配路由:/api/community/feed → CommunityController::feed()
│       ↓
│   检查是否有中间件(本接口无强制中间件)
│       ↓
│   实例化 CommunityController(自动注入 Request 对象)
│       ↓
│   调用 feed() 方法
│       ↓
│   方法内部:认证 → 取参 → 查数据库 → 格式化 → 返回 JSON
│       ↓
└─ 返回 HTTP 响应给前端

对比前端:这就像 Next.js 的 API Route,URL 路径决定执行哪个函数,函数返回 JSON。


4. 实战拆解:/api/community/feed 完整链路

4.1 路由层:URL 如何映射到代码

文件位置:app/v18/route/app.php

<?php
use think\facade\Route;

// Route::group 创建路由组,给子路由加统一的 URL 前缀
// 类似前端 React Router 的嵌套路由
Route::group("api", function () {                    // 前缀: /api
    Route::group("community", function () {          // 前缀: /api/community

        // 公开接口(不需要登录)
        Route::get('feed', 'CommunityController/feed');           // GET /api/community/feed
        Route::get('artwork', 'CommunityController/artworkDetail'); // GET /api/community/artwork
        Route::get('search', 'CommunityController/search');       // GET /api/community/search

        // 需要 JWT 认证的接口(注意 ->middleware() 链式调用)
        Route::post('like', 'CommunityController/like')
             ->middleware(\app\middleware\JwtAuthMiddleware::class);  // POST /api/community/like
    });
});

语法要点:

  • Route::get(路径, '控制器/方法') —— 定义 GET 请求路由
  • Route::post(路径, '控制器/方法') —— 定义 POST 请求路由
  • Route::group(前缀, 闭包) —— 路由分组,自动拼接 URL 前缀
  • >middleware(中间件类) —— 给路由挂载中间件(类似 axios 拦截器)
  • 'CommunityController/feed' —— ThinkPHP 会自动在当前版本的 controller/ 目录下找 CommunityController 类的 feed 方法

最终 URL 拼接规则:

完整路径 = /版本号/路由组前缀/路由路径
         = /v18    /api/community /feed

4.2 中间件层:请求的”安检门”

文件位置:app/middleware/JwtAuthMiddleware.php

中间件在请求到达控制器之前执行,用于做通用的前置检查(认证、限流、跨域等)。

<?php
namespace app\middleware;

use app\common\JwtAuth;

class JwtAuthMiddleware
{
    /**
     * 中间件处理方法
     *@paramRequest $request  当前请求对象
     *@paramClosure $next     下一个处理环节(控制器或下一个中间件)
     */
    public function handle(Request $request, Closure $next)
    {
        // 1. 从请求头获取 token
        $token = $request->header('access-token');

        // 2. 没有 token → 直接返回 401,请求不会到达控制器
        if (empty($token)) {
            return json(['code' => 401, 'msg' => '']);
        }

        // 3. 验证 token 是否有效
        $result = JwtAuth::checkToken($token);
        if ($result['code'] != 200) {
            return json($result);  // token 无效,返回错误
        }

        // 4. 解码 token,获取用户信息
        $Token = JwtAuth::decodeToken($token);

        // 5. 验证用户账号状态...

        // 6. 一切正常,放行请求到控制器
        return $next($request);  // ← 这行是关键!调用 $next 才会继续往下走
    }
}

对比前端理解:

// 前端 axios 拦截器(概念完全一样)
axios.interceptors.request.use((config) => {
    const token = localStorage.getItem('token');
    if (!token) throw new Error('Unauthorized');
    config.headers['Access-Token'] = token;
    return config;  // ← 类似 PHP 的 return $next($request)
});

注意:/api/community/feed 这个路由没有挂载 JwtAuthMiddleware,所以它是公开接口。 但控制器内部仍然会尝试读取 token,因为登录用户需要看到”是否已点赞”的状态。

4.3 控制器层:业务逻辑的入口

文件位置:app/v18/controller/CommunityController.php

控制器是整个接口的核心,负责:接收参数 → 处理业务 → 返回结果。

<?php
namespace app\v18\controller;          // 命名空间声明

use app\BaseController;                // 引入基类
use app\v18\model\CommunityShare;      // 引入模型(数据库表)
use app\v18\model\CommunityLike;
use app\v18\model\CommunityCategory;
use app\common\JwtAuth;                // 引入 JWT 工具

class CommunityController extends BaseController  // 继承基类
{
    public function feed()
    {
        try {
            // ========== 第1步:用户认证(非强制) ==========
            // 从请求头获取 Access-Token
            $Access_Token = $this->request->header('Access-Token', '');
            $uid = 0;
            if ($Access_Token) {
                // 解码 JWT,获取用户 ID
                $Token = JwtAuth::decodeToken($Access_Token);
                $uid = $Token['t_id'];  // t_id 是 JWT payload 中存储的用户 ID
            }
            // 此时 $uid = 用户ID(已登录)或 0(未登录)

            // ========== 第2步:获取请求参数 ==========
            // input() 是 ThinkPHP 的全局函数,用于获取请求参数
            // 'get.xxx' 表示从 URL 查询参数获取(类似 req.query.xxx)
            // 第二个参数是默认值
            $categoryId = input('get.category_id', 0);
            $page = input('get.page', 1);
            $limit = input('get.limit', 20);
            $sortBy = input('get.sort_by', 'trending');

            // 参数校验
            if ($limit > 100) {
                return response('', 403);
            }

            // ========== 第3步:构建数据库查询 ==========
            // 这是 ORM 链式查询,每个 ->where() 添加一个筛选条件
            // with() 预加载关联数据(避免 N+1 查询问题)
            $query = CommunityShare::with(['videoTask', 'itv', 'user'])
                ->where('is_public', 1)       // 公开的
                ->where('isdel', 1)           // 未删除的
                ->where('audit_status', 1)    // 审核通过的
                ->where('status', 1);         // 状态正常的

            // ========== 第4步:分类筛选(条件查询) ==========
            if (!empty($categoryId) && $categoryId != 1) {
                // 判断是父分类还是子分类
                $category = CommunityCategory::where('id', $singleCategoryId)->find();

                if ($category && $category->parent_id == 0) {
                    // 父分类 → 查它和所有子分类下的作品
                    $childCategoryIds = CommunityCategory::where('parent_id', $singleCategoryId)
                        ->where('is_enabled', 1)
                        ->column('id');  // column() 只取某一列,返回一维数组
                    // ...
                }
            }

            // ========== 第5步:排序 ==========
            switch ($sortBy) {
                case 'new':
                    $query->order('share_time', 'desc');  // 按分享时间倒序
                    break;
                case 'most_liked':
                    $query->orderRaw('(like_count + manual_like_count) DESC');  // 原生 SQL 排序
                    break;
                case 'trending':
                default:
                    $query->order('hot_score', 'desc');   // 按热度分数倒序
                    break;
            }

            // ========== 第6步:分页查询 ==========
            $list = $query->page($page, $limit)->select();
            // page(页码, 每页数量) → 自动计算 OFFSET
            // select() → 执行查询,返回结果集合

            // ========== 第7步:批量查询点赞状态 ==========
            // 这是项目规范要求的"禁止循环查数据库"的正确做法
            $likedTaskIds = [];
            if ($uid && !empty($list)) {
                // 先提取所有作品的 taskid
                $taskIds = array_column($list->toArray(), 'taskid');

                // 一次性查出当前用户点赞过哪些作品
                $likedRecords = CommunityLike::where('uid', $uid)
                    ->whereIn('taskid', $taskIds)  // WHERE taskid IN (...)
                    ->column('taskid');             // 只取 taskid 列

                // 转成 hash map,后续 O(1) 查找
                $likedTaskIds = array_flip($likedRecords);
                // array_flip(['a','b','c']) → ['a'=>0, 'b'=>1, 'c'=>2]
                // 之后用 isset($likedTaskIds['a']) 判断,比 in_array 快得多
            }

            // ========== 第8步:格式化输出数据 ==========
            $data = [];
            foreach ($list as $item) {
                $videoTask = $item->videoTask;  // 通过关联访问,不会再查 DB
                if (!$videoTask) continue;      // 跳过无效数据

                $user = $item->user;
                $is_liked = isset($likedTaskIds[$item->taskid]);  // O(1) 查找

                $data[] = [
                    'taskid'      => $item->taskid,
                    'uid'         => $item->uid,
                    'nickname'    => $user->nickname ?: substr($user->email, 0, strpos($user->email, '@')),
                    'avatar'      => $user->avatar ? getFileHostUrl() . $user->avatar : '',
                    'like_count'  => $item->like_count + $item->manual_like_count,
                    'is_liked'    => $is_liked,
                    'bucketkey'   => encodeUrl($videoTask->bucketkey) ?? '',
                    'result'      => encodeUrl($videoTask->result) ?? '',
                    'module_type' => $item->module_type,
                    'task_type'   => $item->task_type,
                ];
            }

            // ========== 第9步:返回 JSON 响应 ==========
            return json(['code' => 200, 'msg' => '', 'data' => $data]);

        } catch (\Exception $e) {
            // 统一异常处理
            writeLog('ERROR', '根据分类查询作品失败:' . $e->getMessage());
            return json(['code' => 500, 'msg' => 'ERROR', 'data' => []]);
        }
    }
}

对比前端理解(伪代码):

// 如果用 Express.js 写同样的接口,大概长这样:
app.get('/api/community/feed', async (req, res) => {
    try {
        const token = req.headers['access-token'];
        const uid = token ? jwt.decode(token).t_id : 0;

        const { category_id = 0, page = 1, limit = 20, sort_by = 'trending' } = req.query;

        const list = await CommunityShare.findAll({
            where: { is_public: 1, isdel: 1, audit_status: 1 },
            include: ['videoTask', 'user'],
            order: sort_by === 'new' ? [['share_time', 'DESC']] : [['hot_score', 'DESC']],
            offset: (page - 1) * limit,
            limit
        });

        res.json({ code: 200, data: list });
    } catch (e) {
        res.json({ code: 500, msg: 'ERROR' });
    }
});

4.4 模型层:数据库操作的封装

模型是 ORM(对象关系映射)的核心,一个模型类对应数据库中的一张表。

CommunityShare 模型(主模型)

文件位置:app/v18/model/CommunityShare.php

<?php
namespace app\v18\model;

use think\Model;

class CommunityShare extends Model
{
    // 表名(不含前缀 ph_)
    // 实际对应数据库表:ph_community_share
    protected $name = 'community_share';

    // 关闭自动日期格式化(ThinkPHP 默认会自动处理时间字段)
    protected $dateFormat = false;

    /**
     * 关联用户表
     * belongsTo = "属于"关系(多对一)
     * 含义:一条分享记录"属于"一个用户
     *
     * 参数说明:
     * - Users::class      → 关联的模型类
     * - 'uid'             → 当前表的外键字段(community_share.uid)
     * - 'uid'             → 关联表的主键字段(users.uid)
     */
    public function user()
    {
        return $this->belongsTo(Users::class, 'uid', 'uid')
            ->field('uid,nickname,avatar,userid,email');  // 只查这几个字段
    }

    /**
     * 关联视频任务表
     * 含义:一条分享记录"属于"一个视频任务
     */
    public function videoTask()
    {
        return $this->belongsTo(History::class, 'taskid', 'taskid')
            ->field('result,taskid,uid,module_type,...');
    }

    /**
     * 关联图生视频表
     */
    public function itv()
    {
        return $this->belongsTo(ImageToVideo::class, 'taskid', 'taskid')
            ->field('taskid,bgm');
    }

    /**
     * 关联点赞记录(一对多)
     * hasMany = "拥有多个"关系
     * 含义:一条分享记录可以有多条点赞记录
     */
    public function likes()
    {
        return $this->hasMany(CommunityLike::class, 'taskid', 'taskid');
    }
}

History 模型(视频任务表)

文件位置:app/v18/model/History.php

<?php
namespace app\v18\model;

use think\Model;

class History extends Model
{
    // 对应数据库表:ph_pwai_video_task
    protected $name = 'pwai_video_task';
    protected $dateFormat = false;

    // 关联增强信息(一对一)
    public function ehanceInfo()
    {
        return $this->hasOne(Ehance::class, 'taskid', 'taskid');
    }
}

CommunityLike 模型(点赞表)

文件位置:app/v18/model/CommunityLike.php

<?php
namespace app\v18\model;

use think\Model;

class CommunityLike extends Model
{
    // 对应数据库表:ph_community_like
    protected $name = 'community_like';
    protected $dateFormat = false;
}

模型关联关系图:

CommunityShare(社区分享表)
    ├── belongsTo → Users(用户表)           通过 uid 关联
    ├── belongsTo → History(视频任务表)      通过 taskid 关联
    ├── belongsTo → ImageToVideo(图生视频表) 通过 taskid 关联
    ├── hasMany   → CommunityLike(点赞表)    通过 taskid 关联
    └── belongsToMany → CommunityCategory(分类表)  通过中间表关联

4.5 全局辅助函数

文件位置:app/common.php

这个文件定义了全局可用的辅助函数,在任何地方都可以直接调用:

<?php
// ===== 日志记录 =====
// 统一的日志函数,项目规范要求所有日志都用这个
writeLog($content, $key);
// 示例:writeLog('查询失败', ['error' => $e->getMessage()]);

// ===== 配置读取 =====
// 根据当前环境(dev/pro)读取对应的配置
getConfig($key);
// 示例:getConfig('s3') → 返回 S3 配置数组
// 内部逻辑:dev 环境读 setting_dev.php,pro 环境读 setting_pro.php

// ===== 文件 URL =====
// 获取云存储文件的域名前缀
getFileHostUrl();
// 示例:getFileHostUrl() . $user->avatar → 完整的头像 URL

// ===== URL 加密 =====
// 对文件路径进行加密(安全考虑)
encodeUrl($original);
// 示例:encodeUrl($videoTask->bucketkey) → 加密后的文件路径

// ===== IP 获取 =====
ip();
// 获取客户端真实 IP 地址

// ===== 生成唯一 ID =====
getDocId($len);
// 生成基于 UUID + 时间戳的唯一标识符

5. ORM 查询构建器详解

ThinkPHP 的 ORM 查询使用链式调用,每个方法返回查询对象本身,最后调用终结方法执行查询。

<?php
// ===== 基础查询 =====

// 查询单条记录(类似 SELECT * FROM users WHERE uid = 1 LIMIT 1)
$user = Users::where('uid', 1)->find();

// 查询多条记录(类似 SELECT * FROM users WHERE status = 1)
$users = Users::where('status', 1)->select();

// 查询指定字段
$names = Users::where('status', 1)->column('nickname');  // 返回一维数组
$map = Users::where('status', 1)->column('nickname', 'uid');  // 返回 uid=>nickname 的映射

// ===== 条件查询 =====

// 等于
->where('status', 1)                    // WHERE status = 1

// 大于、小于
->where('age', '>', 18)                 // WHERE age > 18

// IN 查询
->whereIn('uid', [1, 2, 3])            // WHERE uid IN (1, 2, 3)

// LIKE 模糊查询
->where('nickname', 'like', '%kiro%')   // WHERE nickname LIKE '%kiro%'

// 原生 SQL 条件
->whereRaw("EXISTS (SELECT 1 FROM ph_community_share_category WHERE ...)")

// ===== 排序 =====

->order('create_time', 'desc')           // ORDER BY create_time DESC
->orderRaw('(like_count + manual_like_count) DESC')  // 原生 SQL 排序

// ===== 分页 =====

->page(2, 20)                            // 第2页,每页20条(自动计算 OFFSET)
->limit(10)                              // LIMIT 10

// ===== 关联预加载 =====

// with() 预加载关联数据,避免 N+1 问题
CommunityShare::with(['videoTask', 'user'])->select();
// 等价于:先查 community_share,再批量查 video_task 和 users
// 而不是每条记录单独查一次

// ===== 聚合查询 =====

Users::where('status', 1)->count();      // SELECT COUNT(*) ...
Users::where('status', 1)->max('age');   // SELECT MAX(age) ...
Users::where('status', 1)->sum('score'); // SELECT SUM(score) ...

// ===== 更新操作 =====

// 更新单条
$user = Users::where('uid', 1)->find();
$user->nickname = 'new_name';
$user->save();

// 批量更新
Users::where('status', 0)->update(['status' => 1]);

// 自增/自减
Users::where('uid', 1)->inc('like_count', 1)->update();  // like_count + 1
Users::where('uid', 1)->dec('score', 10)->update();       // score - 10

// ===== 新增操作 =====

$share = new CommunityShare();
$share->uid = $uid;
$share->taskid = $taskid;
$share->save();

6. 模型关联详解

ThinkPHP 支持多种关联类型,以下是本项目中最常用的:

<?php
// ===== 一对一 hasOne =====
// "一个视频任务 有一个 增强信息"
// History 表的一条记录 → Ehance 表的一条记录
public function ehanceInfo()
{
    // hasOne(关联模型, 关联表外键, 当前表主键)
    return $this->hasOne(Ehance::class, 'taskid', 'taskid');
}

// ===== 多对一 belongsTo =====
// "一条分享记录 属于 一个用户"
// CommunityShare 表的多条记录 → Users 表的一条记录
public function user()
{
    // belongsTo(关联模型, 当前表外键, 关联表主键)
    return $this->belongsTo(Users::class, 'uid', 'uid')
        ->field('uid,nickname,avatar');  // 只查需要的字段(性能优化)
}

// ===== 一对多 hasMany =====
// "一条分享记录 有多条 点赞记录"
public function likes()
{
    // hasMany(关联模型, 关联表外键, 当前表主键)
    return $this->hasMany(CommunityLike::class, 'taskid', 'taskid');
}

// ===== 多对多 belongsToMany =====
// "一条分享记录 属于多个 分类"(通过中间表关联)
public function categories()
{
    return $this->belongsToMany(
        CommunityCategory::class,        // 关联模型
        'ph_community_share_category',   // 中间表名(需要完整表名)
        'category_id',                   // 中间表中关联模型的外键
        'taskid',                        // 中间表中当前模型的外键
        'taskid',                        // 当前模型的主键
        'id'                             // 关联模型的主键
    );
}

// ===== 使用关联 =====

// 预加载(推荐,避免 N+1)
$list = CommunityShare::with(['user', 'videoTask'])->select();
foreach ($list as $item) {
    echo $item->user->nickname;      // 直接访问,不会再查 DB
    echo $item->videoTask->result;
}

// 懒加载(按需加载,会产生额外查询)
$share = CommunityShare::find(1);
$user = $share->user;  // 此时才查 users 表

7. 项目开发规范速记

以下是本项目必须遵守的开发规范,违反这些规则会导致代码审查不通过。

7.1 不可修改现有代码

✅ 允许:创建新文件、新类、新方法
❌ 禁止:修改任何已有的代码文件

// 如果需要改变行为,通过继承扩展:
class NewCommunityController extends CommunityController
{
    // 在新类中添加或覆盖方法
}

7.2 禁止循环中查询数据库

// ❌ 错误做法:20 条数据 = 20 次 DB 查询
foreach ($list as $item) {
    $user = Users::where('uid', $item['uid'])->find();
}

// ✅ 正确做法:20 条数据 = 1 次 DB 查询
$uids = array_column($list, 'uid');
$users = Users::whereIn('uid', $uids)->select();
$userMap = array_column($users->toArray(), null, 'uid');
// 之后用 $userMap[$uid] 直接取值

7.3 统一日志记录

// 所有日志必须使用 writeLog()
writeLog('操作描述', ['uid' => $uid, 'action' => 'feed_query']);
writeLog('错误信息', ['error' => $e->getMessage(), 'file' => __FILE__]);

7.4 统一用户认证

// 所有需要用户身份的接口,使用这段标准代码
$Access_Token = $this->request->header('Access-Token', '');
$uid = 0;
if ($Access_Token) {
    $Token = JwtAuth::decodeToken($Access_Token);
    $uid = $Token['t_id'];
}

// 强制登录的接口,加上这段
if (!$uid) {
    return json(['code' => 401, 'msg' => 'Unauthorized']);
}

7.5 错误信息必须使用英文

// ❌ 禁止
return json(['code' => 400, 'msg' => '参数错误']);

// ✅ 正确
return json(['code' => 400, 'msg' => 'Invalid parameter']);
return json(['code' => 401, 'msg' => 'Unauthorized']);
return json(['code' => 500, 'msg' => 'Internal server error']);

7.6 注释和文档使用中文

/**
 * 获取社区动态列表
 *@paramint $page 页码
 *@paramint $limit 每页数量
 *@return\think\response\Json
 */
public function feed()
{
    // 获取当前用户ID
    // 构建基础查询
    // 批量查询点赞状态
}

8. 新接口开发模板

当你需要开发一个新接口时,按照以下步骤操作:

第1步:创建模型(如果需要新表)

文件:app/v18/model/YourModel.php

<?php
namespace app\v18\model;

use think\Model;

/**
 * 你的模型描述
 */
class YourModel extends Model
{
    // 表名(不含 ph_ 前缀)
    // 实际对应数据库表:ph_your_table
    protected $name = 'your_table';
    protected $dateFormat = false;

    /**
     * 关联用户表
     */
    public function user()
    {
        return $this->belongsTo(Users::class, 'uid', 'uid')
            ->field('uid,nickname,avatar');
    }
}

第2步:创建控制器

文件:app/v18/controller/YourController.php

<?php
namespace app\v18\controller;

use app\BaseController;
use app\common\JwtAuth;
use app\v18\model\YourModel;

/**
 * 你的控制器描述
 */
class YourController extends BaseController
{
    /**
     * 你的接口描述
     *@return\think\response\Json
     */
    public function yourMethod()
    {
        try {
            // 1. 用户认证
            $Access_Token = $this->request->header('Access-Token', '');
            $uid = 0;
            if ($Access_Token) {
                $Token = JwtAuth::decodeToken($Access_Token);
                $uid = $Token['t_id'];
            }

            // 2. 获取参数
            $param1 = input('post.param1', '');  // POST 参数
            $param2 = input('get.param2', 0);    // GET 参数

            // 3. 参数校验
            if (empty($param1)) {
                return json(['code' => 400, 'msg' => 'Invalid parameter']);
            }

            // 4. 业务逻辑
            $result = YourModel::where('uid', $uid)
                ->where('status', 1)
                ->select();

            // 5. 格式化返回
            $data = [];
            foreach ($result as $item) {
                $data[] = [
                    'id' => $item->id,
                    'name' => $item->name,
                ];
            }

            return json(['code' => 200, 'msg' => '', 'data' => $data]);

        } catch (\Exception $e) {
            writeLog('接口异常', [
                'error' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'uid' => $uid ?? 0
            ]);
            return json(['code' => 500, 'msg' => 'Internal server error']);
        }
    }
}

第3步:配置路由

文件:app/v18/route/app.php(在已有的路由组中添加)

// 在 Route::group("api", function () { ... }) 内部添加:

// 公开接口(不需要登录)
Route::get('your/endpoint', 'YourController/yourMethod');

// 需要登录的接口
Route::post('your/endpoint', 'YourController/yourMethod')
     ->middleware(\app\middleware\JwtAuthMiddleware::class);

完整数据流回顾

前端发起请求
public/index.php(框架入口)
ThinkPHP 根据 URL 前缀 /v18 定位到 app/v18/ 应用
加载 app/v18/route/app.php 匹配路由
如果有中间件 → 执行中间件(如 JwtAuthMiddleware 验证 token)
实例化控制器 → 调用对应方法
控制器内部:认证 → 取参 → 校验 → 查询数据库(通过模型) → 格式化
return json([...]) 返回 JSON 响应
前端收到响应,渲染页面

附录:常用 ThinkPHP 全局函数

// 获取请求参数
input('get.name', '默认值');     // GET 参数(URL 查询字符串)
input('post.name', '默认值');    // POST 参数(请求体)
input('param.name', '默认值');   // 自动识别 GET/POST

// 返回 JSON 响应
return json(['code' => 200, 'data' => $data]);

// 返回空响应
return response('', 403);

// 数据库门面(不通过模型直接查询)
use think\facade\Db;
Db::table('ph_users')->where('uid', 1)->find();
Db::table('ph_users')->where('status', 1)->select();

// 配置读取
config('database.hostname');     // 读取框架配置
getConfig('s3');                 // 读取项目自定义配置(区分 dev/pro 环境)

// 环境变量
env('APP.APP_ENV');              // 获取 .env 文件中的配置

// 验证器
validate(YourValidate::class)->check($data);

// 事务
Db::startTrans();
Db::commit();
Db::rollback();

建议学习路径:先通读本文档,然后在项目中找 2-3 个不同类型的接口(GET 列表、POST 创建、需要认证的)对照阅读,很快就能掌握开发套路。