04a 插件开发详细教程
适合第一次给可得财务写插件的开发者。按本文可在 30 分钟内 跑通一个可安装、可配置、带钩子的 addon。
欢迎加入制作团队:QQ 群
你将完成什么
- 在
public/plugins/addon/hello_world/放好示例代码(系统已内置同款示例,可对照) - 后台「本地插件」安装并启用
- 用户注册时写入一条欢迎日志(钩子)
- 在后台改插件配置并生效
环境准备
| 项 | 要求 |
|---|---|
| PHP | 8.1+(与正式站一致) |
| 系统 | 已安装可得财务(本地或测试站) |
| 权限 | 可写 public/plugins/ |
| 参考代码 | public/plugins/addon/hello_world/(示例)或 login_log/(生产级示例) |
目录约定(务必使用单数 addon):
public/plugins/{type}/{snake_name}/
{CamelName}.php ← 主类,命名空间 type\snake_name
hooks.php ← 可选,注册钩子
config.php ← 可选,默认配置第一步:主类
创建 public/plugins/addon/hello_world/HelloWorld.php:
php
<?php
declare(strict_types=1);
namespace addon\hello_world;
use app\common\lib\Plugin;
use think\facade\Db;
use think\facade\Log;
class HelloWorld extends Plugin
{
public $info = [
'name' => 'HelloWorld',
'title' => 'Hello 示例插件',
'description' => '官方文档配套示例:安装建表、钩子、配置项',
'author' => 'KeDe Docs',
'version' => '1.0.0',
];
public function install()
{
$prefix = config('database.connections.mysql.prefix');
Db::execute("CREATE TABLE IF NOT EXISTS `{$prefix}addon_hello_world` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL DEFAULT 0,
`message` VARCHAR(255) NOT NULL DEFAULT '',
`create_time` INT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
return true;
}
public function uninstall()
{
$prefix = config('database.connections.mysql.prefix');
Db::execute("DROP TABLE IF EXISTS `{$prefix}addon_hello_world`");
return true;
}
/** 后台「配置」表单字段 */
public function configFields(): array
{
return [
[
'name' => 'welcome_text',
'label' => '欢迎文案',
'type' => 'text',
'default' => '欢迎加入本站',
'required'=> false,
],
[
'name' => 'enabled_log',
'label' => '写入日志表(1/0)',
'type' => 'text',
'default' => '1',
'required'=> false,
],
];
}
/** 供 hooks.php 调用 */
public static function onUserRegister(array $param): void
{
$uid = (int) ($param['user_id'] ?? 0);
if ($uid <= 0) {
return;
}
$plugin = new self();
$cfg = $plugin->getConfig();
$text = trim((string) ($cfg['welcome_text'] ?? '欢迎加入本站'));
if ((string) ($cfg['enabled_log'] ?? '1') === '0') {
return;
}
try {
Db::name('addon_hello_world')->insert([
'user_id' => $uid,
'message' => $text,
'create_time' => time(),
]);
} catch (\Throwable $e) {
Log::warning('[hello_world] ' . $e->getMessage());
}
}
}要点:
$info['name']用 PascalCase,目录用 snake_caseinstall/uninstall必须返回true才算成功- 配置用
configFields()+getConfig(),不要自己另搞一套存储
第二步:钩子
public/plugins/addon/hello_world/hooks.php:
php
<?php
use addon\hello_world\HelloWorld;
// 用户注册成功后(snake_case / CamelCase 均可匹配)
add_hook('after_user_register', function ($param) {
HelloWorld::onUserRegister(is_array($param) ? $param : []);
});常用钩子见 11-hooks。调试时可先在回调里 Log::info(...)。
第三步:默认配置
public/plugins/addon/hello_world/config.php:
php
<?php
return [
'welcome_text' => '欢迎加入本站',
'enabled_log' => '1',
];第四步:安装与验证
- 后台 → 插件 → 本地插件 → 类型选「功能扩展」
- 找到「Hello 示例插件」→ 安装 → 启用
- 前台注册一个新用户
- 数据库查看表
{prefix}addon_hello_world是否有记录 - 后台打开插件配置,改欢迎文案后再注册,确认新文案写入
若列表看不到插件:
- 检查命名空间是否为
addon\hello_world - 检查主类文件名是否与
$info['name']一致(HelloWorld.php) - 检查 PHP 语法错误(站点日志 /
runtime/log)
按类型扩展(简表)
| 类型 | 目录 | 基类 | 必做 |
|---|---|---|---|
| 功能 addon | addon/ | Plugin | hooks / 可选建表 |
| 支付 | gateway/ | GatewayPlugin | pay() / notify() |
| 短信 | sms/ | SmsPlugin | send() |
| 邮件 | mail/ | MailPlugin | send() |
| 实名 | certification/ | CertificationPlugin | initVerify() 等 |
打包分发
生产环境推荐 zip 压缩包,不要依赖开发机上的 resources/marketplace/packages/。
建议 zip 内结构:
hello_world/
HelloWorld.php
hooks.php
config.php
README.md客户解压到 public/plugins/addon/hello_world/ 后,在「本地插件」安装。市场流程见 05-marketplace。
调试清单
| 现象 | 排查 |
|---|---|
| 安装失败 | install() 是否 return true;建表 SQL 是否报错 |
| 钩子不执行 | 是否已 启用;hooks.php 是否被加载;钩子名是否正确 |
| 配置不生效 | 是否点了保存;代码是否读 getConfig() |
| 类找不到 | 命名空间 / 目录 / 自动加载(PluginService::registerAutoload) |
安全与合规(必读)
- 不要在插件里硬编码站点密钥、支付商户私钥以外的系统私密信息
- 不要尝试绕过财务系统的授权校验中间件
- 用户输入写库前做过滤;对外 HTTP 请求设超时
- 公开文档与示例 不包含 授权站管理接口、内部密钥与通信协议细节
下一步
- 04 插件系统(概览)
- 04b 插件对接 API(完整) — 管理/驱动/魔方/自定义 REST
- 05 插件市场
- 11 钩子系统
- 10a 主题开发详细教程