83 lines
3.6 KiB
JavaScript
83 lines
3.6 KiB
JavaScript
/**
|
||
* Verdaccio 缓存可视化管理面板 — 后端服务入口
|
||
* Express + better-sqlite3,§4.2 REST API
|
||
*
|
||
* 环境变量:
|
||
* VERDACCIO_STORAGE Verdaccio 缓存目录(优先于 UI 设置)
|
||
* PORT 监听端口(默认 3000)
|
||
* DATA_DIR SQLite 数据目录(默认 backend/data)
|
||
* VCM_USERNAME 面板登录账号(默认 admin)
|
||
* VCM_PASSWORD 面板登录密码(默认 666666,Docker 部署务必通过 compose 覆盖)
|
||
* UV_THREADPOOL_SIZE libuv IO 线程池大小(默认 4 → 8:扫描/stat/readFile 并发更高,NAS 四核安全)
|
||
*
|
||
* 模块划分:
|
||
* - scan-manager.js 扫描状态机与业务编排(缓存/进度/状态构建)
|
||
* - schedules.js 每日扫描 + 每周抓榜定时任务
|
||
* - routes/* REST 路由(按领域拆分)
|
||
*/
|
||
// 必须在 libuv 线程池首次使用前设置:扫描大量目录/文件时默认 4 线程是主要瓶颈
|
||
process.env.UV_THREADPOOL_SIZE = process.env.UV_THREADPOOL_SIZE || '8'
|
||
import express from 'express'
|
||
import path from 'node:path'
|
||
import fs from 'node:fs'
|
||
import { fileURLToPath } from 'node:url'
|
||
import { resolveStorageDir } from './scanner.js'
|
||
import { isDemoMode } from './scan-manager.js'
|
||
import { scheduleDailyScan, scheduleWeeklyTopList } from './schedules.js'
|
||
import { authRouter, authMiddleware } from './routes/auth.js'
|
||
import { thresholdsRouter } from './routes/thresholds.js'
|
||
import { ignoredRouter } from './routes/ignored.js'
|
||
import { packagesRouter } from './routes/packages.js'
|
||
import { syncRouter } from './routes/sync.js'
|
||
import { toplistRouter } from './routes/toplist.js'
|
||
import { cleanRouter } from './routes/clean.js'
|
||
import { logsRouter } from './routes/logs.js'
|
||
import { apiNotFound, errorHandler } from './http.js'
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||
const app = express()
|
||
app.use(express.json({ limit: '1mb' }))
|
||
|
||
const PORT = Number(process.env.PORT || 3000)
|
||
|
||
// —— 路由装配:登录/登出公开,其余 /api/* 需鉴权 ——
|
||
app.use('/api', authRouter)
|
||
app.use('/api', authMiddleware)
|
||
app.use('/api', thresholdsRouter)
|
||
app.use('/api', ignoredRouter)
|
||
app.use('/api', packagesRouter)
|
||
app.use('/api', syncRouter)
|
||
app.use('/api', toplistRouter)
|
||
app.use('/api', cleanRouter)
|
||
app.use('/api', logsRouter)
|
||
// —— /api 未匹配任何路由 → JSON 404(Express 默认对非 GET 返回 HTML) ——
|
||
app.use('/api', apiNotFound)
|
||
|
||
// —— 生产:托管前端 dist ——
|
||
const distDir = path.join(__dirname, '..', 'frontend', 'dist')
|
||
if (fs.existsSync(distDir)) {
|
||
app.use(express.static(distDir))
|
||
app.get('*', (req, res) => {
|
||
if (req.path.startsWith('/api/')) return res.status(404).json({ ok: false, message: 'not found' })
|
||
res.sendFile(path.join(distDir, 'index.html'))
|
||
})
|
||
}
|
||
|
||
// —— 统一错误处理:Express4 同步 throw 会跳到这里,返回 JSON 而非 HTML 500 ——
|
||
app.use(errorHandler)
|
||
|
||
app.listen(PORT, () => {
|
||
const storage = resolveStorageDir()
|
||
const mode = isDemoMode() ? 'demo' : 'storage'
|
||
if (!process.env.VCM_PASSWORD) {
|
||
console.warn(
|
||
'[auth] ⚠ 未设置 VCM_PASSWORD 环境变量,正在使用默认口令 666666。' +
|
||
'生产部署请务必通过 docker-compose 的环境变量覆盖,否则面板可被任意登录!',
|
||
)
|
||
}
|
||
console.log(`[verdaccio-manager] listening on :${PORT} (mode=${mode})`)
|
||
if (mode === 'storage') console.log(`[verdaccio-manager] storage: ${storage}`)
|
||
else console.log('[verdaccio-manager] 未配置 VERDACCIO_STORAGE,运行于演示模式(设置 VCM_DEMO=0 + 真实目录可关闭)')
|
||
scheduleDailyScan()
|
||
scheduleWeeklyTopList()
|
||
}) |