后端开发文档
XPCMS 基于 ThinkPHP8 框架,完整后台开发体系指南。涵盖 MVC 架构分层、CurdTrait 快速开发、中间件体系、Service 服务层、新模块开发流程及代码规范。
1. 架构分层
XPCMS 采用 6 层分层架构,请求经过多层处理才能到达业务逻辑,确保安全与可维护性。
请求处理流程
用户请求 → 路由中间件(AuthCheck) → 权限中间件(PermissionCheck)
→ 控制器(Controller) → 验证器(Validate)
→ 逻辑层(Service) → 模型(Model) → 数据库
| 层级 | 目录位置 | 职责 | 核心类 |
|---|---|---|---|
| 中间件层 | app/middleware/ | 请求前置拦截:认证、权限、CORS | AuthCheck、PermissionCheck |
| 控制器层 | app/模块名/controller/ | 接收请求、参数绑定、调用Service、返回响应 | BaseController |
| 验证器层 | app/模块名/validate/ | 参数校验、数据过滤、业务规则验证 | BaseValidate |
| 逻辑层 | app/模块名/service/ | 业务逻辑封装、数据组装、事务管理 | 各业务Service类 |
| 模型层 | app/模块名/model/ | 数据库访问、关系定义、查询作用域 | BaseModel |
| Traits | app/common/traits/ | 可复用代码块:CurdTrait、ApiResponse | CurdTrait |
2. CurdTrait 快速开发
CurdTrait 是 XPCMS 最核心的代码复用机制,在控制器中引入即可自动获得完整的增删改查接口能力。位于 app/common/traits/CurdTrait.php。
2.1 控制器属性定义(6 个属性)
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
| $modelClass | string | ✅ 是 | 模型类全限定名,如 app\admin\model\Article::class |
| $validateClass | string | 否 | 验证器类全限定名,留空则跳过验证 |
| $searchFields | array | 否 | 搜索字段列表,支持模糊搜索 |
| $sortField | string | 否 | 默认排序字段,默认 id |
| $sortOrder | string | 否 | 默认排序方向,默认 desc |
| $withTrashed | bool | 否 | 是否包含软删除数据,默认 false |
2.2 自动注册接口(6 个接口)
| 方法 | 请求方式 | 路由后缀 | 功能 |
|---|---|---|---|
| index() | GET | /index | 分页列表查询,支持搜索与排序 |
| save() | POST | /save | 新增记录,自动调用验证器 |
| read() | GET | /read/:id | 读取单条记录详情 |
| update() | PUT | /update/:id | 更新记录,自动调用验证器 |
| delete() | DELETE | /delete/:id | 删除记录(支持软删除) |
| deleteBatch() | DELETE | /delete_batch | 批量删除 |
2.3 可重写钩子方法(8 个)
| 钩子方法 | 调用时机 | 用途 |
|---|---|---|
| beforeIndex($query) | 列表查询前 | 追加额外查询条件 |
| afterIndex(&$list) | 列表查询后 | 对查询结果进行二次加工 |
| beforeSave(&$data) | 新增保存前 | 修改/补充提交数据 |
| afterSave($id) | 新增保存后 | 新增后的关联操作 |
| beforeUpdate(&$data) | 更新保存前 | 修改/补充提交数据 |
| afterUpdate($id) | 更新保存后 | 更新后的关联操作 |
| beforeDelete($id) | 删除前 | 删除前置检查(如关联检查) |
| afterDelete($id) | 删除后 | 删除后的清理操作 |
2.4 完整示例:ArticleController
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\model\Article as ArticleModel;
use app\admin\validate\Article as ArticleValidate;
use app\common\traits\CurdTrait;
class Article extends BaseController
{
use CurdTrait;
// 绑定模型 - 必填
protected string $modelClass = ArticleModel::class;
// 绑定验证器
protected string $validateClass = ArticleValidate::class;
// 搜索字段
protected array $searchFields = ['title', 'author'];
// 排序
protected string $sortField = 'create_time';
protected string $sortOrder = 'desc';
/**
* 列表查询前追加条件:仅显示已发布文章
*/
protected function beforeIndex($query): void
{
$query->where('status', 1);
}
/**
* 查询结果后二次加工:追加分类名称
*/
protected function afterIndex(&$list): void
{
foreach ($list as &$item) {
$item['category_name'] = CategoryModel::getName($item['category_id']);
}
}
/**
* 删除前检查关联评论
*/
protected function beforeDelete($id): void
{
$commentCount = CommentModel::where('article_id', $id)->count();
if ($commentCount > 0) {
throw new \Exception('该文章下有 ' . $commentCount . ' 条评论,请先删除评论');
}
}
}
3. BaseController 基类
所有控制器的父类,位于 app\BaseController.php。提供了统一的请求处理、用户信息获取、成功/失败响应等方法。
核心属性
| 属性 | 类型 | 说明 |
|---|---|---|
| $request | Request | 当前请求对象,通过依赖注入自动绑定 |
| $app | App | 当前应用实例 |
| $loginUser | array|null | 当前登录用户信息,中间件注入 |
| $middleware | array | 控制器中间件注册数组 |
核心方法
| 方法 | 说明 |
|---|---|
| success($msg, $data, $code=200) | 成功响应,返回标准JSON |
| error($msg, $code=500, $data=[]) | 失败响应,返回标准JSON |
| getLoginUserId() | 获取当前登录用户ID |
| getLoginUser() | 获取当前登录用户完整信息 |
| getLoginUserRole() | 获取当前用户角色标识 |
| isSuperAdmin() | 判断是否为超级管理员 |
| getPostData($key=null) | 获取POST提交数据,支持指定字段 |
| param($key, $default=null) | 获取请求参数(GET/POST通用) |
4. BaseModel 模型基类
所有 Model 的父类,位于 app\BaseModel.php。封装了通用的查询作用域、自动时间戳和软删除功能。
核心属性
| 属性 | 值 | 说明 |
|---|---|---|
| $autoWriteTimestamp | true | 自动维护 create_time / update_time |
| $dateFormat | 'Y-m-d H:i:s' | 时间格式化格式 |
查询作用域
| Scope 方法 | 用法示例 | 说明 |
|---|---|---|
| scopeStatus($query, $status) | Model::status(1)->select() | 按状态筛选 |
| scopeKeyword($query, $keyword, $fields) | Model::keyword('搜索词', ['title'])->select() | 多字段模糊搜索 |
| scopeTimeRange($query, $field, $start, $end) | Model::timeRange('create_time', '2025-01-01', '2025-12-31')->select() | 时间范围筛选 |
完整模型示例:Article
<?php
declare(strict_types=1);
namespace app\admin\model;
use think\model\concern\SoftDelete;
class Article extends \app\BaseModel
{
// 启用软删除
use SoftDelete;
protected string $deleteTime = 'delete_time';
// 数据表名(不含前缀,自动加 xp_)
protected string $name = 'article';
// 自动写入时间戳字段
protected $autoWriteTimestamp = true;
/**
* 关联分类
*/
public function category()
{
return $this->belongsTo(Category::class, 'category_id');
}
/**
* 关联作者
*/
public function author()
{
return $this->belongsTo(User::class, 'author_id');
}
/**
* 获取状态文本
*/
public function getStatusTextAttr(): string
{
$statusMap = [0 => '草稿', 1 => '已发布', 2 => '已下架'];
return $statusMap[$this->getData('status')] ?? '未知';
}
}
5. BaseValidate 验证器基类
位于 app\BaseValidate.php,提供统一的验证逻辑封装。支持场景验证和自定义规则。
$defaults 公共规则
所有验证器默认继承以下通用规则:
| 字段 | 规则 |
|---|---|
| id | require|integer|>:0 |
| ids | require|array |
| page | integer|>:0 |
| limit | integer|between:1,100 |
| status | in:0,1,2 |
| sort_field | alphaDash |
| sort_order | in:asc,desc |
完整验证器模板
<?php
declare(strict_types=1);
namespace app\admin\validate;
use app\BaseValidate;
class Article extends BaseValidate
{
/**
* 验证规则(业务字段)
*/
protected $rule = [
'title' => 'require|max:200',
'category_id' => 'require|integer|>:0',
'content' => 'require',
'author' => 'max:50',
'sort' => 'integer|between:0,9999',
];
/**
* 字段中文名(用于错误提示)
*/
protected $field = [
'title' => '文章标题',
'category_id' => '所属分类',
'content' => '文章内容',
'author' => '作者',
'sort' => '排序',
];
/**
* 验证场景定义
*/
protected $scene = [
'save' => ['title', 'category_id', 'content', 'author', 'sort'],
'update' => ['id', 'title', 'category_id', 'content', 'author', 'sort'],
'delete' => ['id'],
];
}
6. 中间件体系
XPCMS 采用 4 层中间件体系,从应用层到路由层逐级过滤请求。
| 级别 | 中间件 | 文件位置 | 说明 |
|---|---|---|---|
| 全局 | CorsMiddleware | app/middleware/ | 处理跨域请求,允许前端跨域访问API |
| 应用 | AuthCheck | app/middleware/ | 验证JWT Token是否有效,注入用户信息 |
| 路由 | PermissionCheck | app/middleware/ | 基于RBAC的角色权限校验 |
| 控制器 | 控制器内部注册 | 各Controller | 控制器特有逻辑拦截(如操作日志) |
AuthCheck 认证中间件
拦截所有需要登录的路由,验证请求头 Authorization: Bearer {token} 中的 JWT Token。验证通过后将用户信息注入 $request->loginUser,控制器通过 $this->loginUser 获取。
PermissionCheck 权限中间件
基于 RBAC(基于角色的访问控制) 模型:用户 → 角色 → 权限节点。每个路由定义时指定所需权限节点(如 article/index),中间件校验当前用户角色是否拥有该节点。
RBAC 核心数据表
| 表名 | 说明 |
|---|---|
| xp_admin_user | 管理员用户表,关联角色 |
| xp_admin_role | 角色表 |
| xp_admin_permission | 权限节点表,树形结构 |
| xp_admin_role_permission | 角色-权限关联中间表 |
7. Service 服务层
业务逻辑核心层,位于 app/common/service/。控制器通过依赖注入调用 Service,禁止控制器直接写复杂业务逻辑或 SQL。
核心 Service 列表(17 个)
| Service | 文件 | 核心功能 |
|---|---|---|
| AdminService | AdminService.php | 管理员登录、密码修改、个人资料 |
| ArticleService | ArticleService.php | 文章CRUD、发布、定时发布 |
| CategoryService | CategoryService.php | 分类树管理、排序、缓存 |
| ConfigService | ConfigService.php | 系统配置读取/写入、站点设置 |
| ThemeService | ThemeService.php | 主题安装、切换、卸载、配置解析 |
| PluginService | PluginService.php | 插件安装、启用、禁用、卸载 |
| UpdateService | UpdateService.php | 系统更新检测、下载、安装 |
| UploadService | UploadService.php | 文件上传、图片压缩、存储驱动 |
| MenuService | MenuService.php | 后台菜单管理、权限节点同步 |
| LogService | LogService.php | 操作日志记录、查询、清理 |
| CacheService | CacheService.php | 缓存管理、清理、预热 |
| DatabaseService | DatabaseService.php | 数据库备份、恢复、优化 |
| MessageService | MessageService.php | 站内消息发送、已读管理 |
| CommentService | CommentService.php | 评论管理、审核、敏感词过滤 |
| MemberService | MemberService.php | 会员注册、登录、信息管理 |
| WechatService | WechatService.php | 微信公众号对接、消息处理 |
| ApiService | ApiService.php | API通用工具方法、签名验证 |
使用示例
<?php
// 控制器中通过 invoke() 调用 Service
use app\common\service\ArticleService;
class Article extends BaseController
{
/**
* 发布文章(含复杂业务逻辑)
*/
public function publish(): \think\Response
{
$articleService = $this->app->make(ArticleService::class);
$data = $this->request->post();
$result = $articleService->publishArticle($data);
return $this->success('发布成功', $result);
}
}
8. 新模块开发 6 步流程
第 1 步:创建数据表
CREATE TABLE `xp_news` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '标题',
`content` text COMMENT '内容',
`category_id` int(11) DEFAULT '0' COMMENT '分类ID',
`cover_image` varchar(500) DEFAULT '' COMMENT '封面图',
`author` varchar(50) DEFAULT '' COMMENT '作者',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0草稿 1发布 2下架',
`sort` int(11) DEFAULT '100' COMMENT '排序',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻表';
第 2 步:创建 Model
<?php
declare(strict_types=1);
namespace app\admin\model;
use think\model\concern\SoftDelete;
class News extends \app\BaseModel
{
use SoftDelete;
protected string $deleteTime = 'delete_time';
protected string $name = 'news';
// 状态获取器
public function getStatusTextAttr(): string
{
$map = [0 => '草稿', 1 => '发布', 2 => '下架'];
return $map[$this->getData('status')] ?? '未知';
}
// 分类关联
public function category()
{
return $this->belongsTo(Category::class, 'category_id');
}
}
第 3 步:创建 Validate
<?php
declare(strict_types=1);
namespace app\admin\validate;
use app\BaseValidate;
class News extends BaseValidate
{
protected $rule = [
'title' => 'require|max:200',
'category_id' => 'require|integer|>:0',
'content' => 'require',
];
protected $field = [
'title' => '新闻标题',
'category_id' => '所属分类',
'content' => '新闻内容',
];
protected $scene = [
'save' => ['title', 'category_id', 'content'],
'update' => ['id', 'title', 'category_id', 'content'],
];
}
第 4 步:创建 Service
<?php
declare(strict_types=1);
namespace app\admin\service;
use app\admin\model\News as NewsModel;
class NewsService
{
/**
* 发布新闻(含额外业务处理)
*/
public function publish(array $data): int
{
// 业务校验:检查标题是否重复
$exist = NewsModel::where('title', $data['title'])->find();
if ($exist) {
throw new \Exception('新闻标题已存在');
}
$data['status'] = 1;
$data['create_time'] = date('Y-m-d H:i:s');
$news = NewsModel::create($data);
// 发布后清除分类缓存
cache('category_tree', null);
return $news->id;
}
}
第 5 步:创建 Controller
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\model\News as NewsModel;
use app\admin\validate\News as NewsValidate;
use app\common\traits\CurdTrait;
class News extends BaseController
{
use CurdTrait;
protected string $modelClass = NewsModel::class;
protected string $validateClass = NewsValidate::class;
protected array $searchFields = ['title', 'author'];
protected string $sortField = 'sort';
protected string $sortOrder = 'asc';
}
第 6 步:注册路由与菜单
① 在 route/admin.php 中注册 RESTful 资源路由:
Route::group('news', function () {
Route::get('/', 'News/index'); // 列表
Route::post('/', 'News/save'); // 新增
Route::get('/:id', 'News/read'); // 详情
Route::put('/:id', 'News/update'); // 更新
Route::delete('/:id', 'News/delete'); // 删除
})->middleware([AuthCheck::class, PermissionCheck::class]);
② 在后台菜单管理中新增菜单,关联权限节点为 news/index。
9. 插件开发指南
XPCMS 插件系统基于 PluginService(位于 app/common/service/PluginService.php),支持插件的安装、启用、禁用、卸载完整生命周期管理。插件存放于项目根目录 plugins/ 下。
9.1 插件目录结构
一个完整的插件包含以下文件:
plugins/
└── my_plugin/ ← 插件名(小写蛇形命名)
├── plugin.json ← 插件元数据(必填)
├── Install.php ← 安装脚本(必填,命名空间 plugins\my_plugin\Install)
├── Uninstall.php ← 卸载脚本(可选,命名空间 plugins\my_plugin\Uninstall)
├── Bootstrap.php ← 启动引导(可选,启用时自动加载)
├── config.php ← 配置项(可选,返回数组)
├── controller/ ← 控制器目录
├── view/ ← 视图模板
├── public/ ← 静态资源(将被软链接到 public/static/plugins/my_plugin/)
└── assets/ ← 资源文件(图片/CSS/JS)
9.2 plugin.json 定义
插件元数据文件,JSON 格式:
{
"title": "示例插件",
"version": "1.0.0",
"description": "这是一个示例插件,演示插件开发流程",
"author": "彭浩",
"icon": "fas fa-puzzle-piece"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| title | string | 是 | 插件显示名称 |
| version | string | 是 | 版本号,格式 x.y.z |
| description | string | 否 | 插件功能描述 |
| author | string | 否 | 作者署名 |
| icon | string | 否 | Font Awesome 图标 class |
9.3 生命周期钩子:Install.php
安装插件时自动执行。命名空间必须为 plugins\{插件名},类名必须为 Install,实现 run() 方法:
<?php
declare(strict_types=1);
namespace plugins\my_plugin;
use think\facade\Db;
class Install
{
/**
* 安装插件时执行
* 用于创建数据表、插入默认数据、注册菜单和权限节点
* @return void
* @throws \Exception 安装失败时抛出异常
*/
public function run(): void
{
// 1. 创建插件专用数据表
Db::execute('CREATE TABLE IF NOT EXISTS `xp_my_plugin_log` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`content` text COMMENT '日志内容',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
// 2. 插入默认配置
Db::name('config')->insert([
'name' => 'my_plugin_enable',
'title' => '启用插件功能',
'value' => '1',
'type' => 'switch'
]);
// 3. 注册后台菜单(可选)
// MenuService 会自动处理权限节点同步
}
}9.4 Uninstall.php(卸载脚本)
卸载插件时可选执行,用于清理数据表、删除配置等:
<?php
declare(strict_types=1);
namespace plugins\my_plugin;
use think\facade\Db;
class Uninstall
{
/**
* 卸载插件时执行
* 用于删除数据表、清理配置等清理操作
* @return void
*/
public function run(): void
{
// 删除插件专用数据表
Db::execute('DROP TABLE IF EXISTS `xp_my_plugin_log`');
// 删除插件配置项
Db::name('config')->where('name', 'like', 'my_plugin_%')->delete();
}
}9.5 Bootstrap.php(启动引导)
插件启用时自动加载,可用于注册路由、事件监听、中间件等初始化操作:
<?php
declare(strict_types=1);
namespace plugins\my_plugin;
use think\facade\Event;
/**
* 插件启动引导
* 在插件启用时被 PluginService::enablePlugin() 自动加载
*/
// 注册事件监听
Event::listen('article_published', function ($article) {
// 当文章发布时,记录日志
\think\facade\Db::name('my_plugin_log')->insert([
'content' => '文章《' . $article['title'] . '》已发布',
'create_time' => date('Y-m-d H:i:s')
]);
});9.6 config.php(插件配置)
返回 PHP 数组,通过 PluginService::getPluginConfig($name) 读取:
<?php
return [
'enable' => true,
'api_key' => '',
'max_items' => 10,
'cache_time' => 3600,
'allowed_roles' => ['super_admin', 'editor'],
];9.7 插件生命周期完整流程
| 阶段 | 触发的操作 | 触发的钩子 |
|---|---|---|
| 📦 安装 | 解压 ZIP → 写入 xp_plugin 表(status=0) | Install.php::run() + event: plugin_install |
| ✅ 启用 | 更新 status=1 | 加载 Bootstrap.php + event: plugin_enable |
| ⏸️ 禁用 | 更新 status=0 | event: plugin_disable |
| 🗑️ 卸载 | 先禁用 → 删数据库记录(保留物理文件) | Uninstall.php::run() + event: plugin_uninstall |
| 💣 删除 | 卸载 + 递归删除物理目录 | 同卸载 |
9.8 插件开发最佳实践
- 插件名使用 小写蛇形命名,如 seo_optimizer
- Install.php 创建数据库表时使用 CREATE TABLE IF NOT EXISTS 避免重复安装报错
- Uninstall.php 做好清理工作,不留下冗余数据
- 静态资源放在 assets/ 下,通过 /static/plugins/{插件名}/ 路径访问
- 配置文件 key 统一加插件名前缀,避免与系统配置冲突
- 通过 Event 事件系统与核心系统解耦,监听需要的钩子即可
10. 主题开发指南
XPCMS 主题系统基于 ThemeService(位于 app/common/service/ThemeService.php),支持前台主题、后台主题和双端合并主题三种类型。主题存放于项目根目录 themes/ 下。
10.1 主题目录结构
themes/
└── frontend/ ← 前台主题(scope: frontend)
│ └── default/ ← 默认主题(不可物理删除)
│ └── my_theme/ ← 自定义前台主题
│ ├── theme.json ← 主题元数据(必填)
│ ├── config.json ← 主题配置项(可选)
│ ├── screenshot.png ← 主题截图(可选)
│ ├── view/ ← 视图模板目录
│ │ ├── common/ ← 公共模板(_header/_footer)
│ │ ├── index/ ← 首页模块模板
│ │ ├── article/ ← 文章模块模板
│ │ └── ...
│ └── assets/ ← 静态资源(CSS/JS/图片)
│ ├── css/
│ ├── js/
│ └── images/
└── backend/ ← 后台主题(scope: backend)
└── xpui-portal/ ← XPUI 后台主题
├── theme.json
└── view/
└── admin/ ← 后台模板
├── index/
└── ...
10.2 theme.json 主题定义
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| title | string | 是 | 主题显示名称 |
| version | string | 是 | 版本号,格式 x.y.z |
| description | string | 否 | 主题简介 |
| author | string | 否 | 作者署名 |
| type | string | 是 | 前端:front / 后端:admin / 双端:both |
| screenshot | string | 否 | 截图文件名,如 screenshot.png |
10.3 type 字段详解
| type 值 | 安装目标 | 使用场景 |
|---|---|---|
| front | 仅 themes/frontend/ | 纯前台主题(企业官网/博客/商城首页) |
| admin | 仅 themes/backend/ | 纯后台主题(后台UI换肤) |
| both | 同时安装到前后台目录 | 前后台统一风格的企业级主题 |
10.4 前台主题开发
⚠ 前台主题的核心是 view/ 目录下的模板文件,必须提供对应模块的模板才能正常渲染。
theme.json 示例
{
"title": "企业门户主题",
"version": "1.0.0",
"description": "适用于企业官网的前台主题",
"author": "彭浩",
"type": "front",
"screenshot": "screenshot.png"
}模板文件映射
| URL路径 | 对应控制器方法 | 模板路径(相对 view/) |
|---|---|---|
| / | IndexController::index() | index/index.html |
| /article/1 | ArticleController::detail() | article/detail.html |
| /article | ArticleController::index() | article/index.html |
| /product | ProductController::index() | product/index.html |
| /page/about | PageController::detail() | page/detail.html |
| /search | SearchController::index() | search/index.html |
模板文件最小示例(index/index.html)
{include file="common/_header" /}
{$xp_current_module = 'index'}
<div class="xp-container">
<h1>欢迎访问 {$site_config.site_name|default='我的网站'}</h1>
<!-- 文章列表 -->
{if !empty($article_list)}
<div class="xp-article-list">
{foreach $article_list as $item}
<div class="xp-article-item">
<h2><a href="{:url('article/detail', ['id' => $item.id])}">{$item.title}</a></h2>
<p>{$item.description|default=''}</p>
<span>{$item.create_time}</span>
</div>
{/foreach}
</div>
{/if}
</div>
{include file="common/_footer" /}公共模板(common/_header.html)关键要素
- 引入 xp-all.css 和 xp-all.js
- 引入主题自定义 CSS(放在 assets/css/ 下)
- 根据 $xp_current_module 变量自动高亮当前导航项
- 输出 $site_config 中的 SEO 信息(title/keywords/description)
10.5 后台主题开发
后台主题主要用于定制后台 UI 风格,模板位于 view/admin/ 目录下,对应后台管理界面各个功能模块的视图。
{
"title": "深色后台主题",
"version": "1.0.0",
"description": "暗色系后台管理界面主题",
"author": "彭浩",
"type": "admin",
"screenshot": "screenshot.png"
}10.6 config.json(主题配置)
主题配置通过 ThemeService::getThemeConfig($name) 读取:
{
"primary_color": "#16a34a",
"layout": "wide",
"show_banner": true,
"home_sections": ["banner", "about", "news", "products"],
"footer_text": "Copyright © 2026 小彭CMS",
"social_links": {
"wechat": "",
"weibo": "",
"github": ""
}
}在模板中使用:{$theme_config.primary_color}(由控制器通过 ThemeService 读取后 assign 到模板变量 $theme_config)。
10.7 主题安装与分发(ZIP 包规范)
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1. 打包 | 将主题目录打包为 ZIP | ZIP 根目录直接是主题文件(不要外层文件夹),如 my_theme.zip 内含 theme.json + view/ + assets/ |
| 2. 上传 | 后台 → 主题管理 → 安装主题 | 调用 ThemeService::installTheme($zipPath) |
| 3. 解析 | 系统自动读取 theme.json | 根据 type 字段决定安装到 frontend 或/和 backend 目录 |
| 4. 入库 | 写入 xp_theme 表 | 记录名称、版本、作者等信息 |
| 5. 启用 | 设为当前主题 | 调用 ThemeService::setCurrentTheme($name),更新 is_current/is_admin 字段 |
10.8 主题切换机制
XPCMS 通过以下方式动态决定使用哪个主题的模板:
- 控制器注入 FrontendConfig Trait,自动调用 ThemeService::getCurrentViewPath() 设置视图路径
- getCurrentViewPath() 查询 xp_theme 表中 is_current=1 的记录
- 若查询到已启用的非默认主题,则使用该主题的 view/ 路径渲染模板
- 若未找到或主题目录不存在,自动回退到 themes/frontend/default/view/
10.9 主题开发最佳实践
- 始终保留 default 主题作为回退模板,新主题只需覆盖需要的模块
- 模板中使用 !empty($variable) 检查变量存在性,避免未定义数组键报错
- 静态资源放在 assets/ 目录下,通过绝对路径引用
- theme.json 的 type 字段务必正确:前台写 front,后台写 admin,双端写 both
- 所有 class/id 命名必须使用 xp- 前缀 + 短横线命名
- 前台主题必须提供 view/common/_header.html 和 view/common/_footer.html
- 不要修改 default 主题的原始文件,该主题作为系统回退保障
11. 代码规范 15 项清单
所有 XPCMS 后端代码提交前必须通过以下检查:
| 序号 | 检查项 | 标准 |
|---|---|---|
| 1 | 文件首行声明 | declare(strict_types=1); |
| 2 | 文件头部注释 | 模块描述、作者彭浩、开发日期、版权声明 |
| 3 | 命名空间 | 符合 PSR-4 规范,与目录路径完全一致 |
| 4 | 类名/方法名 | 大驼峰类名、小驼峰方法名,禁止拼音和下划线 |
| 5 | 数据库操作 | 仅使用 think\facade\Db,禁止 db() 助手函数 |
| 6 | 参数接收 | Request 依赖注入,禁止 $_GET / $_POST / $_REQUEST |
| 7 | 方法注释 | 完整 PHPDoc:功能、@param、@return、@throws |
| 8 | 业务注释 | 复杂逻辑/算法/模糊变量必须有简体中文注释 |
| 9 | 分层调用 | Controller → Service → Model,禁止跨层调用 |
| 10 | 数据表规范 | xp_ 前缀、小写蛇形命名、id 主键、create_time/update_time |
| 11 | 安全 | 禁止 eval/exec,所有前端输入必须过滤验证 |
| 12 | 异常处理 | 使用 try-catch 包裹可能出错的操作,记录日志 |
| 13 | 返回值 | API 统一返回 {code, msg, data} JSON 格式 |
| 14 | 软删除 | 敏感数据使用 SoftDelete Trait,不允许物理删除 |
| 15 | 依赖注入 | 优先使用容器依赖注入,避免 new 实例化 |