本文是"个人AI助手平台"系列第五篇。建议先阅读前四篇了解基础架构后再看本文。一、为什么需要插件前面的文章里我们搭好了架构、部署上了云、打通了四个IM通道。但一个只会聊天的 AI 助手太单薄了——用户问"明天会下雨吗",你需要查询天气;用户说"帮我记下这条待办",你需要持久化存储。插件的价值:能力扩展:让 AI 能查天气、做翻译、管理待办——不再是一个只会聊天的花瓶热加载:新增能力不用重启服务,开发者写好插件、丢进插件目录就生效社区生态:参照 VS Code 插件市场模式,任何人都可以贡献插件本文带你从零写出三个完整可运行的插件,并掌握插件开发范式。二、插件规范(Plugin Specification)先定义插件接口——所有插件必须遵守的契约:// plugin-spec.tsexportinterfacePluginManifest{/** 插件唯一标识,如 "weather" */id:string;/** 显示名称 */name:string;/** 版本号 */version:string;/** 作者 */author:string;/** 一句话描述 */description:string;/** 所需权限 */permissions:string[];/** 触发关键词,如 ["天气", "weather"] */triggers:string[];}exportinterfacePluginContext{/** 用户ID */userId:string;/** 所属群聊ID */channelId?:string;/** 插件数据存储路径(持久化用) */dataDir:string;/** 日志函数 */logger:PluginLogger;/** 发送消息到当前通道 */sendMessage:(text:string)=Promisevoid;}exportinterfacePluginLogger{info:(msg:string)=void;warn:(msg:string)=void;error:(msg:string,err?:Error)=void;}/** * 插件基类——所有插件必须继承 */exportabstractclassBasePlugin{abstractreadonlymanifest:PluginManifest;/** * 初始化(插件加载时调用一次) */abstractonInit(ctx:PluginContext):Promisevoid;/** * 收到消息时调用 * @returns true 表示已处理该消息(不再向下传递) */abstractonMessage(message:string,ctx:PluginContext):Promise{handled:boolean;reply?:string};/** * 卸载插件时调用(清理资源) */abstractonDestroy():Promisevoid;}三、插件管理器(动态加载)插件管理器负责扫描插件目录、加载、卸载、热更新:// plugin-manager.tsimport*asfsfrom'fs';import*aspathfrom'path';import{BasePlugin,PluginContext,PluginManifest}from'./plugin-spec';interfaceLoadedPlugin{instance:BasePlugin;manifest:PluginManifest;filePath:string;lastModified:number;}exportclassPluginManager{privateplugins:Mapstring,LoadedPlugin=newMap();privatectx:PluginContext;privatehotWatchInterval:NodeJS.Timeout|null=null;constructor(ctx:PluginContext,privatepluginsDir:string){this.ctx=ctx;}/** * 启动:扫描并加载所有插件、启动热监听 */asyncstart():Promisevoid{awaitthis.loadAll();this.startHotWatch();this.ctx.logger.info(`插件管理器已启动,加载${this.plugins.size}个插件`);}/** * 停止:卸载所有插件 */asyncstop():Promisevoid{if(this.hotWatchInterval)clearInterval(this.hotWatchInterval);for(const[id,loaded]ofthis.plugins){awaitloaded.instance.onDestroy();this.ctx.logger.info(`插件${id}已卸载`);}this.plugins.clear();}/** * 处理消息——遍历所有插件,第一个处理的生效 */asynchandleMessage(message:string):Promisestring|null{for(const[id,loaded]ofthis.plugins){try{constresult=awaitloaded.instance.onMessage(message,this.ctx);if(result.handledresult.reply){this.ctx.logger.info(`插件 [${id}] 处理了消息`);returnresult.reply;}}catch(err){this.ctx.logger.error(`插件 [${id}] 处理消息出错`,errasError);}}returnnull;// 无插件处理}/** * 扫描并加载所有插件 */privateasyncloadAll():Promisevoid{if(!fs.existsSync(this.pluginsDir)){fs.mkdirSync(this.pluginsDir,{recursive:true});return;}constfiles=fs.readdirSync(this.pluginsDir);for(constfileoffiles){if(file.endsWith('.js')||file.endsWith('.ts')){awaitthis.loadPlugin(path.join(this.pluginsDir,file));}}}/** * 加载单个插件 */privateasyncloadPlugin(filePath:string):Promisevoid{try{// 清除 require 缓存以支持热更新constresolvedPath=require.resolve(filePath);deleterequire.cache[resolvedPath];constPluginClass=require(filePath).default;constinstance:BasePlugin=newPluginClass();constmanifest=instance.manifest;if(this.plugins.has(manifest.id)){this.ctx.logger.warn(`插件${manifest.id}已存在,跳过重复加载`);return;}awaitinstance.onInit(this.ctx);conststats=fs.statSync(filePath);this.plugins.set(manifest.id,{instance,manifest,filePath,lastModified:stats.mtimeMs,});this.ctx.logger.info(`插件 [${manifest.id}] v${manifest.version}加载成功`);}catch(err){this.ctx.logger.error(`加载插件失败:${filePath}`,errasError);}}/** * 启动热监听:每 3 秒检查插件文件是否变化 */privatestartHotWatch():void{this.hotWatchInterval=setInterval(async()={for(const[id,loaded]ofthis.plugins){try{conststats=fs.statSync(loaded.filePath);if(stats.mtimeMsloaded.lastModified){this.ctx.logger.info(`检测到插件 [${id}] 文件变化,热更新中...`);awaitloaded.instance.onDestroy();this.plugins.delete(id);awaitthis.loadPlugin(loaded.filePath);}}catch(err){// 文件可能被删除,忽略}}},3000);}/** * 获取已加载插件列表 */getLoadedPlugins():PluginManifest[]{returnArray.from(this.plugins.values()).map(p=p.manifest);}}四、实战一:天气查询插件4.1 设计思路触发词:天气、weather、会下雨、多少度依赖:调用公开天气 API(Open-Meteo,免费无需 Key)返回格式:城市名 + 温度 + 天气状况 + 穿衣建议4.2 完整代码