插件开发 1 更新于 2026-10-13

插件开发 · 数据库与模型

一、表命名与建表

  • 表前缀统一 ws_,插件自建表建议加上插件标识:ws_myplugin_log
  • 字符集统一 utf8mb4,引擎 InnoDB
  • 每张表保留 create_time / update_time(int unsigned,Unix 秒)
  • 需要软删除的表加 delete_time(可空)
CREATE TABLE IF NOT EXISTS `ws_myplugin_log` (
  `id`          int(10) unsigned NOT NULL AUTO_INCREMENT,
  `site_id`     int(10) unsigned NOT NULL DEFAULT 0,
  `content`     varchar(255) NOT NULL DEFAULT '',
  `ip`          varchar(64)  NOT NULL DEFAULT '',
  `create_time` int(10) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_site` (`site_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件日志';

多站点项目里,自建表请加 site_id 并在查询时过滤,避免站点间数据串台。

二、执行 SQL

安装、升级脚本放在 install/ 下,也可在生命周期回调中直接执行:

use think\facade\Db;

Db::execute("ALTER TABLE `ws_myplugin_log` ADD COLUMN `ua` varchar(255) NOT NULL DEFAULT ''");

三、读写数据

3.1 查询构造器

use think\facade\Db;

// 查询
$list = Db::name('myplugin_log')->where('site_id', $siteId)->order('id desc')->limit(20)->select();
$one  = Db::name('myplugin_log')->where('id', $id)->find();
$cnt  = Db::name('myplugin_log')->where('site_id', $siteId)->count();
$val  = Db::name('myplugin_log')->where('id', $id)->value('content');

// 写入
$id = Db::name('myplugin_log')->insertGetId([
    'content'     => 'hello',
    'create_time' => time(),
]);

// 更新
Db::name('myplugin_log')->where('id', $id)->update(['content' => 'updated']);

// 删除
Db::name('myplugin_log')->where('id', $id)->delete();

3.2 事务

多步写入必须放在事务中:

Db::transaction(function () use ($order) {
    Db::name('order')->where('id', $order['id'])->update(['status' => 1]);
    Db::name('license')->insert([
        'order_no'    => $order['order_no'],
        'create_time' => time(),
    ]);
});

四、模型

需要复杂逻辑时定义模型:

<?php
namespace addons\myplugin\model;

use think\Model;

class Log extends Model
{
    protected $name     = 'myplugin_log';   // 物理表名(不含前缀)
    protected $autoWriteTimestamp = 'int';
    protected $createTime = 'create_time';
    protected $updateTime = 'update_time';

    protected $type = [
        'create_time' => 'timestamp:Y-m-d H:i',
    ];

    /** 关联站点 */
    public function site()
    {
        return $this->belongsTo(\app\common\model\Site::class, 'site_id');
    }

    /** 查询作用域 */
    public function scopeToday($query)
    {
        return $query->where('create_time', '>=', strtotime('today'));
    }
}

用法:

Log::where('site_id', $siteId)->today()->paginate(20);

⚠️ 模型的 $name 是物理表名(不含前缀),与表名含保留字时需注意:ws_model 的物理表名列本身叫 ` table `,是 MySQL 保留字,写 SQL 时要用反引号。

五、容易踩的坑

坑现象正确做法
->column('id,sort') 不可靠拿不到 [id => sort] 映射改用 field('id,sort')->select()->toArray() 自行遍历
排序字段加反引号order express errororder('sort desc') 不加反引号
runtime_path() 在应用内缓存写到了 runtime/<应用>/自定义缓存用项目根 runtime/<名字>/
未过滤 site_id站点间数据串台所有查询都带上 site_id
直写 delete_time 判断已删数据仍出现用 whereNull('delete_time')

六、数据迁移建议

  • 用 info.ini 的 version 驱动升级:upgrade($from,$to) 里按版本区间做增量变更
  • 不要在升级里 DROP TABLE,除非用户明确确认
  • 结构变更前先备份:后台 → 运维工具 → 数据备份
  • 所有变更脚本要可重复执行(ADD COLUMN IF NOT EXISTS 之类),避免中途失败后无法重跑