feat: 新增多项功能并添加新工具
- 私人剪贴板新增限时直通URL生成功能 - doc_cloud_keeper新增标题复制按钮 - 新增贪蛇出动游戏工具 - 新增OSS中转直链工具 - 添加批量抽帧测试脚本
This commit is contained in:
+359
-1
@@ -473,6 +473,7 @@ const ensurePscLoaded = () => {
|
||||
ensurePscLoaded()
|
||||
|
||||
let privateClipboardRouter = null
|
||||
let privateClipboardShareRouter = null
|
||||
const ensurePrivateClipboardLoaded = () => {
|
||||
let mod = null
|
||||
try { mod = require('./private_clipboard') } catch (e) { try { logJSON('private_clipboard.require.error', { error: String(e.message || e) }, 'private_clipboard') } catch {}; setTimeout(ensurePrivateClipboardLoaded, 2000); return }
|
||||
@@ -484,6 +485,10 @@ const ensurePrivateClipboardLoaded = () => {
|
||||
mod.bindRoutes(app)
|
||||
try { logJSON('private_clipboard.bind_routes.done', { ok: true }, 'private_clipboard') } catch {}
|
||||
}
|
||||
if (mod && mod.createPublicShareRouter) {
|
||||
privateClipboardShareRouter = mod.createPublicShareRouter()
|
||||
try { logJSON('private_clipboard.create_share_router.done', { ok: true }, 'private_clipboard') } catch {}
|
||||
}
|
||||
} catch (e) {
|
||||
try { logJSON('private_clipboard.create_router.error', { error: String(e.message || e) }, 'private_clipboard') } catch {}
|
||||
}
|
||||
@@ -1636,7 +1641,9 @@ const mapSystemIdToUrl = (systemId) => {
|
||||
'Tools-private_clipboard': '/tools/private_clipboard',
|
||||
'Tools-style_check': '/tools/style_check/index.html',
|
||||
'Tools-short_link': '/tools/short_link',
|
||||
'Tools-web_mobile_calendar_reminder': '/tools/web_mobile_calendar_reminder'
|
||||
'Tools-web_mobile_calendar_reminder': '/tools/web_mobile_calendar_reminder',
|
||||
'Tools-greedy_snake': '/tools/greedy_snake',
|
||||
'Tools-oss_browser_link': '/tools/oss_browser_link'
|
||||
}
|
||||
if (m[systemId]) return m[systemId]
|
||||
if (systemId.startsWith('Tools-')) {
|
||||
@@ -2585,6 +2592,12 @@ app.use('/uploads/private_clipboard', (req, res, next) => {
|
||||
if (hasPrivateClipboardAuth(req)) return next()
|
||||
return res.status(401).send('未授权')
|
||||
})
|
||||
// 私人剪贴板「限时直通URL」公开访问入口:无任何鉴权,供第三方浏览器/程序直连;
|
||||
// 安全性由 token 随机性(32位hex)与 3 天有效期控制,过期返回失效提示页。
|
||||
app.use('/share', (req, res, next) => {
|
||||
if (privateClipboardShareRouter) return privateClipboardShareRouter(req, res, next)
|
||||
return res.status(503).json({ ok: false, error: 'service_not_ready' })
|
||||
})
|
||||
app.use('/api/private_clipboard', (req, res, next) => {
|
||||
// 白名单:登录和登出接口不需要鉴权
|
||||
if (req.path === '/auth/login' || req.path === '/auth/logout') return next()
|
||||
@@ -6585,6 +6598,351 @@ app.post('/api/zen_box/config', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// 贪蛇出动 greedy_snake:鉴权基础函数 + 路由守卫 + 轻量战绩存储
|
||||
// 必须注册在下方全局 express.static 之前,否则静态资源会绕过鉴权中间件
|
||||
// ============================================================
|
||||
const GREEDY_SNAKE_AUTH_COOKIE = 'greedy_snake_gate'
|
||||
const readGreedySnakeAuthConfig = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'public', 'tools', 'greedy_snake', 'auth_config.json')
|
||||
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'))
|
||||
} catch {}
|
||||
return {}
|
||||
}
|
||||
const readGreedySnakeJwk = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'config', 'greedy_snake.jwk.json')
|
||||
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'))
|
||||
} catch {}
|
||||
return { kty: 'RSA', n: '', e: '' }
|
||||
}
|
||||
const getGreedySnakeCookieName = () => {
|
||||
try { const n = String(readGreedySnakeAuthConfig().cookieName || ''); if (n) return n } catch {}
|
||||
return GREEDY_SNAKE_AUTH_COOKIE
|
||||
}
|
||||
const getGreedySnakeMaxAge = () => {
|
||||
try { const days = Number(readGreedySnakeAuthConfig().max_age_days || 0); if (Number.isFinite(days) && days > 0) return Math.min(days, 365) * 24 * 3600 * 1000 } catch {}
|
||||
return 30 * 24 * 3600 * 1000
|
||||
}
|
||||
const getGreedySnakeGuidConfig = () => {
|
||||
try {
|
||||
const cfg = readGreedySnakeAuthConfig()
|
||||
const keyRaw = String(cfg.guidParam || cfg.guid_param || 'guid').trim()
|
||||
const valueRaw = String(cfg.guidValue || cfg.guid_value || '').trim()
|
||||
return { key: keyRaw || 'guid', value: valueRaw }
|
||||
} catch { return { key: 'guid', value: '' } }
|
||||
}
|
||||
const hasValidGreedySnakeGuid = req => {
|
||||
try {
|
||||
const cfg = readGreedySnakeAuthConfig()
|
||||
if (cfg.enable_auth === false) return false
|
||||
const { key, value } = getGreedySnakeGuidConfig()
|
||||
if (!value) return false
|
||||
const q = req && req.query ? req.query : {}
|
||||
const raw = q[key]
|
||||
if (raw === undefined || raw === null) return false
|
||||
const incoming = Array.isArray(raw) ? String(raw[0] || '') : String(raw)
|
||||
return incoming === value
|
||||
} catch { return false }
|
||||
}
|
||||
const isGreedySnakeForceGuid = () => {
|
||||
try {
|
||||
const cfg = readGreedySnakeAuthConfig()
|
||||
if (cfg.enable_auth === false) return false
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, 'requireGuid')) return !!cfg.requireGuid
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, 'require_guid')) return !!cfg.require_guid
|
||||
return false
|
||||
} catch { return false }
|
||||
}
|
||||
const hasGreedySnakeAuth = req => {
|
||||
try {
|
||||
const config = readGreedySnakeAuthConfig()
|
||||
if (config.enable_auth === false) return true
|
||||
const cookies = parseCookie(req.headers.cookie || '')
|
||||
return cookies[getGreedySnakeCookieName()] === '1'
|
||||
} catch { return false }
|
||||
}
|
||||
app.get('/tools/greedy_snake', (req, res, next) => {
|
||||
setNoCache(res)
|
||||
try {
|
||||
const forceGuid = isGreedySnakeForceGuid()
|
||||
if (hasGreedySnakeAuth(req)) return next()
|
||||
const guidKey = getGreedySnakeGuidConfig().key
|
||||
const hasGuidInput = req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)
|
||||
if (hasValidGreedySnakeGuid(req)) {
|
||||
const maxAge = getGreedySnakeMaxAge()
|
||||
const cookieName = getGreedySnakeCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/greedy_snake/index.html')
|
||||
}
|
||||
if (hasGuidInput) return res.status(401).send('未授权')
|
||||
const token = String(req.query.token || '')
|
||||
if (!token) {
|
||||
if (forceGuid) return res.status(401).send('未授权')
|
||||
return next()
|
||||
}
|
||||
const payload = verifyJwtWithKeys(token, jwkKeys(readGreedySnakeJwk()))
|
||||
if (!payload) return res.status(401).send('未授权')
|
||||
{
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-greedy_snake')) return res.status(401).send('未授权')
|
||||
}
|
||||
const maxAge = getGreedySnakeMaxAge()
|
||||
const cookieName = getGreedySnakeCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/greedy_snake/index.html')
|
||||
} catch (e) { return res.status(401).send('未授权') }
|
||||
})
|
||||
app.use('/tools/greedy_snake', (req, res, next) => {
|
||||
const forceGuid = isGreedySnakeForceGuid()
|
||||
if (hasGreedySnakeAuth(req)) return next()
|
||||
const guidKey = getGreedySnakeGuidConfig().key
|
||||
const isHandshake = req.method === 'GET' && (req.path === '/' || req.path === '' || req.path === '/index.html')
|
||||
if (isHandshake && hasValidGreedySnakeGuid(req)) {
|
||||
const maxAge = getGreedySnakeMaxAge()
|
||||
const cookieName = getGreedySnakeCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/greedy_snake/index.html')
|
||||
}
|
||||
if (isHandshake && req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)) return res.status(401).send('未授权')
|
||||
if (isHandshake && String(req.query.token || '')) {
|
||||
const payload = verifyJwtWithKeys(String(req.query.token), jwkKeys(readGreedySnakeJwk()))
|
||||
if (payload) {
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-greedy_snake')) return res.status(401).send('未授权')
|
||||
const maxAge = getGreedySnakeMaxAge()
|
||||
const cookieName = getGreedySnakeCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/greedy_snake/index.html')
|
||||
}
|
||||
}
|
||||
if (isHandshake && forceGuid) return res.status(401).send('未授权')
|
||||
return res.status(401).send('未授权')
|
||||
})
|
||||
// 贪蛇出动:战绩/配置 API(默认受 gate 保护)
|
||||
const GREEDY_SNAKE_DATA_FILE = path.join(process.cwd(), 'data', 'greedy_snake.json')
|
||||
const readGreedySnakeData = () => {
|
||||
try {
|
||||
if (fs.existsSync(GREEDY_SNAKE_DATA_FILE)) return JSON.parse(fs.readFileSync(GREEDY_SNAKE_DATA_FILE, 'utf-8'))
|
||||
} catch {}
|
||||
return { gems: 0, totalCoins: 0, best: 0, todayBest: null, _init: false }
|
||||
}
|
||||
const writeGreedySnakeData = data => {
|
||||
try {
|
||||
const dir = path.dirname(GREEDY_SNAKE_DATA_FILE)
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(GREEDY_SNAKE_DATA_FILE, JSON.stringify(data, null, 2), 'utf-8')
|
||||
return true
|
||||
} catch { return false }
|
||||
}
|
||||
const greedySnakeTodayBJ = () => {
|
||||
try { return new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' }) } catch { return new Date().toISOString().slice(0, 10) }
|
||||
}
|
||||
app.use('/api/greedy_snake', (req, res, next) => {
|
||||
if (hasGreedySnakeAuth(req)) return next()
|
||||
return res.status(401).json({ ok: false, error: 'unauthorized' })
|
||||
})
|
||||
app.get('/api/greedy_snake/config', (req, res) => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'config', 'greedy_snake.json')
|
||||
const cfg = fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf-8')) : {}
|
||||
return res.json({ ok: true, data: cfg })
|
||||
} catch (e) { return res.status(500).json({ ok: false, error: String(e.message || e) }) }
|
||||
})
|
||||
app.get('/api/greedy_snake/stats', (req, res) => {
|
||||
try {
|
||||
const d = readGreedySnakeData()
|
||||
if (!d._init) {
|
||||
// 首次访问:写入初始钻石
|
||||
d.gems = d.gems || 100
|
||||
d._init = true
|
||||
writeGreedySnakeData(d)
|
||||
}
|
||||
const today = greedySnakeTodayBJ()
|
||||
const todayBest = d.todayBest && d.todayBest.date === today ? d.todayBest : null
|
||||
return res.json({ ok: true, data: { gems: d.gems || 0, totalCoins: d.totalCoins || 0, best: d.best || 0, todayBest } })
|
||||
} catch (e) { return res.status(500).json({ ok: false, error: String(e.message || e) }) }
|
||||
})
|
||||
app.post('/api/greedy_snake/stats', (req, res) => {
|
||||
try {
|
||||
const b = req.body || {}
|
||||
const d = readGreedySnakeData()
|
||||
if (typeof b.gems === 'number' && Number.isFinite(b.gems)) d.gems = Math.max(0, Math.floor(b.gems))
|
||||
if (typeof b.totalCoins === 'number' && Number.isFinite(b.totalCoins)) d.totalCoins = Math.max(d.totalCoins || 0, Math.floor(b.totalCoins))
|
||||
const score = Number(b.score)
|
||||
if (Number.isFinite(score) && score > 0) {
|
||||
d.best = Math.max(d.best || 0, Math.floor(score))
|
||||
const today = greedySnakeTodayBJ()
|
||||
if (!d.todayBest || d.todayBest.date !== today || (d.todayBest.score || 0) < score) {
|
||||
d.todayBest = { date: today, score: Math.floor(score), name: String(b.name || '').slice(0, 20) || '贪蛇小将' }
|
||||
}
|
||||
}
|
||||
writeGreedySnakeData(d)
|
||||
const today = greedySnakeTodayBJ()
|
||||
return res.json({ ok: true, data: { gems: d.gems || 0, totalCoins: d.totalCoins || 0, best: d.best || 0, todayBest: d.todayBest && d.todayBest.date === today ? d.todayBest : null } })
|
||||
} catch (e) { return res.status(500).json({ ok: false, error: String(e.message || e) }) }
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// OSS中转直链 oss_browser_link:鉴权基础函数 + 路由守卫 + 受保护下载
|
||||
// 必须注册在下方全局 express.static 之前,否则静态资源会绕过鉴权中间件
|
||||
// ============================================================
|
||||
const OSS_BROWSER_LINK_AUTH_COOKIE = 'oss_browser_link_gate'
|
||||
const readOssBrowserLinkAuthConfig = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'public', 'tools', 'oss_browser_link', 'auth_config.json')
|
||||
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'))
|
||||
} catch {}
|
||||
return {}
|
||||
}
|
||||
const readOssBrowserLinkJwk = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'config', 'oss_browser_link.jwk.json')
|
||||
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'))
|
||||
} catch {}
|
||||
return { kty: 'RSA', n: '', e: '' }
|
||||
}
|
||||
const getOssBrowserLinkCookieName = () => {
|
||||
try { const n = String(readOssBrowserLinkAuthConfig().cookieName || ''); if (n) return n } catch {}
|
||||
return OSS_BROWSER_LINK_AUTH_COOKIE
|
||||
}
|
||||
const getOssBrowserLinkMaxAge = () => {
|
||||
try { const days = Number(readOssBrowserLinkAuthConfig().max_age_days || 0); if (Number.isFinite(days) && days > 0) return Math.min(days, 365) * 24 * 3600 * 1000 } catch {}
|
||||
return 30 * 24 * 3600 * 1000
|
||||
}
|
||||
const getOssBrowserLinkGuidConfig = () => {
|
||||
try {
|
||||
const cfg = readOssBrowserLinkAuthConfig()
|
||||
const keyRaw = String(cfg.guidParam || cfg.guid_param || 'guid').trim()
|
||||
const valueRaw = String(cfg.guidValue || cfg.guid_value || '').trim()
|
||||
return { key: keyRaw || 'guid', value: valueRaw }
|
||||
} catch { return { key: 'guid', value: '' } }
|
||||
}
|
||||
const hasValidOssBrowserLinkGuid = req => {
|
||||
try {
|
||||
const cfg = readOssBrowserLinkAuthConfig()
|
||||
if (cfg.enable_auth === false) return false
|
||||
const { key, value } = getOssBrowserLinkGuidConfig()
|
||||
if (!value) return false
|
||||
const q = req && req.query ? req.query : {}
|
||||
const raw = q[key]
|
||||
if (raw === undefined || raw === null) return false
|
||||
const incoming = Array.isArray(raw) ? String(raw[0] || '') : String(raw)
|
||||
return incoming === value
|
||||
} catch { return false }
|
||||
}
|
||||
const isOssBrowserLinkForceGuid = () => {
|
||||
try {
|
||||
const cfg = readOssBrowserLinkAuthConfig()
|
||||
if (cfg.enable_auth === false) return false
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, 'requireGuid')) return !!cfg.requireGuid
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, 'require_guid')) return !!cfg.require_guid
|
||||
return false
|
||||
} catch { return false }
|
||||
}
|
||||
const hasOssBrowserLinkAuth = req => {
|
||||
try {
|
||||
const config = readOssBrowserLinkAuthConfig()
|
||||
if (config.enable_auth === false) return true
|
||||
const cookies = parseCookie(req.headers.cookie || '')
|
||||
return cookies[getOssBrowserLinkCookieName()] === '1'
|
||||
} catch { return false }
|
||||
}
|
||||
app.get('/tools/oss_browser_link', (req, res, next) => {
|
||||
setNoCache(res)
|
||||
try {
|
||||
const forceGuid = isOssBrowserLinkForceGuid()
|
||||
if (hasOssBrowserLinkAuth(req)) return next()
|
||||
const guidKey = getOssBrowserLinkGuidConfig().key
|
||||
const hasGuidInput = req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)
|
||||
if (hasValidOssBrowserLinkGuid(req)) {
|
||||
const maxAge = getOssBrowserLinkMaxAge()
|
||||
const cookieName = getOssBrowserLinkCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/oss_browser_link/index.html')
|
||||
}
|
||||
if (hasGuidInput) return res.status(401).send('未授权')
|
||||
const token = String(req.query.token || '')
|
||||
if (!token) {
|
||||
if (forceGuid) return res.status(401).send('未授权')
|
||||
return next()
|
||||
}
|
||||
const payload = verifyJwtWithKeys(token, jwkKeys(readOssBrowserLinkJwk()))
|
||||
if (!payload) return res.status(401).send('未授权')
|
||||
{
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-oss_browser_link')) return res.status(401).send('未授权')
|
||||
}
|
||||
const maxAge = getOssBrowserLinkMaxAge()
|
||||
const cookieName = getOssBrowserLinkCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/oss_browser_link/index.html')
|
||||
} catch (e) { return res.status(401).send('未授权') }
|
||||
})
|
||||
app.use('/tools/oss_browser_link', (req, res, next) => {
|
||||
const forceGuid = isOssBrowserLinkForceGuid()
|
||||
if (hasOssBrowserLinkAuth(req)) return next()
|
||||
const guidKey = getOssBrowserLinkGuidConfig().key
|
||||
const isHandshake = req.method === 'GET' && (req.path === '/' || req.path === '' || req.path === '/index.html')
|
||||
if (isHandshake && hasValidOssBrowserLinkGuid(req)) {
|
||||
const maxAge = getOssBrowserLinkMaxAge()
|
||||
const cookieName = getOssBrowserLinkCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/oss_browser_link/index.html')
|
||||
}
|
||||
if (isHandshake && req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)) return res.status(401).send('未授权')
|
||||
if (isHandshake && String(req.query.token || '')) {
|
||||
const payload = verifyJwtWithKeys(String(req.query.token), jwkKeys(readOssBrowserLinkJwk()))
|
||||
if (payload) {
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-oss_browser_link')) return res.status(401).send('未授权')
|
||||
const maxAge = getOssBrowserLinkMaxAge()
|
||||
const cookieName = getOssBrowserLinkCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/oss_browser_link/index.html')
|
||||
}
|
||||
}
|
||||
if (isHandshake && forceGuid) return res.status(401).send('未授权')
|
||||
return res.status(401).send('未授权')
|
||||
})
|
||||
// OSS中转直链:受保护的 zip 下载(必须在全局 express.static 之前注册)
|
||||
const OSS_BROWSER_LINK_ZIP_NAME = 'oss-browser-win32-x64.zip'
|
||||
app.get(`/download_files/${OSS_BROWSER_LINK_ZIP_NAME}`, (req, res) => {
|
||||
if (!hasOssBrowserLinkAuth(req)) return res.status(401).json({ ok: false, error: 'unauthorized' })
|
||||
const p = path.join(process.cwd(), 'public', 'download_files', OSS_BROWSER_LINK_ZIP_NAME)
|
||||
if (!fs.existsSync(p)) return res.status(404).json({ ok: false, error: '文件尚未中转,暂不可下载' })
|
||||
res.download(p, OSS_BROWSER_LINK_ZIP_NAME)
|
||||
})
|
||||
// OSS中转直链:API(默认受 gate 保护,auth/hint 白名单放行)
|
||||
app.get('/api/oss_browser_link/auth/hint', (req, res) => {
|
||||
res.json({ ok: true, message: '请通过首页九宫格「OSS中转直链」进入,或使用正确的访问链接' })
|
||||
})
|
||||
app.use('/api/oss_browser_link', (req, res, next) => {
|
||||
if (hasOssBrowserLinkAuth(req)) return next()
|
||||
return res.status(401).json({ ok: false, error: 'unauthorized' })
|
||||
})
|
||||
app.get('/api/oss_browser_link/config', (req, res) => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'config', 'oss_browser_link.json')
|
||||
const cfg = fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf-8')) : {}
|
||||
return res.json({ ok: true, data: { ui_mode: cfg.ui_mode || 'debug', public_url: cfg.public_url || '', launch_protocol: cfg.launch_protocol || 'ossbrowser' } })
|
||||
} catch (e) { return res.status(500).json({ ok: false, error: String(e.message || e) }) }
|
||||
})
|
||||
app.get('/api/oss_browser_link/info', (req, res) => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'public', 'download_files', OSS_BROWSER_LINK_ZIP_NAME)
|
||||
if (!fs.existsSync(p)) return res.json({ ok: true, data: { available: false, name: OSS_BROWSER_LINK_ZIP_NAME, size: 0 } })
|
||||
const st = fs.statSync(p)
|
||||
const mtime = new Date(st.mtime).toLocaleString('sv-SE', { timeZone: 'Asia/Shanghai' })
|
||||
return res.json({ ok: true, data: { available: true, name: OSS_BROWSER_LINK_ZIP_NAME, size: st.size, updated_at: mtime } })
|
||||
} catch (e) { return res.status(500).json({ ok: false, error: String(e.message || e) }) }
|
||||
})
|
||||
|
||||
// Static: keep weekly tool assets always fresh
|
||||
app.use(express.static(path.join(process.cwd(), 'public'), {
|
||||
setHeaders: (res, filePath) => {
|
||||
|
||||
Reference in New Issue
Block a user