Skip to content

开发规范

参考:ThinkPHP 8.0 开发规范 | ThinkPHP 8.0 目录结构

命名规范

ThinkPHP 8.0 遵循 PSR-2 命名规范和 PSR-4 自动加载规范。

目录和文件

规则示例
目录使用小写+下划线controller/, user_service/
类库、函数文件统一以 .php 为后缀UserController.php
类的文件名与命名空间路径一致app\auth\controller\AuthControllerapp/auth/controller/AuthController.php
类文件采用驼峰法命名(首字母大写)UserController.php, UserType.php
其它文件采用小写+下划线命名common.php, route.php

类命名

规则示例
类名采用驼峰法(首字母大写)User, UserType, AuthController
类名与文件名保持一致UserController 类 → UserController.php

方法和属性命名

规则示例
方法采用驼峰法(首字母小写)getUserName(), getUserById()
属性采用驼峰法(首字母小写)$tableName, $instance
魔术方法以双下划线开头__call(), __autoload()

函数命名

规则示例
函数使用小写字母和下划线get_client_ip(), array_to_tree()

常量和配置

类型规则示例
常量大写字母和下划线APP_PATH, HAS_ONE
配置参数小写字母和下划线url_route_on, url_convert
环境变量大写字母和下划线APP_DEBUG, DB_HOST

数据表和字段

规则示例
数据表采用小写+下划线think_user, sys_role_menu
字段采用小写+下划线user_name, created_at
禁止使用驼峰和中文命名userName, ❌ 用户表

项目结构

参考:ThinkPHP 8.0 目录结构

app/
├─ auth/controller/        # 认证模块
├─ system/                 # 系统模块(controller/service/model/enums/validate)
├─ codegen/                # 代码生成
├─ file/                   # 文件上传
├─ common/                 # 公共模块
│   ├─ constants/          # 常量定义
│   ├─ enums/              # 枚举定义
│   ├─ exception/          # 异常类
│   ├─ middleware/         # 中间件(Auth/Perm/DataScope/RateLimit/Log)
│   ├─ model/              # 基础模型
│   ├─ traits/             # Trait 复用
│   ├─ util/               # 工具类
│   ├─ validate/           # 验证器
│   └─ web/                # Web 响应/基类
extend/
├─ jwt/                    # JWT Token 管理
├─ sse/                    # SSE 实时通信
├─ redis/                  # Redis 客户端
└─ http/                   # HTTP 工具
config/                    # 配置文件

分层架构规约

调用链路

各层职责

层级职责禁止事项
Controller参数校验、调用 Service、组装返回结果❌ 禁止直接操作数据库
Service业务逻辑、事务控制、数据组装❌ 禁止跨业务调用 Model
Model数据库访问、ORM 操作❌ 禁止包含业务逻辑

Service 调用规范

核心原则:Service 只能调用自己业务域的 Model,禁止跨业务调用。

php
// ✅ 正确:UserService 调用 User Model
final class UserService
{
    public function getUserById(int $id): ?User
    {
        return User::find($id);  // 自己业务域
    }
}

// ❌ 错误:UserService 直接调用 Role Model
final class UserService
{
    public function getUserWithRoles(int $id): ?User
    {
        $user = User::find($id);
        $roles = Role::where('user_id', $id)->select();  // ❌ 错误
        return $user;
    }
}

// ✅ 正确:通过 RoleService 获取角色
final class UserService
{
    public function getUserWithRoles(int $id): ?User
    {
        $user = User::find($id);
        $roleService = app()->make(RoleService::class);
        $roles = $roleService->getRolesByUserId($id);  // ✅ 正确
        return $user;
    }
}

跨业务数据交互

场景正确做法
UserService 需要角色数据调用 RoleService::getRolesByUserId()
OrderService 需要用户数据调用 UserService::getUserById()
需要多个业务数据组装在 Service 层调用多个 Service 组装

RESTful API 规范

URL 设计

规则示例说明
使用名词复数/api/v1/users✅ 正确
避免动词/api/v1/getUsers❌ 错误
层级不超过 3 层/api/v1/users/{id}/roles✅ 正确
使用连字符分隔/api/v1/user-profiles✅ 正确

HTTP 方法映射

HTTP 方法操作URL 示例权限标识
GET列表查询GET /api/v1/userssys:user:list
GET单个查询GET /api/v1/users/{id}sys:user:list
POST新增POST /api/v1/userssys:user:create
PUT全量修改PUT /api/v1/users/{id}sys:user:update
PATCH部分修改PATCH /api/v1/users/{id}/statussys:user:update
DELETE删除DELETE /api/v1/users/{id}sys:user:delete

HTTP 状态码规范

核心原则

HTTP 状态码表示 HTTP 请求是否成功到达并被处理,业务错误不属于 HTTP 层面的错误。

状态码对照表

HTTP场景说明
200业务错误验证码错误、密码错误、用户不存在、数据校验失败等
200成功请求成功,业务正常执行
401未认证未登录、Token 无效或过期
403无权限已登录但无接口访问权限(RBAC 检查失败)
404资源不存在接口路径不存在
500服务器错误数据库异常、空指针、未捕获异常等

三种"无权限"的区分

场景HTTP说明
没登录401没有身份
登录了但没有接口权限403没有接口访问权
有权限访问接口,但业务校验不通过200业务失败

响应格式示例

json
// 成功 - HTTP 200
{
    "code": "00000",
    "msg": "成功",
    "data": { ... }
}

// 业务错误 - HTTP 200
{
    "code": "A0240",
    "msg": "验证码错误",
    "data": null
}

// 未认证 - HTTP 401
{
    "code": "A0230",
    "msg": "访问令牌无效",
    "data": null
}

// 无权限 - HTTP 403
{
    "code": "A0301",
    "msg": "访问未授权",
    "data": null
}

// 服务器错误 - HTTP 500
{
    "code": "B0001",
    "msg": "系统执行出错",
    "data": null
}

为什么业务错误返回 200

  1. HTTP 语义:HTTP 200 表示服务器成功处理了请求,只是业务结果不符合预期
  2. 前端处理:前端可以根据响应体中的 code 字段判断具体错误类型,统一显示错误提示
  3. 日志记录:业务错误通常不需要触发 HTTP 错误日志,减少监控噪音
  4. 主流方案:大多数后台管理系统采用此方案,便于前后端协作

接口示例

php
// 路由定义
Route::group('api/v1', function () {
    Route::get('users', 'system.user/list');           // 列表查询
    Route::get('users/:id', 'system.user/read');       // 单个查询
    Route::post('users', 'system.user/save');          // 新增
    Route::put('users/:id', 'system.user/update');     // 修改
    Route::delete('users/:id', 'system.user/delete');  // 删除
    Route::patch('users/:id/status', 'system.user/updateStatus'); // 部分修改
});

权限标识规范

命名格式

模块:资源:操作

操作命名对照表

操作标识HTTP 方法说明
列表查询listGET分页/列表查询
新增createPOST创建资源
修改updatePUT/PATCH更新资源
删除deleteDELETE删除资源
导入importPOST批量导入
导出exportGET数据导出
发布publishPOST发布操作
撤销revokePOST撤销操作

权限标识示例

php
// 用户管理
sys:user:list           // 列表
sys:user:create         // 新增
sys:user:update         // 修改
sys:user:delete         // 删除
sys:user:import         // 导入
sys:user:export         // 导出
sys:user:reset-password // 重置密码

// 角色管理
sys:role:create
sys:role:update
sys:role:delete
sys:role:assign         // 分配权限

// 通知管理
sys:notice:list
sys:notice:create
sys:notice:update
sys:notice:delete
sys:notice:publish      // 发布
sys:notice:revoke       // 撤销

错误码规范

设计原则

参考《阿里巴巴 Java 开发手册》错误码规范:

  1. 快速溯源:通过错误码快速定位错误来源
  2. 简单易记:5 位字符串,易于记忆和比对
  3. 沟通标准化:脱离文档也能准确沟通

错误码结构

错误来源(1位)+ 数字编号(4位)
来源前缀说明
用户端错误A参数错误、认证失败、权限不足等
系统错误B系统超时、内部异常等
第三方服务C数据库、中间件、外部 API 等

号段划分

号段分类示例
A0001一级宏观用户端错误
A0100二级宏观用户注册错误
A01xx三级细分A0101 用户未同意协议
A0200二级宏观用户登录异常
A02xx三级细分A0230 Token 无效

常用错误码

错误码说明场景
00000成功正常执行
A0001用户端错误一级宏观
A0200用户登录异常登录失败
A0230Token 无效或过期认证失败
A0301访问未授权权限不足
A0400请求参数错误参数校验失败
A0506请勿重复提交防重复提交
B0001系统执行出错一级宏观
C0001第三方服务出错一级宏观

使用示例

php
// 定义错误码常量
final class ResultCode
{
    public const SUCCESS = '00000';
    public const USER_ERROR = 'A0001';
    public const USER_LOGIN_ERROR = 'A0200';
    public const TOKEN_INVALID = 'A0230';
    public const ACCESS_UNAUTHORIZED = 'A0301';
    public const PARAM_ERROR = 'A0400';
    public const DUPLICATE_SUBMISSION = 'A0506';
    public const SYSTEM_ERROR = 'B0001';
}

// 使用示例
if (!$user) {
    throw new BusinessException(ResultCode::USER_ERROR, '用户不存在');
}

// 响应格式
{
    "code": "A0001",
    "msg": "用户不存在",
    "data": null
}

排序字段规范

字段命名

字段名类型说明
sortint排序号(升序,值越小越靠前)
create_timedatetime创建时间(降序)
update_timedatetime更新时间(降序)

排序规则

php
// ✅ 推荐:先按 sort 升序,再按创建时间降序
User::order('sort', 'asc')
    ->order('create_time', 'desc')
    ->select();

// 示例:角色列表排序
Role::order('sort', 'asc')
    ->order('create_time', 'desc')
    ->order('update_time', 'desc')
    ->select();

// 示例:菜单列表排序
Menu::order('sort', 'asc')->select();

默认排序

业务场景默认排序
列表查询sort ASC, create_time DESC
树形结构sort ASC
日志记录create_time DESC

代码注释规范

类注释

php
/**
 * 用户业务服务类
 *
 * @author Ray.Hao
 * @since 2024/1/14
 */
final class UserService
{
    // ...
}

方法注释

php
/**
 * 获取用户分页列表
 *
 * @param array $params 查询参数
 * @return array 用户分页列表
 */
public function getPageList(array $params): array
{
    // ...
}

/**
 * 新增用户
 *
 * @param array $data 用户表单数据
 * @return bool 是否新增成功
 */
public function saveUser(array $data): bool
{
    // ...
}

属性注释

php
final class User extends Model
{
    /** @var string 用户ID */
    protected $id;

    /** @var string 用户名 */
    protected $username;

    /** @var int 用户状态:1-启用,0-禁用 */
    protected $status;
}

日志规范

日志级别

级别场景
error错误信息,需要立即处理
warning警告信息,可能存在问题
info关键流程信息
debug调试信息

日志格式

php
// ✅ 推荐:使用结构化日志
Log::info('用户登录成功', [
    'userId' => $userId,
    'username' => $username,
    'ip' => request()->ip()
]);

Log::error('用户登录失败', [
    'username' => $username,
    'reason' => $e->getMessage()
]);

// ❌ 不推荐:字符串拼接
Log::info("用户登录成功:userId={$userId}");

敏感信息脱敏

php
// ✅ 推荐:敏感信息脱敏
Log::info('用户登录', [
    'mobile' => maskMobile($mobile)
]);

// ❌ 不推荐:打印敏感信息
Log::info('用户登录', [
    'password' => $password  // ❌ 禁止打印密码
]);

最佳实践

避免魔法值

php
// ❌ 不推荐
if ($user->status == 1) { }

// ✅ 推荐:使用枚举
if ($user->status == UserStatus::ENABLED) { }

// ✅ 推荐:使用常量
final class UserStatus
{
    public const ENABLED = 1;
    public const DISABLED = 0;
}

数组判空

php
// ❌ 不推荐
if (count($list) > 0) { }

// ✅ 推荐
if (!empty($list)) { }

字符串判空

php
// ❌ 不推荐
if ($str != null && strlen($str) > 0) { }

// ✅ 推荐
if (!empty($str)) { }

避免空指针

php
// ❌ 不推荐
return $user->username == 'admin';

// ✅ 推荐
return $user && $user->username == 'admin';

// ✅ 推荐:使用空合并运算符
return $user->username ?? '' == 'admin';

相关文件

文件说明
app/common/constants/ResultCode.php错误码常量定义
app/common/exception/BusinessException.php业务异常类
app/common/exception/Handle.php全局异常处理
app/common/web/BaseController.php控制器基类
app/common/web/Response.php统一响应封装

控制器规范

php
final class UserController extends BaseController
{
    public function list(): Json
    {
        $service = $this->app->make(UserService::class);
        return $this->success($service->getPageList($this->request->get()));
    }
}

要点:

  • 继承 BaseController,使用 $this->success() 返回
  • 禁止直接操作数据库,必须通过 Service
  • 使用 declare(strict_types=1) 开启严格类型

服务规范

php
final class UserService
{
    public function getPageList(array $params): array
    {
        $query = User::where('is_deleted', 0);
        return ['list' => $query->page($params['page'] ?? 1, $params['pageSize'] ?? 10)->select(), 'total' => $query->count()];
    }
}

参数校验

php
final class UserValidate extends Validate
{
    protected $rule = ['username' => 'require|max:50', 'password' => 'require|min:6'];
    protected $message = ['username.require' => '用户名不能为空'];
}

// 控制器中使用
$this->validate($this->request->post(), UserValidate::class);

统一响应

php
// 成功: {"code": "00000", "msg": "成功", "data": ...}
// 分页: {"code": "00000", "msg": "成功", "data": {"list": [...], "total": 100}}
// 错误: {"code": "A0400", "msg": "参数错误", "data": null}

异常处理

php
// 抛出业务异常
throw new BusinessException(ResultCode::USER_NOT_FOUND, '用户不存在');

// 全局异常处理器自动捕获并返回标准响应

接口幂等

  • 优先使用数据库唯一约束兜底
  • 高风险写操作使用 Redis 分布式锁
php
$lockKey = "lock:order:{$orderId}";
if (!Redis::setnx($lockKey, 1, 30)) throw new BusinessException(ResultCode::DUPLICATE_SUBMISSION);
try { /* 业务处理 */ } finally { Redis::del($lockKey); }

注意事项

  1. 避免使用 PHP 保留字:禁止使用 class, function, if 等保留字作为类名、方法名、命名空间
  2. 大小写敏感:在类 Unix 系统上,文件名大小写敏感,确保命名空间与文件路径一致
  3. 调试模式:ThinkPHP 调试模式下会严格检查大小写

参考资料

下一步

基于 MIT 许可发布 · 由 ❤️ 和 ☕ 驱动 · 支持作者