功能模块概览
功能模块承载后端业务能力,可同时提供后台管理页、PC 前台控制器和 Uni API。源码位于 app/{name}/,目录名使用全小写字母数字,应用类型固定为 module。
推荐开发顺序
- 确定模块标识与数据边界,创建
module.json。 - 建立 Model、API 与后台控制器菜单。
- 实现后台页面及需要的前台/Uni 调用端。
- 补齐迁移、语言模板、安全校验与发布说明。
从零创建功能模块
1. 在开发者中心建档
进入开发者中心的本地开发功能,选择“功能模块”,填写应用标识、名称和说明。标识一旦在开放平台建档便是长期身份,不要使用公司名、环境名或版本号作为标识。系统根据 app/devcenter/stub/module/ 生成基础目录。
2. 建立最小可运行页面
以应用标识 book 为例:在 controller/Admin.php 增加 books() 并返回 vue();建立 view/admin/books/books.vue、同名 JS 和 LESS;建立 api/admin/book_books.php。启用应用后刷新后台导航即可进入页面。
3. 建表并创建 Model
开发库中新建 {prefix}book_books,同时创建 model/BookBooks.php。模型不是可选装饰:安装包打表、卸载识别和数据库快照都依赖模型归属。开发者中心的表管理可辅助创建表和 Model。
4. 完成接口闭环
先实现列表、详情、保存、删除四个接口,再接后台页面。每个写操作都要校验 ID、字段范围和权限;删除不存在的记录应返回明确错误,不能对空对象直接调用方法。
5. 本地验证后发布
以普通管理员而非仅超级管理员验证菜单权限;再测试空库首装、带旧数据更新和失败重试。发布时开发者中心会生成 ZIP、结构快照和必要的自动迁移,并向开放平台提交审核。
目录与 Manifest
app/example/
├── module.json
├── controller/Admin.php
├── model/ExampleItem.php
├── api/admin/example_item.php
├── api/uni/example_item.php
├── view/admin/item/{item.vue,item.js,item.less}
├── lang/tpl/{zh-cn.php,en.php}
└── migrations/1.1.0_add_status.sql
module.json 声明 name、type、version、title、description,功能模块可选声明 icon。name 必须等于目录名,version 使用三段 SemVer,例如 1.2.0。
| 字段 | 要求 |
|---|---|
| type | module |
| name | 与模块目录名一致 |
| version | 三段 SemVer |
| 表归属 | 每张业务表必须有对应 Model |
完整 CRUD 示例
下面展示一个最小“图书”资源的关键代码。示例重点是文件命名、返回结构、校验和资源存在性,实际项目应再加入业务权限与验证器。
Model:model/BookBooks.php
<?php
namespace app\book\model;
use app\BaseModel;
class BookBooks extends BaseModel
{
// 默认由类名解析为 book_books 表
}
后台 API:api/admin/book_books.php
<?php
namespace app\book\api\admin;
class book_books
{
public function book_books($params)
{
$query = (new \app\book\model\BookBooks)->order('id desc');
if (!empty($params['keyword'])) {
$query->whereLike('title', '%' . trim($params['keyword']) . '%');
}
return show($query->paginate(max(1, (int)($params['limit'] ?? 20))));
}
/** @title 新增或编辑 */
public function book_books_save($params)
{
$title = trim((string)($params['title'] ?? ''));
if ($title === '' || mb_strlen($title) > 100) {
return show([], 0, '标题不能为空且不能超过100字');
}
$id = (int)($params['id'] ?? 0);
$model = $id ? \app\book\model\BookBooks::find($id) : new \app\book\model\BookBooks;
if (!$model) return show([], 0, '记录不存在');
$model->save(['title' => $title, 'status' => (int)($params['status'] ?? 1)]);
return show(['id' => $model->id], 1, '保存成功');
}
/** @title 删除 */
public function book_books_delete($params)
{
$model = \app\book\model\BookBooks::find((int)($params['id'] ?? 0));
if (!$model) return show([], 0, '记录不存在');
$model->delete();
return show([], 1, '删除成功');
}
}
页面请求:view/admin/books/books.js
export default {
data() { return { loading: false, list: [], form: { title: '', status: 1 } } },
methods: {
load() {
this.loading = true
return this.$tdy.request({ url: '/api/admin?api=book_books', data: { limit: 20 } })
.then(res => { if (res.code) this.list = res.data.data || [] })
.finally(() => { this.loading = false })
},
save() {
return this.$tdy.request({ url: '/api/admin?api=book_books_save', data: this.form })
.then(res => { res.code ? this.load() : this.$message.error(res.message) })
}
},
mounted() { this.load() }
}
模型、接口与权限
模型与表
业务 Model 继承 app\BaseModel。表归属以模块 model/ 为权威;种子或固定配置表设置 protected $isFixed = true。SQL 同时兼容 MySQL 与达梦数据库。
API 分发
后台接口放在 api/admin/,Uni/H5 接口放在 api/uni/。文件、类和方法保持同名 snake_case,例如 example_item.php → class example_item → example_item($params)。
this.$tdy.request({
url: '/api/admin?api=example_item',
data: { page: 1 }
})
业务响应统一使用 show($data, $code, $message, $time)。后台菜单由 controller/Admin.php 的 PHPDoc 反射生成;使用 @title、@subpage、@authnode 和 @public 表达菜单与权限。
后台与前台页面
后台页面位于 view/admin/{page}/,保持 Vue Options API 风格,优先复用 tdy-table、tdy-filtrate、上传组件和 Element Plus 图标。
模块自带的 PC 前台页面会与站点网站模板共同加载。所有前台 CSS 类必须以 tdy-app-{name} 开头,避免主题全局样式相互污染。接口统一走 $tdy.request。
public/@/ 是自动生成的静态镜像;开发时只修改模块源码。语言、文件与安全
语言包
开发者维护的语言源仅放在 app/{name}/lang/tpl/*.php。同级上方的 lang/zh-cn.php、lang/en.php 等属于站点按模板生成并允许用户修改的文件,发布包会主动排除,不能直接编辑。新增 lang() 键时同步补齐所支持语言的模板源。
上传与本地文件
上传应复用平台上传能力。服务端必须使用允许的扩展名和 MIME 白名单、限制大小、生成服务端文件名,并把最终路径约束在预期目录。不得信任客户端文件名,也不得将用户输入直接拼接为磁盘路径。
接口安全基线
- 查询、修改与删除均校验当前用户对目标资源的归属或业务权限。
- 金额、状态和权限字段由服务端决定,不能直接接受客户端结果。
- 删除、重置、退款等危险操作需要明确确认、防重复提交和可理解的失败反馈。
- 数据库使用 ORM/参数绑定;禁止把输入拼入 SQL、Shell 或动态类名。
- 日志只记录排障所需标识,不记录密码、Token、密钥、支付原文或完整个人敏感信息。
迁移、打包与发布
增量数据库变更写入 migrations/{semver}_{desc}.sql,表前缀使用 tableprefix_,已发布迁移不可改名或覆盖。更新只运行待执行迁移。
开发者中心打包时会按 Model 归属从当前开发数据库重新导出首装 install/sql 基线;固定表同时导出数据。首次安装执行基线后调用 Migration::baseline(),更新会跳过含 DROP 的基线,仅运行 Migration::runPending()。因此每次升级的结构和数据变化都必须有新迁移。
包内路径必须保持 app/{name}/...。打包自动排除 .svn、.git、node_modules、站点生成语言包等内容。应用级版本只看 module.json.version;不要创建或依赖应用级 .version。
- 校验所有写接口的登录态、权限与输入格式。
- 检查上传类型、大小、文件名与路径穿越风险。
- 确认响应、日志和页面不包含密码、Token 或密钥。
- 验证 MySQL/达梦兼容与失败反馈。
- 提升
module.json.version,填写变更说明后由开发者中心提交。
验收与排错
| 现象 | 优先检查 |
|---|---|
| 后台不显示应用 | 应用是否启用;Admin.php 类级 @title;Manifest 是否为合法 JSON |
| 菜单不显示 | 方法是否 public、带方法级 @title;当前角色是否拥有节点 |
| API 找不到类或方法 | api 参数、文件、命名空间、类名、方法名是否完全一致 |
| 安装后缺表 | 每张业务表是否存在对应 Model,首装 SQL 是否完整 |
| 更新后字段未变化 | 是否新增更高版本迁移;不要只修改首装 SQL |
| 页面改动未生效 | 只改源码,随后后台清缓存/触发静态构建;不要改 public/@/ |
| 仅超级管理员可用 | 使用普通角色验证 PHPDoc 权限节点与菜单授权 |
| 达梦执行失败 | 检查反引号、MySQL 专有函数、字段类型和多表更新语法,按项目适配方式改写 |