From 4554d4fadeccf44f6d06a936b7c35228d4de590b Mon Sep 17 00:00:00 2001 From: Snowz <372492339@qq.com> Date: Sat, 13 Dec 2025 18:43:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(seed):=20=E6=94=AF=E6=8C=81=E4=BB=8E?= =?UTF-8?q?=E8=BF=9C=E7=A8=8BURL=E5=8A=A0=E8=BD=BDseed.txt=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=A4=E8=AF=81=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增环境变量配置支持从远程Gitea等URL加载seed.txt文件,包括: - SEED_SOURCE: 指定来源(file/url) - SEED_URL: 远程文件地址 - SEED_AUTH_HEADER/SEED_TOKEN: 认证配置 添加远程加载失败时自动回退到本地文件的功能 更新README文档说明配置方法 --- .env.example | 17 ++++++++++++ README.md | 31 ++++++++++++++++++++- seed.txt | 77 ++++++++++++++++++++++++++++++++++++++++++++++++---- server.js | 40 ++++++++++++++++++++++----- 4 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bb3cc5d --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# 服务器端口 +PORT=3000 + +# Seed 来源:file 或 url +SEED_SOURCE=file + +# 当 SEED_SOURCE=url 时,配置远程原始TXT地址 +# 示例:Gitea Raw 文件直链或 API Raw 接口 +# SEED_URL=https://gitea.example.com///raw/seed.txt +SEED_URL= + +# 认证(任选其一) +# 1) 自定义 Authorization 头(例如 Bearer 或 Basic) +# SEED_AUTH_HEADER=Bearer xxxxx +SEED_AUTH_HEADER= +# 2) Token 形式(将自动使用 Authorization: token ) +SEED_TOKEN= diff --git a/README.md b/README.md index 31144a7..d372ec1 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,20 @@ - `npm install && npm start` - 建议使用 systemd/pm2 守护进程,并在 Nginx 反向代理到 `127.0.0.1:` +## 环境变量示例(.env.example) + +- 复制示例文件并按需修改: + - Windows PowerShell:`Copy-Item .env.example .env` + - Linux/Mac:`cp .env.example .env` +- 可用变量: + - `PORT`:服务器端口(默认 `3000`) + - `SEED_SOURCE`:`file` 或 `url`(默认 `file`) + - `SEED_URL`:当 `SEED_SOURCE=url` 时的远程TXT地址(Gitea Raw直链或API) + - `SEED_AUTH_HEADER`:自定义认证头(如 `Bearer `) + - `SEED_TOKEN`:令牌(将自动以 `Authorization: token ` 发送) +- 生效方式:修改 `.env` 后重启服务 +- 相关代码位置:`server.js:19`(环境变量读取)、`server.js:419`(远程/本地加载逻辑) + ## 常见问题(FAQ) - 修改 `seed.txt` 是否需要重启?不需要,`GET /api/seed` 会重新读取。 @@ -190,6 +204,7 @@ - 页面视觉细节:为 `header` 与 `main` 增加间距(≥30px),背景设置 `background-attachment: fixed` 并覆盖视窗(居中、等比、无重复) - 修复:CSS 依赖路径对 `..` 上级目录的正确解析与落盘(兼容 Font Awesome 的 `../webfonts`);增强 CDN 回退匹配支持 `@scope` 包名(jsDelivr / unpkg) - 新增:开源许可文件 `LICENSE`(GPL-3.0),并在 README 增加许可说明与仓库地址 + - 新增:`seed.txt` 支持远程来源(Gitea 原始文件),通过环境变量配置 ## 开源许可与仓库地址 @@ -200,4 +215,18 @@ - 分发时需保留版权声明与本许可证文本,并开放源代码 - 不提供任何形式的担保,详见 `LICENSE` 的免责声明章节 -Copyright © 2025 Asset Cache Server Developer By [SnowZ](https://ckk.photo8.site/Photo8/Asset-cache) \ No newline at end of file +## Seed 配置(本地与远程) + +- 默认行为:`GET /api/seed` 读取仓库根目录的本地 `seed.txt` +- 远程来源:将环境变量设置为以下值以从 Gitea 原始文件拉取 + - `SEED_SOURCE=url` + - `SEED_URL=<你的Gitea原始TXT地址>`(例如:`https://gitea.example.com/api/v1/repos///raw/seed.txt?ref=main` 或 Raw 文件直链) + - 可选认证: + - `SEED_AUTH_HEADER="Bearer "`(自定义 Authorization 头) + - 或 `SEED_TOKEN=""`(自动使用 `Authorization: token ` 头) +- 回退机制:远程拉取失败时,若本地 `seed.txt` 存在则自动使用本地文件 +- 相关代码: + - 远程/本地加载逻辑:`server.js:419`(`loadSeedTxt`) + - 执行批量抓取:`server.js:440`(`runSeed`) + +Copyright © 2025 Asset Cache Server Developer By [SnowZ](https://ckk.photo8.site/Photo8/Asset-cache) diff --git a/seed.txt b/seed.txt index e82d76d..01704e4 100644 --- a/seed.txt +++ b/seed.txt @@ -1,18 +1,83 @@ # 示例:本文件内置一些常用的CSS/JS地址用于测试 # 注释行以 # 开头;空行将被忽略 -# Bootstrap 5.3.0 样式与脚本 +# Bootstrap https://cdn.jsdmirror.com/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css https://cdn.jsdmirror.com/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js -https://cdn.tailwindcss.com -# Element UI 2.15.13 样式(用于验证字体依赖自动抓取) -https://dist.jicelue.com/css/npm/element-ui@2.15.13/lib/theme-chalk/index.css - -# Font Awesome Free 6(验证 webfonts 自动抓取) +# font-awesome https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.1/css/all.min.css https://s4.zstatic.net/ajax/libs/font-awesome/5.15.4/css/all.min.css https://s4.zstatic.net/ajax/libs/font-awesome/5.15.4/css/v4-shims.min.css https://s4.zstatic.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css + +# layer +https://s4.zstatic.net/ajax/libs/layer/3.1.1/layer.js +https://s4.zstatic.net/ajax/libs/layer/3.1.1/theme/default/layer.css + +# iconify +https://code.iconify.design/3/3.1.1/iconify.min.js + +# JQUERY +https://upcdn.b0.upaiyun.com/libs/jquery/jquery-1.9.1.min.js +https://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js +https://upcdn.b0.upaiyun.com/libs/jqueryui/jquery.ui-1.9.1.min.js +https://cdn.staticfile.net/jquery/3.7.1/jquery.js +https://cdn.staticfile.net/jquery/3.7.1/jquery.min.js +https://cdn.staticfile.net/jquery/3.7.1/jquery.min.map +https://cdn.staticfile.net/jquery/3.7.1/jquery.slim.js +https://cdn.staticfile.net/jquery/3.7.1/jquery.slim.min.js +https://cdn.staticfile.net/jquery/3.7.1/jquery.slim.min.map + +# xgplayer +https://unpkg.byted-static.com/xgplayer/3.0.10/dist/index.min.js +https://unpkg.byted-static.com/xgplayer-hls/3.0.10/dist/index.min.js +https://unpkg.byted-static.com/xgplayer-flv/3.0.10/dist/index.min.js +https://unpkg.byted-static.com/xgplayer-dash/3.0.0-alpha.2/dist/index.min.js +https://unpkg.byted-static.com/xgplayer-mp4/3.0.10/dist/index.min.js +https://unpkg.byted-static.com/xgplayer/3.0.10/dist/index.min.css +https://cdn.jsdelivr.net/npm/xgplayer-hls.js@3.0.20/dist/index.min.js + +# swarmcloud +https://cdn.jsdelivr.net/npm/@swarmcloud/hls/p2p-engine.min.js +https://cdn.jsdelivr.net/npm/@swarmcloud/hls/hls.min.js + +# tailwindcss/browser +https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.1.17/dist/index.global.min.js +https://cdn.tailwindcss.com + +# iconify +https://code.iconify.design/3/3.1.1/iconify.min.js + +# notyf +https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.css +https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.js + +# Vue.js +https://unpkg.com/vue@3.5.7/dist/vue.global.prod.js +https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js + +# Axios +https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js +https://cdn.jsdelivr.net/npm/axios@0.26.0/dist/axios.min.js +https://cdn.jsdmirror.com/npm/axios@0.21.1/dist/axios.min.js + +# Swiper +https://cdnjs.cloudflare.com/ajax/libs/Swiper/7.4.1/swiper-bundle.min.js +https://cdnjs.cloudflare.com/ajax/libs/Swiper/7.4.1/swiper-bundle.min.css + +# npm +https://cdn.jsdelivr.net/npm/element-ui@2.15.7/lib/theme-chalk/index.css +https://cdn.jsdelivr.net/npm/element-ui@2.15.7/lib/index.js +https://cdn.jsdelivr.net/npm/element-ui/lib/index.js +https://cdn.jsdelivr.net/npm/element-ui/lib/theme-chalk/index.css +https://cdn.jsdmirror.com/npm/element-ui@2.15.13/lib/theme-chalk/index.css +https://cdn.jsdmirror.com/npm/element-ui@2.15.13/lib/index.js +https://cdn.jsdmirror.com/npm/element-ui@2.15.6/lib/theme-chalk/index.css +https://cdn.jsdmirror.com/npm/element-ui@2.15.6/lib/index.js + +# Emoji +https://cdn.staticfile.net/emoji-js/3.8.0/emoji.js +https://cdn.staticfile.net/emoji-js/3.8.0/emoji.css \ No newline at end of file diff --git a/server.js b/server.js index ee2814d..4d32308 100644 --- a/server.js +++ b/server.js @@ -16,6 +16,11 @@ const CSS_DIR = path.join(CACHE_ROOT, 'css') const JS_DIR = path.join(CACHE_ROOT, 'js') const SEED_FILE = path.join(__dirname, 'seed.txt') const PUBLIC_DIR = path.join(__dirname, 'public') +// Seed 源设置(默认读取本地 seed.txt,可选从远程URL读取原始TXT) +const SEED_SOURCE = (process.env.SEED_SOURCE || 'file').toLowerCase() +const SEED_URL = process.env.SEED_URL || '' +const SEED_TOKEN = process.env.SEED_TOKEN || '' +const SEED_AUTH_HEADER = process.env.SEED_AUTH_HEADER || '' // 中间件 app.use(express.json({ limit: '2mb' })) @@ -405,14 +410,35 @@ app.get('/health', (req, res) => { // 已移除外部提交接口:/api/upload-txt 与 /api/cache /** - * 从项目内置 seed.txt 加载URL并执行批量缓存 + * 加载 Seed 文本(本地或远程) + * - 当 SEED_SOURCE = 'url' 且配置了 SEED_URL 时,优先从远程拉取 + * - 支持可选认证头(SEED_AUTH_HEADER),或 SEED_TOKEN 以 Authorization: token 形式 + * - 远程拉取失败时回退到本地文件(若存在) + * @returns {Promise} 返回原始TXT文本(可能为空字符串) + */ +async function loadSeedTxt() { + if (SEED_SOURCE === 'url' && SEED_URL) { + try { + const headers = { 'User-Agent': 'AssetCache/1.0', 'Accept': 'text/plain, */*' } + if (SEED_AUTH_HEADER) headers['Authorization'] = SEED_AUTH_HEADER + else if (SEED_TOKEN) headers['Authorization'] = `token ${SEED_TOKEN}` + const resp = await axios.get(SEED_URL, { responseType: 'text', timeout: 20000, headers }) + const txt = typeof resp.data === 'string' ? resp.data : (resp.data?.toString?.() || '') + if (txt && txt.trim().length > 0) return txt + } catch (e) { + // 回退到本地 + } + } + if (fs.existsSync(SEED_FILE)) return fs.readFileSync(SEED_FILE, 'utf8') + return '' +} + +/** + * 加载Seed(本地/远程)并执行批量缓存 * @returns {Promise<{count:number, results:any[]}>} */ -async function runSeedFromFile() { - if (!fs.existsSync(SEED_FILE)) { - return { count: 0, results: [] } - } - const txt = fs.readFileSync(SEED_FILE, 'utf8') +async function runSeed() { + const txt = await loadSeedTxt() const urls = parseTxtToUrls(txt) if (urls.length === 0) return { count: 0, results: [] } const results = await batchFetch(urls) @@ -422,7 +448,7 @@ async function runSeedFromFile() { // 触发内置seed.txt抓取 app.get('/api/seed', async (req, res) => { try { - const r = await runSeedFromFile() + const r = await runSeed() const mapped = (r.results || []).map(x => ({ url: x.url, saved: x.saved ? getPublicPath(x.url, x.type, x.saved) : '',