diff --git a/.env.example b/.env.example index 9a49f06..4257524 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,9 @@ MINIO_PORT=9000 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_BUCKET=kt-template-online +BLOG_LIVE2D_ALLOWED_ORIGINS=https://blog.kwitsukasa.top,http://localhost:5999 +BLOG_LIVE2D_BUCKET=kt-template-online +BLOG_LIVE2D_PREFIX=blog/live2d/pio WORDPRESS_BASE_URL=http://localhost WORDPRESS_HOST_HEADER= diff --git a/API.md b/API.md index 5fd670b..8c266da 100644 --- a/API.md +++ b/API.md @@ -93,7 +93,7 @@ Admin、Component、Dict、MinIO、Blog 管理、WordPress 管理和 QQBot 管 | 分组 | 关键变量 | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | MySQL | `DB_HOST`、`DB_PORT`、`DB_USERNAME`、`DB_PASSWORD`、`DB_DATABASE`、`DB_SYNC` | -| MinIO | `MINIO_ENDPOINT`、`MINIO_PORT`、`MINIO_ACCESS_KEY`、`MINIO_SECRET_KEY`、`MINIO_BUCKET` | +| MinIO | `MINIO_ENDPOINT`、`MINIO_PORT`、`MINIO_ACCESS_KEY`、`MINIO_SECRET_KEY`、`MINIO_BUCKET`、`BLOG_LIVE2D_ALLOWED_ORIGINS`、`BLOG_LIVE2D_BUCKET`、`BLOG_LIVE2D_PREFIX` | | Admin | `ADMIN_TOKEN_SECRET`、`ADMIN_COOKIE_SECURE`、`SNOWFLAKE_WORKER_ID`、`SNOWFLAKE_DATACENTER_ID` | | WordPress | `WORDPRESS_BASE_URL`、`WORDPRESS_HOST_HEADER`、`WORDPRESS_ADMIN_USERNAME`、`WORDPRESS_ADMIN_PASSWORD` | | Loki | `LOG_LEVEL`、`LOG_APP_NAME`、`LOKI_URL`、`LOKI_QUERY_HOST`、`LOKI_QUERY_SELECTOR` | @@ -331,9 +331,12 @@ WordPress rewrite 未开启导致 `/wp-json/*` 返回 404 时,后端会回退 | `GET` | `/minio/resource-proxy` | 代理读取资源 | | `GET` | `/minio/download` | 下载文件流 | | `DELETE` | `/minio/remove` | 删除文件 | +| `GET` | `/blog/live2d/pio/:version/*assetPath` | 公开读取 Pio Live2D 运行包资源,按 Referer/Origin 白名单防盗链 | `bucketName` 不传时使用 `MINIO_BUCKET`。 +Blog Live2D 运行包读取入口不使用 Vben 响应包装,直接返回 MinIO 文件流。`BLOG_LIVE2D_ALLOWED_ORIGINS` 配置允许的 Blog 源,例如 `https://blog.kwitsukasa.top,http://localhost:5999`;`BLOG_LIVE2D_BUCKET` 和 `BLOG_LIVE2D_PREFIX` 决定对象位置。路由支持嵌套资源路径,如 `textures/texture_00.png` 和 `motions/idle.motion3.json`,并拒绝绝对 URL、反斜杠、`.`/`..` 和多重编码后的路径逃逸。 + ## QQBot 管理 QQBot 运行态包括 NapCat 容器登录、OneBot v11 反向 WebSocket、MQTT 事件总线、账号能力绑定、在线命令、自动回复规则、权限名单、发送/接收日志和插件生态。 diff --git a/README.md b/README.md index f702a70..cd621b0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ | `qqbot/plugins/bilibili-card` | 解析 QQ/NapCat Bilibili 卡片和短链,按账号事件绑定回复封面图和视频文字摘要 | | `qqbot/plugins/ff14-market` | XIVAPI + Universalis 物品解析和 FF14 市场查价 | | `qqbot/plugins/fflogs` | FFLogs v2 GraphQL 角色排名和指定高难最近记录查询 | -| `minio` | Bucket 检查、上传、列表、临时 URL、代理下载、删除 | +| `minio` | Bucket 检查、上传、列表、临时 URL、代理下载、删除,以及 Blog Live2D 运行包受控读取入口 | | `common` | 响应封装、异常过滤、请求日志、日期格式化、字典解码、Snowflake、工具服务 | ## 目录结构 @@ -59,7 +59,7 @@ ci/ Jenkins Agent/Docker 辅助文件 | 分组 | 变量 | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | MySQL | `DB_HOST`、`DB_PORT`、`DB_USERNAME`、`DB_PASSWORD`、`DB_DATABASE`、`DB_SYNC` | -| MinIO | `MINIO_ENDPOINT`、`MINIO_PORT`、`MINIO_ACCESS_KEY`、`MINIO_SECRET_KEY`、`MINIO_BUCKET` | +| MinIO | `MINIO_ENDPOINT`、`MINIO_PORT`、`MINIO_ACCESS_KEY`、`MINIO_SECRET_KEY`、`MINIO_BUCKET`、`BLOG_LIVE2D_ALLOWED_ORIGINS`、`BLOG_LIVE2D_BUCKET`、`BLOG_LIVE2D_PREFIX` | | Admin | `ADMIN_TOKEN_SECRET`、`ADMIN_COOKIE_SECURE`、`SNOWFLAKE_WORKER_ID`、`SNOWFLAKE_DATACENTER_ID` | | WordPress | `WORDPRESS_BASE_URL`、`WORDPRESS_HOST_HEADER`、`WORDPRESS_ADMIN_USERNAME`、`WORDPRESS_ADMIN_PASSWORD`、`WORDPRESS_*_TIMEOUT_MS` | | Logging/Loki | `LOG_LEVEL`、`LOG_APP_NAME`、`LOKI_URL`、`LOKI_QUERY_HOST`、`LOKI_*` | @@ -71,6 +71,8 @@ ci/ Jenkins Agent/Docker 辅助文件 `DB_SYNC=true` 只适合本地开发或明确允许自动同步表结构的环境;生产应关闭并使用 SQL/迁移脚本。 +Blog Live2D 运行包存放在 MinIO,公开读取入口为 `/blog/live2d/pio/:version/*assetPath`。`BLOG_LIVE2D_ALLOWED_ORIGINS` 是允许加载 Pio runtime 的 Blog 域名白名单,`BLOG_LIVE2D_BUCKET` 和 `BLOG_LIVE2D_PREFIX` 指向运行包对象位置;未通过 Referer/Origin 白名单的请求会在读取 MinIO 前拒绝。 + QQBot 插件 worker 使用 BullMQ 队列串行执行同一插件安装实例的请求。K8s 生产清单包含内部服务 `kt-qqbot-plugin-redis`,生产 env 可将 `QQBOT_PLUGIN_QUEUE_REDIS_HOST` 配为该服务名。`QQBOT_PLUGIN_QUEUE_WAIT_TIMEOUT_MS` 控制排队等待窗口,插件 `operation.timeoutMs` 仍表示单次执行预算。 QQBot 插件定时任务由 manifest 的 `tasks` 声明,平台持久化到 `qqbot_plugin_task` / `qqbot_plugin_task_run`,通过 BullMQ Job Scheduler 调度并经插件 worker 的 `executeTask` 边界执行。`sql/qqbot-init.sql` 可为既有环境增量创建任务表和 Admin 菜单。Admin 页面路径为 `/qqbot/plugin-task`。定时任务队列可用 `QQBOT_PLUGIN_TASK_QUEUE_REDIS_*` 单独配置;留空时复用插件 worker 队列的 Redis 连接。BangDream Bestdori 主数据缓存使用 `BANGDREAM_TSUGU_CACHE_ROOT`,生产清单挂载到容器内 `/data/qqbot/plugins/bangdream/cache`,对应 k3d 节点可写 hostPath `/var/lib/rancher/k3s/kt-template-online-api/qqbot-plugins`。 diff --git a/src/modules/asset/application/blog-live2d-asset.service.ts b/src/modules/asset/application/blog-live2d-asset.service.ts new file mode 100644 index 0000000..e1ee2a1 --- /dev/null +++ b/src/modules/asset/application/blog-live2d-asset.service.ts @@ -0,0 +1,190 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { MinioClientService } from './asset-minio.service'; +import type { + BlogLive2DAssetResult, + BlogLive2DRuntimeAssetPath, +} from '../domain/blog-live2d-asset.types'; + +const DEFAULT_ALLOWED_ORIGINS = 'https://blog.kwitsukasa.top'; +const DEFAULT_LIVE2D_BUCKET = 'kt-template-online'; +const DEFAULT_LIVE2D_PREFIX = 'blog/live2d/pio'; +const MAX_DECODE_DEPTH = 6; + +@Injectable() +export class BlogLive2DAssetService { + /** + * Creates the guarded Blog Live2D asset service. + * @param minioClientService - Existing MinIO helper used to stream versioned Pio runtime files. + * @param configService - Runtime config source for bucket, object prefix, and allowed browser origins. + */ + constructor( + private readonly minioClientService: MinioClientService, + private readonly configService: ConfigService, + ) {} + + /** + * Rejects browser asset requests that do not come from the configured Blog origins. + * @param referer - Browser Referer header; used for normal `