# AGENTS.md — sharelink(Halo 资源下载管理插件) ## 项目概述 Halo 2.x(>=2.22,目标实例 Halo Pro 2.25.4)插件。管理文章中的资源下载链接:按资源的下载统计、邮箱验证、防附件直链、文章引用扫描。邮箱验证体系复制改造自 `~/Documents/Code/Ai/blog-comment`(plugin-comment-widget fork 的 emailcode/guard 包)。 ## 结构 - `src/main/java/run/halo/sharelink/` - `model/` 自定义 Extension(@GVK group `sharelink.halo.run`):DownloadResource(spec.slug 唯一索引)、DownloadRecord(spec.resourceSlug 索引)、VerifiedDownloader(name=SHA-256(归一化邮箱)) - `emailcode/` 验证码:Guava 内存缓存、限流、NotificationCenter 发信(REASON_TYPE `sharelink-download-verification`,模板在 `resources/extensions/notification.yaml`) - `verify/` 公开端点 `api.sharelink.halo.run/v1alpha1`(email-verify/-/send|/-/check)+ VerifiedEmailService(本插件 VerifiedDownloader ∪ 评论插件 VerifiedCommenter,后者运行时 Unstructured fetch,缺席自动降级) - `download/` RouterFunction Bean:`GET /download/{slug}`(自包含 HTML 页)、`GET /download/{slug}/file`(一次性 token 核销 → 记录 → 流式下载);FileStreamer 本地附件直接读盘(`halo.work-dir`/attachments + `storage.halo.run/local-relative-path` 注解),外部存储回环 HTTP(带 `X-Sharelink-Internal` 头) - `protect/UploadProtectFilter` AdditionalWebFilter:GET/HEAD `/upload/**` 命中受保护附件 → 404 - `reference/PostReferenceService` 扫描已发布文章 releaseSnapshot 内容匹配 `/download/{slug}`,5min 缓存 - `console/ConsoleEndpoint` `console.api.sharelink.halo.run/v1alpha1`:资源 CRUD(body 为扁平 spec)、记录分页/删除/CSV、references 查询/刷新 - `src/main/resources/extensions/` settings.yaml、notification.yaml、role-templates.yaml(匿名放行公开 API;console view/manage 角色,`ui-permissions: plugin:sharelink:view/manage`) - `ui/` console 前端:Vue 3 + @halo-dev/ui-shared + ui-plugin-bundler-kit(rsbuild),产物输出到 `src/main/resources/console/`(**构建前会覆盖该目录,勿手改**)。API 层 `ui/src/api/index.ts` 的 `normalizeResource()` 把后端扁平 ResourceVo 转成 Halo 风格结构 ## 构建 / 部署 ```bash pnpm -C ui install && pnpm -C ui build export JAVA_HOME=~/.gradle/jdks/jdk-21.0.12+8/Contents/Home # 本机无系统 JDK ./gradlew build -x test # build/libs/sharelink-.jar ``` 关键经验(踩过的坑): 1. 改代码后发布必须递增 `gradle.properties` 的 version——console 静态资源按 `?version=` 缓存,不递增浏览器会用旧包。 2. Console UI 升级插件会弹「插件已存在,是否升级?」确认框,不点确定不会真正替换 jar。 3. 插件数据存 ExtensionStore(MySQL),卸载/重装插件不丢数据;但 Halo 的插件静态资源目录在重装时才重新解压。 4. 计数在「token 核销 + 附件可流式输出」之后才写入,下载失败不计数;免验证资源按 slug+IP 内存窗口去重。 5. UI 安装有两条路:①「远程下载」需要 Halo 服务器能反向访问本机 jar 服务(本机换网/IP 变化后会失败);②「本地上传」用 webbridge 时 CDP `setFileInputFiles` 被浏览器禁用,可用兜底方案:分块 base64 经 `evaluate` 推到页面 `window.__jarB64` → JS 构造 `File` + `DataTransfer` → 对 `.uppy-Dashboard-inner` 派发合成 `dragenter/dragover/drop` 事件触发 Uppy 上传 → 再点升级确认框「确定」。 6. 下载页自包含 HTML 里若给元素自定义了 `display`(如 flex),必须补 `[hidden] { display: none !important; }`,否则 `hidden` 属性失效导致元素误显示。 ## 测试 端到端验证依赖目标实例(http://192.168.3.2:8090)。公开 API 可用 curl 匿名测试: `POST /apis/api.sharelink.halo.run/v1alpha1/downloads/-/token {"slug":"..."}` → `GET /download/{slug}/file?token=...`。