aliyunpan 阿里云盘客户端 JavaScript 插件开发指南:回调机制、内置 API 与 8 大实战场景全解析

📅 发布时间:2026/9/18 23:51:18
aliyunpan 阿里云盘客户端 JavaScript 插件开发指南:回调机制、内置 API 与 8 大实战场景全解析
aliyunpan 阿里云盘客户端 JavaScript 插件开发指南回调机制、内置 API 与 8 大实战场景全解析【免费下载链接】aliyunpan阿里云盘命令行客户端支持JavaScript插件支持同步备份功能。项目地址: https://gitcode.com/GitHub_Trending/ali/aliyunpan本篇技术指南以阿里云盘命令行客户端 aliyunpan 的 JS 插件系统为核心系统讲解如何通过 JavaScript 脚本定制上传、下载、同步、删除、Token 刷新等关键环节的自动化行为。读完本文你将掌握插件的文件结构与加载规则、9 类回调函数的参数与返回值契约、10 个内置 PluginUtil API 的用法与底层实现以及过滤敏感文件、自动改名、云端删源、外部通知、限流下载等 8 个可直接落地的实战脚本模板。插件系统概述aliyunpan 是阿里云盘的命令行客户端其核心特性之一便是内置 JavaScript 插件引擎。通过 JS 插件用户可以按照自己的需要定制上传、下载、同步备份、删除过程中关键步骤的行为最大程度满足个性化需求。插件系统把上传前/后、下载前/后、同步扫描前、文件删除前、Token 刷新后等关键节点抽象为回调钩子开发者只需在脚本中按约定定义同名函数引擎就会在对应时机自动调用。官方文档docs/plugin_manual.md列举了插件能实现的典型能力排除某个特定敏感文件的上传上传文件进行改名但本地文件不做更改上传完成文件后删除本地文件修改上传到云盘的文件路径本地文件保持不变上传文件成功后通过 HTTP 通知其他服务排除某些网盘文件的下载下载的文件进行改名网盘文件保持不变修改下载保存的本地路径网盘文件保持不变下载文件完成后通过 HTTP 通知其他服务同步备份功能中过滤本地文件或云盘文件定制需要同步的文件。从实现层面看插件引擎基于 Go 语言编写的gojaECMAScript 5.1解释器构建位于 internal/plugins/js_plugin.go引擎启动时会把console与PluginUtil两大对象注入到 JS 运行时见 internal/plugins/js_plugin.go。因此你不需要在系统上安装 Node.js 或其他 JS 运行时插件脚本由客户端进程内嵌解释执行。插件使用方式插件文件的存放位置与命名JS 插件的样本文件默认存放在程序所在目录的plugin/js文件夹下对应仓库中的 assets/plugin/js共 5 个样本文件对应功能download_handler.js.sample下载插件定制下载前/后的行为upload_handler.js.sample上传插件定制上传前/后的行为remove_handler.js.sample删除插件定制删除文件前行为sync_handler.js.sample同步备份插件过滤同步文件、同步完成通知token_handler.js.sample用户 Token 插件Token 刷新失败通知关键约定必须拷贝一份并将后缀名从.sample改为.js插件才会生效例如upload_handler.js。此外还有两点需要注意如果你通过环境变量ALIYUNPAN_CONFIG_DIR设置了自定义配置目录则需要将plugin文件夹拷贝到该配置目录中插件才会被加载。编写插件需要一定的 JavaScript 语言基础如果不会 JS也可以到项目 issue 区寻求开发者或网友提供现成脚本。加载规则源码级验证插件管理器在 internal/plugins/plugin_manager.go 中实现了加载逻辑可以从源码确认以下规则插件目录固定为pluginPath/js目录不存在时直接回退到空插件NewIdlePlugin只加载以.js不区分大小写结尾的文件.sample后缀因此不会被加载这正是必须改名约定的原因以.或~开头的文件如临时文件、备份文件会被跳过多个.js文件会被依次加载进同一个 goja 运行时因此多个文件可以共享全局变量与函数但也要注意避免函数名冲突只要有一个 JS 文件加载成功插件即生效全部加载失败则使用空插件程序照常运行。回调函数的执行遵循存在才调用的原则例如上传前回调在 internal/plugins/js_plugin.go 中先检查uploadFilePrepareCallback是否存在不存在则直接返回nil不影响正常上传流程。这意味着你的脚本不需要定义全部回调按需定义即可。回调函数体系9 类钩子与参数契约插件的核心是回调函数。所有回调统一签名function xxxCallback(context, params)其中context当前调用上下文由GetContext构造见 internal/plugins/plugin_manager.go包含应用名、版本、当前登录用户 ID、昵称以及备份盘/相册盘/资源盘的网盘 IDparams本次回调的业务参数由各命令模块按对应结构体填充定义见 internal/plugins/plugin.go。9 类回调及其触发时机如下回调函数触发时机返回结构uploadFilePrepareCallback每个文件上传前{uploadApproved, driveFilePath}uploadFileFinishCallback每个文件上传结束后无downloadFilePrepareCallback每个文件下载前{downloadApproved, localFilePath}downloadFileFinishCallback每个文件下载结束后无syncScanLocalFilePrepareCallback同步备份扫描本地文件前{syncScanLocalApproved}syncScanPanFilePrepareCallback同步备份扫描云盘文件前{syncScanPanApproved}syncFileFinishCallback同步备份单个文件同步结束后无syncAllFileFinishCallback同步任务全部文件完成后仅只运行一次备份模式无removeFilePrepareCallback删除命令执行前{result: [{driveId, driveFileId, removeApproved}]}userTokenRefreshFinishCallback用户 Token 刷新完成后无回调钩子贯穿各功能命令上传命令在 internal/command/upload.go 中获取插件并调用UploadFilePrepareCallback下载任务在 internal/functions/pandownload/download_task_unit.go 中调用下载类回调删除命令则在 internal/command/utils.go 中调用RemoveFilePrepareCallback。这也解释了为什么改动插件后重启客户端即可生效——插件是在每次命令执行时重新加载的。关键回调参数详解以上传前回调uploadFilePrepareCallback为例参数定义见 internal/plugins/plugin.goparams完整字段如下{ localFilePath: D:\\Program Files\\aliyunpan\\Downloads\\token.bat, // 本地文件绝对完整路径 localFileName: token.bat, // 本地文件名 localFileSize: 125330, // 本地文件大小单位B localFileType: file, // 文件类型file-文件folder-文件夹 localFileUpdatedAt: 2022-04-14 07:05:12, // 文件修改时间 driveId: 19519221, // 目标网盘ID driveFilePath: aliyunpan/Downloads/token.bat // 目标网盘保存路径相对路径 }返回结果中uploadApprovedyes允许上传no禁止上传driveFilePath修改后的网盘保存路径相对路径为空字符串表示保持原本目标路径。注意修改路径要小心重名文件可能只会上传一个该提示同样见于 assets/plugin/js/upload_handler.js.sample 的注释。下载前回调downloadFilePrepareCallback的params则包含driveId、driveFileName、driveFilePath、driveFileSha1、driveFileSize、driveFileType、driveFileUpdatedAt、localFilePath以及downloadActionId本次下载动作的 ID可用于统计单次下载数量返回结果downloadApprovedyes/no与localFilePath修改后的本地保存路径。同步备份相关的两个扫描回调返回syncScanLocalApproved/syncScanPanApprovedyes/no被禁止扫描的文件不会执行后续的上传或下载动作。样本 assets/plugin/js/sync_handler.js.sample 演示了如何过滤以.开头、以~$开头的 Office 暂存文件、.txt文件及eadir等。JS 内置函数PluginUtil API引擎向每个插件脚本注入了console与PluginUtil两大全局对象注册代码见 internal/plugins/js_plugin.go用于日志输出、网络请求、文件操作、邮件通知、键值存储与哈希计算增强插件的扩展性与可玩性。以下逐一说明用法与底层实现。console.log() / console.println()console.log(hello world)打印日志需要开启 debug 日志才会在控制台窗口显示。底层走logger.Verboseln详细日志级别输出带JAVASCRIPT:前缀。console.println(hello world)打印日志无需开启 debug 日志直接显示在控制台窗口输出带[PLUGIN]前缀。两个函数的 Go 实现分别见 internal/plugins/js_plugin.go。在调试脚本时建议用console.println观察参数内容正式环境再改用console.log减少干扰。PluginUtil.Http.get(header, url)发起 HTTP GET 请求返回响应体字符串。var header { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36, Content-Type: application/json, Accept: application/json }; try { var r PluginUtil.Http.get(header, https://625f528c53a42eaa07f37e13.mockapi.io/files/1); console.log(r); } catch (e) { if (e ! Error) { throw e; } }底层实现internal/plugins/plugin_util.go使用requester.NewHTTPClient()发起请求请求失败时返回空字符串并输出详细日志。PluginUtil.Http.post(header, url, data)发起 HTTP POST 请求data为字符串形式的请求体通常由JSON.stringify(...)生成返回响应体字符串。var header { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36, Content-Type: application/json, Accept: application/json }; try { var reqDataStr JSON.stringify({ id: 1, localFilePath: /usr/local/src/borders_burundi_producer.gram.aab, localFileSize: 1111, uploadApproved: false }); var r PluginUtil.Http.post(header, https://625f528c53a42eaa07f37e13.mockapi.io/files, reqDataStr); console.log(r); } catch (e) { if (e ! Error) { throw e; } }这对函数是上传/下载完成后通知外部服务如 Webhook、Server 酱、企业微信机器人等的基础能力。PluginUtil.LocalFS.deleteFile(localFilePath)删除本地指定文件不支持文件夹返回布尔值表示是否删除成功。PluginUtil.LocalFS.deleteFile(localFilePath); 其中 localFilePath - 本地文件的绝对完整路径PluginUtil.LocalFS.deleteFile(/Users/tickstep/Downloads/IMG_0884.HEIC);底层直接调用 Go 的os.Removeinternal/plugins/plugin_util.go删除失败文件不存在、权限不足等返回false。典型用途是上传完成后删除本地文件以释放磁盘空间。PluginUtil.PanFS.deleteFile(userId, driveId, panFileId)删除云盘指定文件支持文件和文件夹返回布尔值。PluginUtil.PanFS.deleteFile(userId, driveId, panFileId); 其中 userId - 登录的用户ID driveId - 网盘ID panFileId - 网盘文件IDvar userId context[userId] var driveId params[driveId] var driveFileId params[driveFileId] if (PluginUtil.PanFS.deleteFile(userId, driveId, driveFileId)) { console.println(插件删除云盘文件成功) }底层实现internal/plugins/plugin_util.go会根据userId从配置的用户列表中查找对应用户再调用其 OpenAPI 客户端的FileDelete接口。注意userId、driveId、panFileId任一为空都会直接返回false。典型用途是下载完成后删除云盘源文件实现类似搬库的效果。PluginUtil.Email.sendTextMail(...) / sendHtmlMail(...)发送邮件通知两个函数分别发送纯文本邮件与 HTML 富文本邮件均返回布尔值。PluginUtil.Email.sendTextMail(mailServer, userName, password, to, subject, body) PluginUtil.Email.sendHtmlMail(mailServer, userName, password, to, subject, body) 其中 mailServer - smtp服务器端口例如 smtp.qq.com:465 userName - 发件人邮箱地址 password - 发件人邮箱密码授权码 to - 收件人邮箱地址 subject - 邮件标题 body - 邮件内容纯文本或HTML富文本纯文本邮件示例PluginUtil.Email.sendTextMail(smtp.qq.com:465, 123xxxqq.com, pwdxxxxxx, 12545xxxqq.com, 文件上传通知, 该文件已经上传完毕);HTML 富文本邮件示例var html html body h1文件上传通知/h1 h2该文件已经上传完毕/h2 /body /html; PluginUtil.Email.sendHtmlMail(smtp.qq.com:465, 123xxxqq.com, pwdxxxxxx, 12545xxxqq.com, 文件上传通知, html);底层实现internal/plugins/plugin_util.go使用smtp.PlainAuth认证并通过SendWithStartTLSSSL 端口发送。请确保发件邮箱具备 SMTP 发送权限没有的需要找邮箱服务商开启例如 QQ 邮箱需获取授权码而非登录密码。PluginUtil.KV.putString(key, value) / getString(key)插件的轻量键值存储用于在多次命令运行之间持久化状态例如记录上次通知时间、累计下载数量。PluginUtil.KV.putString(key, value) // 存储键值对key/value 均为字符串 PluginUtil.KV.getString(key) // 获取存储的值如果没有则返回空字符串PluginUtil.KV.putString(mykey, 1670419352); var value PluginUtil.KV.getString(mykey);底层使用嵌入式BoltDB持久化存储文件路径由config.GetPluginKvFile()指定见 internal/plugins/js_plugin.go读取与写入均带互斥锁与 JSON 序列化internal/plugins/plugin_util.go。这保证了插件状态跨命令、跨进程重启依然存在——注意getString返回的是字符串做时间比较等运算时需要自行parseInt/Date.parse。PluginUtil.HashTool.md5Hex(text)计算指定字符串的 MD5 值返回 hex 格式字符串。PluginUtil.HashTool.md5Hex(text) 其中 text - 字符串var md5 PluginUtil.HashTool.md5Hex(123456);底层即标准库crypto/md5的十六进制输出internal/plugins/plugin_util.go。典型用途是以文件路径为维度生成稳定的去重 Key见下文场景 8。常见场景样例以下 8 个样例直接取自官方文档 docs/plugin_manual.md均可作为插件定制的模板直接套用。使用前请先按上文规则将对应样本拷贝为.js文件。1. 禁止特定文件上传使用上传插件中的uploadFilePrepareCallback通过修改返回结果的uploadApproved字段决定文件是否上传。本样例演示了三种过滤写法以点号开头的隐藏文件、.txt后缀文件正则、指定文件名。function uploadFilePrepareCallback(context, params) { console.log(params) var result { uploadApproved: yes, driveFilePath: }; if (params[localFileType] ! file) { // do nothing return result; } // 禁止点号.开头的文件上传 if (params[localFileName].indexOf(.) 0) { result[uploadApproved] no; } // 禁止.txt文件上传 if (params[localFileName].search(/.txt$/i) 0) { result[uploadApproved] no; } // 禁止password.key文件上传 if (params[localFileName] password.key) { result[uploadApproved] no; } return result; }注意localFileType为folder时脚本直接放行保证文件夹本身照常创建只拦截具体文件。2. 上传文件后删除本地文件使用上传插件中的uploadFileFinishCallback在上传成功uploadResult success后调用PluginUtil.LocalFS.deleteFile删除本地源文件实现云盘留档、本地清理。function uploadFileFinishCallback(context, params) { console.log(params); if (params[localFileType] ! file) { // do nothing return; } if (params[uploadResult] success) { PluginUtil.LocalFS.deleteFile(params[localFilePath]); } }3. 下载文件并截断过长的文件名有些文件的路径或名称太长下载时可能因路径过长导致失败。使用下载插件中的downloadFilePrepareCallback定制下载保存的文件名——将路径拆分出目录、主文件名、后缀名再按需截断主文件名后拼接返回。该方法只影响下载保存路径网盘上的文件保持不变。function downloadFilePrepareCallback(context, params) { console.log(params) var result { downloadApproved: yes, localFilePath: }; if (params[driveFileType] ! file) { return result; } // 下面的代码都是分隔路径方便后面修改路径时使用 var filePath params[localFilePath]; filePath filePath.replace(/\\/g, /); // 目录完整路径 var dirPath ; // 文件名不包括后缀名 var fileName ; // 文件后缀名 var fileExt ; var idx filePath.lastIndexOf(/); if (idx 0) { dirPath filePath.substring(0,idx); fileName filePath.substring(idx1,filePath.length); } else { fileName filePath; } idx fileName.lastIndexOf(.) if (idx 0) { fileExt fileName.substring(idx,fileName.length); fileName fileName.substring(0, fileName.length-fileExt.length) } // 开始按照需要截断太长的文件路径例如下面的这个例子: // // dirPath / 这个的意思是保留前面的文件夹路径如果不需要就去掉 // fileName.substr(0,10) 文件名只取前面10个字符其他的不要了 // fileExt 把文件的后缀名补上 var saveFilePath dirPath / fileName.substr(0,10) fileExt; // 返回 result[localFilePath] saveFilePath; return result; }实际运行效果取自文档网盘源路径/亚马逊书籍合集/亚马逊 kindle ebook 大合集5289册/经济金融 (2)/解密Instagram 《金融时报》和麦肯锡2020年度商业书籍社交应用如何改变世界解锁打造估值千亿美元爆品的核心方法 ) by 莎拉·弗莱尔(z-lib.org).mobi下载后本地保存路径D:\test\亚马逊书籍合集\亚马逊 kindle ebook 大合集5289册\经济金融 (2)\解密Instagra.mobi[1] ---- 文件ID: 630f0623e67c0a1d2e554b1994a29108d089f0d5 文件名: 解密Instagram 《金融时报》和麦肯锡2020年度商业书籍社交应用如何改变世界解锁打造估值千亿美元爆品的核心方法 ) by 莎拉·弗莱尔(z-lib.org).mobi 文件类型: 文件 文件路径: /亚马逊书籍合集/亚马逊 kindle ebook 大合集5289册/经济金融 (2)/解密Instagram 《金融时报》和麦肯锡2020年度商业书籍社交应用如何改变世界解锁打造估值千亿美元爆品的核心方法 ) by 莎拉·弗莱尔(z-lib.org).mobi 插件修改文件下载保存路径为: D:\test\亚马逊书籍合集/亚马逊 kindle ebook 大合集5289册/经济金融 (2)/解密Instagra.mobi [1] 准备下载: /亚马逊书籍合集/亚马逊 kindle ebook 大合集5289册/经济金融 (2)/解密Instagram 《金融时报》和麦肯锡2020年度商业书籍社交应用如何改变世界解锁打造估值千亿美元爆品的核心方法 ) by 莎拉·弗莱尔(z-lib.org).mobi [1] 将会下载到路径: D:\test\亚马逊书籍合集/亚马逊 kindle ebook 大合集5289册/经济金融 (2)/解密Instagra.mobi [1] 下载开始 [1] 下载完成, 保存位置: D:\test\亚马逊书籍合集/亚马逊 kindle ebook 大合集5289册/经济金融 (2)/解密Instagra.mobi [1] 检验文件有效性成功: D:\test\亚马逊书籍合集/亚马逊 kindle ebook 大合集5289册/经济金融 (2)/解密Instagra.mobi4. 上传文件去掉文件名包含的部分字符例如本地文件夹名称为[周杰伦]范特西[mp3]希望上传到网盘后名称变为[周杰伦]范特西同时保持本地名称不变。由于文件夹数量多、不想逐个手动改名可以通过修改driveFilePath批量实现。同样地只改网盘路径本地文件保持不变。function uploadFilePrepareCallback(context, params) { var result { uploadApproved: yes, driveFilePath: }; // 去掉网盘保存路径中包含的[mp3]字段 var filePath params[driveFilePath]; filePath filePath.replace(/\[mp3\]/g, ); result[driveFilePath] filePath; return result; }5. 上传文件时过滤指定目录或者文件路径upload命令本身支持exn参数排除上传文件或目录但只能指定文件名称无法指定文件的路径。如需按绝对路径排除可通过插件脚本实现。本样例中只需在脚本开头维护forbiddenUploadFolders与forbiddenUploadFiles两个清单即可其余代码保持不变。function uploadFilePrepareCallback(context, params) { //自行配置禁止上传的本地【目录列表】使用绝对路径可以配置多个路径用逗号分隔 var forbiddenUploadFolders [/Users/tickstep/Downloads/up/target,/Users/tickstep/Downloads/up/.idea] //自行配置禁止上传的本地【文件列表】使用绝对路径可以配置多个路径用逗号分隔 var forbiddenUploadFiles [/Users/tickstep/Downloads/up/pom.xml] // -------------------- 以下代码不要修改 -------------------- var result { uploadApproved: yes, driveFilePath: }; // 下面的代码都是分隔路径方便后面使用 var filePath params[localFilePath]; filePath filePath.replace(/\\/g, /); // 目录完整路径 var dirPath ; // 文件名不包括后缀名 var fileName ; // 文件后缀名 var fileExt ; var idx filePath.lastIndexOf(/); if (idx 0) { dirPath filePath.substring(0,idx); fileName filePath.substring(idx1,filePath.length); } else { fileName filePath; } idx fileName.lastIndexOf(.) if (idx 0) { fileExt fileName.substring(idx,fileName.length); fileName fileName.substring(0, fileName.length-fileExt.length) } if (params[localFileType] file) { for (var i 0; i forbiddenUploadFiles.length; i) { if (forbiddenUploadFiles[i].replace(/\\/g, /) params[localFilePath]) { result[uploadApproved] no; // 禁止文件上传 break } } for (var i 0; i forbiddenUploadFolders.length; i) { if (forbiddenUploadFolders[i].replace(/\\/g, /) dirPath) { result[uploadApproved] no; // 禁止文件上传 break } } } else if (params[localFileType] folder) { for (var i 0; i forbiddenUploadFolders.length; i) { if (forbiddenUploadFolders[i].replace(/\\/g, /) params[localFilePath]) { result[uploadApproved] no; // 禁止文件上传 break } } } return result; }6. 下载云盘文件到本地后删除云盘对应的文件使用下载插件中的downloadFileFinishCallback在文件成功下载downloadResult success后从context与params中取出userId、driveId、driveFileId调用PluginUtil.PanFS.deleteFile删除云盘源文件实现下载即搬离。function downloadFileFinishCallback(context, params) { console.log(params) // 云盘文件成功下载到本地后删除云盘的文件 if (params[downloadResult] success) { if (params[driveFileType] file) { // 文件下载成功删除该云盘文件 var userId context[userId] var driveId params[driveId] var driveFileId params[driveFileId] if (PluginUtil.PanFS.deleteFile(userId, driveId, driveFileId)) { console.println(插件删除云盘文件成功 params[driveFilePath]) } } } }7. Token 刷新失败发送外部通知当用户 Token 刷新失败时可通过userTokenRefreshFinishCallback发送外部通知如 Server 酱推送或邮件及时提醒手动处理避免静默失效。方案一Server 酱 HTTP 推送。利用PluginUtil.KV记录上次发送时间10 分钟内只发送一次避免频繁打扰function userTokenRefreshFinishCallback(context, params) { var header { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36, Content-Type: application/json, Accept: application/json }; try { if (params[result] fail) { // 避免频繁发送 var ONE_MINUTE 60 * 1000; var lastSendEmailTime PluginUtil.KV.getString(email_last_send_time); if (lastSendEmailTime ! ) { if ((Date.now() - lastSendEmailTime) (10 * ONE_MINUTE)) { console.log(距离上次发送邮件小于10分钟先不发送了); return } } PluginUtil.KV.putString(email_last_send_time, Date.now()); var reqData { text: Token刷新失败, desp: params[message] }; var r PluginUtil.Http.post(header, https://sctapi.ftqq.com/xxxxxkeyxxxxxx.send, JSON.stringify(reqData)); } } catch (e) { if (e ! Error) { throw e; } } }方案二发送邮件通知。同样带 10 分钟限频通过PluginUtil.Email.sendTextMail发送提醒邮件function userTokenRefreshFinishCallback(context, params) { try { if (params[result] fail) { // 避免频繁发送 var ONE_MINUTE 60 * 1000; var lastSendEmailTime PluginUtil.KV.getString(email_last_send_time); if (lastSendEmailTime ! ) { if ((Date.now() - lastSendEmailTime) (10 * ONE_MINUTE)) { console.log(距离上次发送邮件小于10分钟先不发送了); return } } PluginUtil.KV.putString(email_last_send_time, Date.now()); // 发送通知邮件请确保你的发送邮箱具备smtp发送权限没有的需要找邮箱提供商配置开启 console.log(发送通知邮件); PluginUtil.Email.sendTextMail(smtp.qq.com:465, 123xxxqq.com, pwdxxxxxx, 12545xxxqq.com, Token刷新失败了, Token过期了骚年快去手动恢复吧); } } catch (e) { if (e ! Error) { throw e; } } }params[message]中携带 Token 刷新失败的具体原因见 internal/plugins/plugin.go 中UserTokenRefreshFinishParams的定义。8. 每次只下载指定数量的文件利用KV存储与HashTool.md5Hex实现每次运行 download 命令只下载指定数量的文件其余文件留到下一次命令再下载的限流策略。核心思路以downloadActionId为维度统计本次已放行数量以driveFilePath的 MD5 为维度标记单个文件是否已处理过。function downloadFilePrepareCallback(context, params) { var result { downloadApproved: yes, localFilePath: }; // 这次下载限制的最大文件总数量 const maxCountOfDownloadAction 3; // 只处理文件 if (params[driveFileType] ! file) { return } // 获取这次下载动作已经下载的文件数量 var keyOfThisDownloadAction download: params[downloadActionId]; var valueOfThisDownloadAction PluginUtil.KV.getString(keyOfThisDownloadAction); if (valueOfThisDownloadAction ) { valueOfThisDownloadAction 0; } var countOfThisDownloadAction parseInt(valueOfThisDownloadAction); if (countOfThisDownloadAction maxCountOfDownloadAction) { // 下载数量已达到最大其他文件不再下载 result[downloadApproved] no; return result; } // 该文件需要下载记录相关信息 var keyOfThisDownloadFile file: PluginUtil.HashTool.md5Hex(params[driveFilePath]); var valueOfThisDownloadFile PluginUtil.KV.getString(keyOfThisDownloadFile); if (valueOfThisDownloadFile finish) { // 已经在下载的文件不做处理 return result; } PluginUtil.KV.putString(keyOfThisDownloadFile, downloading); // 更新下载总数量 countOfThisDownloadAction 1; PluginUtil.KV.putString(keyOfThisDownloadAction, String(countOfThisDownloadAction)); return result; } function downloadFileFinishCallback(context, params) { var keyOfThisDownloadFile file: PluginUtil.HashTool.md5Hex(params[driveFilePath]); console.log(keyOfThisDownloadFile) PluginUtil.KV.putString(keyOfThisDownloadFile, finish); }由于KV数据持久化在 BoltDB 中即使中途退出程序下次运行命令时也能从上次的计数继续不会重复放行或漏放。进阶技巧与注意事项多文件合并运行所有.js文件会被加载进同一个运行时见 internal/plugins/plugin_manager.go你可以在一个文件里定义回调、在另一个文件里定义公共工具函数它们互相可见。调试三板斧先用console.println(params)打印完整参数确认字段取值HTTP 回调统一用try/catch包裹并判断e ! Error避免网络异常导致上传/下载流程中断注意回调中的 JS 抛错会被引擎捕获并记录详细日志见 internal/plugins/js_plugin.go 的错误处理路径插件异常不会导致客户端崩溃。路径分隔符Windows 路径中的\在回调参数中会保留处理时建议先replace(/\\/g, /)统一为正斜杠再解析上述场景 3、5 已给出标准写法。同步备份场景同步插件的过滤能力比upload/download命令更细粒度——syncScanLocalFilePrepareCallback过滤本地侧文件、syncScanPanFilePrepareCallback过滤云盘侧文件配合syncFileFinishCallback单文件完成与syncAllFileFinishCallback全任务完成仅只运行一次备份模式可以实现完整的同步通知与二次处理详见 assets/plugin/js/sync_handler.js.sample。删除保护removeFilePrepareCallback支持按driveIddriveFileId逐条返回removeApprovedyes/no可用来保护特定文件或目录不被误删参数定义见 internal/plugins/plugin.go。总结aliyunpan 的 JS 插件系统通过 goja 内嵌 JS 引擎 生命周期回调钩子 PluginUtil 工具集三者的组合把上传、下载、同步、删除、Token 刷新五大环节的关键节点完全开放给了用户脚本。本文覆盖了官方文档 docs/plugin_manual.md 的全部内容并补充了 internal/plugins 源码层的机制解读回调的加载与调用规则、参数结构定义、内置 API 的 Go 实现细节以及 5 个样本文件assets/plugin/js的完整回调签名。掌握这些之后无论是过滤敏感文件、自动改名、删除源文件还是对接外部通知服务、实现下载限流都可以用几十行 JS 脚本轻松完成。【免费下载链接】aliyunpan阿里云盘命令行客户端支持JavaScript插件支持同步备份功能。项目地址: https://gitcode.com/GitHub_Trending/ali/aliyunpan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考