可道云的插件系统工作原理

  1. 插件系统架构

    • 插件系统采用基于钩子(Hook)的架构
    • 每个插件都继承自 PluginBase 基类
    • 插件通过注册钩子来扩展系统功能
  2. 插件目录结构

    plugins/
    ├── pluginName/
    │   ├── app.php          # 插件主类文件
    │   ├── static/          # 静态资源
    │   ├── php/            # PHP 类文件
    │   └── package.json    # 插件配置文件
    
  3. 插件生命周期

    • 安装:通过 adminPlugin->install() 方法处理
    • 启用/禁用:通过 adminPlugin->changeStatus() 方法控制
    • 卸载:通过 adminPlugin->unInstall() 方法处理
  4. 插件注册机制

    public function regist(){
        $this->hookRegist(array(
            'user.commonJs.insert' => 'pluginName.echoJs',
            // 其他钩子...
        ));
    }
  5. 钩子系统

    • 系统预定义了多个钩子点,如:
      • user.commonJs.insert:插入公共 JS
      • globalRequest:全局请求处理
      • admin.storage.add.before:存储添加前
      • explorer.kodApp.before:应用列表加载前
  6. 插件权限控制

    public function checkAuth($appName){
        $plugin = Model("Plugin")->loadList($appName);
        if (!$plugin) return true;
        if ($plugin['status'] == 0) return false;
        // 权限检查逻辑...
    }
  7. 插件配置管理

    • 每个插件可以有自己的配置文件
    • 通过 getConfig() 方法获取配置
    • 配置项在 package.json 中定义
  8. 插件开发示例

    class examplePlugin extends PluginBase {
        function __construct(){
            parent::__construct();
        }
        
        public function regist(){
            $this->hookRegist(array(
                'user.commonJs.insert' => 'examplePlugin.echoJs',
            ));
        }
        
        public function echoJs(){
            $this->echoFile('static/main.js');
        }
    }
  9. 插件与前端交互

    kodReady.push(function(){
        Events.bind('explorer.kodApp.before',function(appList){
            appList.push({
                name: '{{package.id}}',
                title: '{{package.name}}',
                ext: "{{config.fileExt}}",
                // 其他配置...
            });
        });
    });
  10. 插件安全机制

    • 插件权限检查
    • 插件状态管理
    • 插件配置验证
    • 插件依赖检查

要开发一个插件,您需要:

  1. 创建插件目录结构
  2. 实现 app.php 主类文件
  3. regist() 方法中注册需要的钩子
  4. 实现钩子对应的处理方法
  5. 添加必要的静态资源
  6. 配置 package.json

url访问

[domain]/?plugin/md5Opener/test

SSE连接

// 建立 SSE 连接
const eventSource = new EventSource("/?plugin/md5Opener/test");
 
// 接收消息
eventSource.onmessage = function (event) {
	console.log("收到SSE消息:", event.data);
	// 如果页面有messages元素则显示消息
	const msgDiv = document.getElementById("messages");
	if (msgDiv) {
		msgDiv.innerHTML += `<p>${event.data}</p>`;
	}
};
 
// 连接打开时
eventSource.onopen = function () {
	console.log("SSE 连接已建立");
};
 
// 连接错误时
eventSource.onerror = function (err) {
	console.error("SSE 错误", err);
	// 尝试重新连接
	if (eventSource.readyState === EventSource.CLOSED) {
		console.log("SSE 连接已关闭");
	}
};
 
// 处理自定义结束事件
eventSource.addEventListener('end', function(event) {
	console.log("SSE 连接正常结束:", event.data);
	eventSource.close();
});
public function test()
{
	// 设置 SSE 的响应头
	header("Content-Type: text/event-stream");
	header("Cache-Control: no-cache");
	header("Connection: keep-alive");
	header("Access-Control-Allow-Origin: *"); // 允许跨域请求
	header("X-Accel-Buffering: no"); // 禁用nginx缓冲
 
	// 禁止超时
	set_time_limit(0);
	
	// 清除输出缓冲区
	if (ob_get_level()) {
		ob_end_clean();
	}
 
	$counter = 0;
 
	while (true) {
		$counter++;
		$time = date("Y-m-d H:i:s");
 
		// SSE 格式:必须以 "data:" 开头,以两个换行结尾
		echo "data: 当前时间是 {$time}, 次数 {$counter}\n\n";
 
		// 把数据立即推送给客户端
		if (@ob_get_level()) {
			@ob_flush();
		}
		flush();
		
		// 检查连接是否断开
		if (connection_aborted()) {
			break;
		}
 
		// 每 2 秒推送一次
		sleep(2);
	}
	
	// 正常结束时发送结束标识
	echo "event: end\ndata: 连接已关闭\n\n";
	if (@ob_get_level()) {
		@ob_flush();
	}
	flush();
}