feat: sharelink 资源下载管理插件 v1.1.0

- 下载资源管理与 /download/{slug} 下载链接
- 下载统计(次数/去重人数/明细/CSV 导出)
- 按资源邮箱验证(Halo 通知中心发信,互认评论插件已验证邮箱)
- 本地附件防直链(/upload/** 404)
- 文章引用扫描
- Console 前端:Vue3 + ui-plugin-bundler-kit
This commit is contained in:
shirainbown
2026-08-03 00:25:15 +08:00
commit aa4befa0ff
62 changed files with 7838 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# AGENTS.md — sharelinkHalo 资源下载管理插件)
## 项目概述
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`):DownloadResourcespec.slug 唯一索引)、DownloadRecordspec.resourceSlug 索引)、VerifiedDownloadername=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` AdditionalWebFilterGET/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(匿名放行公开 APIconsole view/manage 角色,`ui-permissions: plugin:sharelink:view/manage`
- `ui/` console 前端:Vue 3 + @halo-dev/ui-shared + ui-plugin-bundler-kitrsbuild),产物输出到 `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-<version>.jar
```
关键经验(踩过的坑):
1. 改代码后发布必须递增 `gradle.properties` 的 version——console 静态资源按 `?version=` 缓存,不递增浏览器会用旧包。
2. Console UI 升级插件会弹「插件已存在,是否升级?」确认框,不点确定不会真正替换 jar。
3. 插件数据存 ExtensionStoreMySQL),卸载/重装插件不丢数据;但 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=...`