refactor(api): 移除外部提交接口仅保留种子文件方式
移除 POST /api/upload-txt 和 POST /api/cache 接口,改为仅通过 seed.txt 文件管理URL抓取 更新README文档以反映接口变更,增强安全性
This commit is contained in:
24
README.md
24
README.md
@@ -32,26 +32,9 @@
|
|||||||
|
|
||||||
## 接口说明
|
## 接口说明
|
||||||
|
|
||||||
- `POST /api/upload-txt`
|
|
||||||
- 上传 TXT(字段名 `file`),每行一个 URL;支持 `#` 注释与空行
|
|
||||||
- 响应项示例:
|
|
||||||
- `{
|
|
||||||
"url": "https://cdn.jsdmirror.com/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css",
|
|
||||||
"saved": "/css/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css",
|
|
||||||
"accessUrl": "https://<你的域名>/css/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css",
|
|
||||||
"filename": "bootstrap.min.css",
|
|
||||||
"type": "css",
|
|
||||||
"size": 232914,
|
|
||||||
"skipped": false
|
|
||||||
}`
|
|
||||||
|
|
||||||
- `POST /api/cache`
|
|
||||||
- 提交单条 `url` 或数组 `urls`
|
|
||||||
- 单条示例:`{"url":"https://.../bootstrap.min.css"}`
|
|
||||||
- 多条示例:`{"urls":["https://.../bootstrap.min.css","https://.../bootstrap.bundle.min.js"]}`
|
|
||||||
|
|
||||||
- `GET /api/seed`
|
- `GET /api/seed`
|
||||||
- 从项目内置 `seed.txt` 读取并批量抓取,无需重启
|
- 从项目内置 `seed.txt` 读取并批量抓取,无需重启
|
||||||
|
- 管理抓取仅通过修改服务器上的 `seed.txt` 实现,服务端不接受外部提交 URL
|
||||||
|
|
||||||
## 静态访问
|
## 静态访问
|
||||||
|
|
||||||
@@ -75,7 +58,7 @@
|
|||||||
## 安全与白名单建议
|
## 安全与白名单建议
|
||||||
|
|
||||||
- 推荐在 CDN/WAF 层配置防盗链白名单(如 `*.aaa.com`、`www.bbb.com`)
|
- 推荐在 CDN/WAF 层配置防盗链白名单(如 `*.aaa.com`、`www.bbb.com`)
|
||||||
- 如需更强控制可扩展签名 URL 校验(服务端或边缘验证令牌)
|
- 管理接口仅保留 `GET /api/seed`,不提供外部 POST;如需更强控制可扩展签名 URL 校验(服务端或边缘验证令牌)
|
||||||
|
|
||||||
## 部署建议
|
## 部署建议
|
||||||
|
|
||||||
@@ -98,4 +81,5 @@
|
|||||||
- 去重逻辑(已存在则跳过,返回 `skipped: true`)
|
- 去重逻辑(已存在则跳过,返回 `skipped: true`)
|
||||||
- `.env` 支持(`PORT`),适配多端口部署
|
- `.env` 支持(`PORT`),适配多端口部署
|
||||||
- 接口响应统一返回公共路径 `saved` 与完整 URL `accessUrl`
|
- 接口响应统一返回公共路径 `saved` 与完整 URL `accessUrl`
|
||||||
- 依赖升级:`express@^5.1.0`、`multer@^2.0.2`、`axios@^1.13.2`、`morgan@^1.10.1`
|
- 依赖升级:`express@^5.1.0`、`multer@^2.0.2`、`axios@^1.13.2`、`morgan@^1.10.1`
|
||||||
|
- 移除外部提交接口:`POST /api/upload-txt` 与 `POST /api/cache`,仅支持通过 `seed.txt` 批量抓取
|
||||||
64
server.js
64
server.js
@@ -2,7 +2,6 @@ import 'dotenv/config'
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import morgan from 'morgan'
|
import morgan from 'morgan'
|
||||||
import multer from 'multer'
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -11,7 +10,6 @@ const __filename = fileURLToPath(import.meta.url)
|
|||||||
const __dirname = path.dirname(__filename)
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 5 * 1024 * 1024 } })
|
|
||||||
const PORT = Number(process.env.PORT || 3000)
|
const PORT = Number(process.env.PORT || 3000)
|
||||||
const CACHE_ROOT = path.join(__dirname, 'cache')
|
const CACHE_ROOT = path.join(__dirname, 'cache')
|
||||||
const CSS_DIR = path.join(CACHE_ROOT, 'css')
|
const CSS_DIR = path.join(CACHE_ROOT, 'css')
|
||||||
@@ -197,67 +195,7 @@ app.get('/health', (req, res) => {
|
|||||||
res.json({ ok: true })
|
res.json({ ok: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
// 上传TXT:multipart/form-data,字段名为 file
|
// 已移除外部提交接口:/api/upload-txt 与 /api/cache
|
||||||
app.post('/api/upload-txt', upload.single('file'), async (req, res) => {
|
|
||||||
try {
|
|
||||||
if (!req.file) return res.status(400).json({ error: '缺少TXT文件字段: file' })
|
|
||||||
const txt = req.file.buffer.toString('utf8')
|
|
||||||
const urls = parseTxtToUrls(txt)
|
|
||||||
if (urls.length === 0) return res.status(400).json({ error: 'TXT未包含有效URL' })
|
|
||||||
const results = await batchFetch(urls)
|
|
||||||
const mapped = results.map(r => ({
|
|
||||||
url: r.url,
|
|
||||||
saved: r.saved ? getPublicPath(r.url, r.type, r.saved) : '',
|
|
||||||
accessUrl: r.saved ? buildAccessUrl(req, r.url, r.type, r.saved) : '',
|
|
||||||
size: r.size,
|
|
||||||
type: r.type,
|
|
||||||
skipped: r.skipped,
|
|
||||||
error: r.error,
|
|
||||||
filename: r.saved
|
|
||||||
}))
|
|
||||||
res.json({ count: mapped.length, results: mapped })
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).json({ error: e.message })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 直接提交URL或URL列表
|
|
||||||
app.post('/api/cache', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { url, urls } = req.body || {}
|
|
||||||
if (url) {
|
|
||||||
const r = await fetchAndStore(url)
|
|
||||||
const publicPath = r.saved ? getPublicPath(r.url, r.type, r.saved) : ''
|
|
||||||
const accessUrl = r.saved ? buildAccessUrl(req, r.url, r.type, r.saved) : ''
|
|
||||||
return res.json({
|
|
||||||
url: r.url,
|
|
||||||
saved: publicPath,
|
|
||||||
accessUrl,
|
|
||||||
size: r.size,
|
|
||||||
type: r.type,
|
|
||||||
skipped: r.skipped,
|
|
||||||
filename: r.saved
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (Array.isArray(urls) && urls.length > 0) {
|
|
||||||
const results = await batchFetch(urls)
|
|
||||||
const mapped = results.map(r => ({
|
|
||||||
url: r.url,
|
|
||||||
saved: r.saved ? getPublicPath(r.url, r.type, r.saved) : '',
|
|
||||||
accessUrl: r.saved ? buildAccessUrl(req, r.url, r.type, r.saved) : '',
|
|
||||||
size: r.size,
|
|
||||||
type: r.type,
|
|
||||||
skipped: r.skipped,
|
|
||||||
error: r.error,
|
|
||||||
filename: r.saved
|
|
||||||
}))
|
|
||||||
return res.json({ count: mapped.length, results: mapped })
|
|
||||||
}
|
|
||||||
res.status(400).json({ error: '请提供 url 或 urls' })
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).json({ error: e.message })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从项目内置 seed.txt 加载URL并执行批量缓存
|
* 从项目内置 seed.txt 加载URL并执行批量缓存
|
||||||
|
|||||||
Reference in New Issue
Block a user