- 顶部英雄区下载按钮与下方下载区块重复, 删除该按钮; 保留下载区块(按钮+二维码) - Web 端访问的页面即部署版本, 不应有版本不一致提示: 移除 Landing 的 WebUpdateBanner - index.html 响应加 Cache-Control: no-cache, no-store 兜底: 避免浏览器/代理缓存旧 index 导致刷新后仍加载旧哈希资源, 与部署 dist 不一致 - 新增 GET /healthz 作为容器活性检查路径, Dockerfile HEALTHCHECK 从 /api/health 切换
48 lines
1.8 KiB
JavaScript
48 lines
1.8 KiB
JavaScript
import 'dotenv/config'
|
|
import path from 'node:path'
|
|
import fs from 'node:fs'
|
|
import express from 'express'
|
|
import cors from 'cors'
|
|
import otaRouter from './routes/ota.js'
|
|
|
|
// 默认 3002: 与 iboard (3001) 同机部署时不冲突
|
|
const PORT = Number(process.env.PORT) || 3002
|
|
const DIST_DIR = path.join(process.cwd(), 'dist')
|
|
const HAS_DIST = fs.existsSync(DIST_DIR)
|
|
|
|
const app = express()
|
|
app.use(cors())
|
|
app.use(express.json())
|
|
|
|
// 健康检查 (容器活性探针统一用 /healthz, 保留 /api/health 兼容旧引用)
|
|
app.get('/healthz', (_req, res) => res.json({ ok: true, ts: Date.now() }))
|
|
app.get('/api/health', (_req, res) => res.json({ ok: true, ts: Date.now() }))
|
|
|
|
// OTA 接口公开 (检查/下载更新不需要登录), 必须挂载在最前面
|
|
app.use('/api', otaRouter)
|
|
|
|
// 静态资源 (Vite build 产物)
|
|
if (HAS_DIST) {
|
|
app.use(express.static(DIST_DIR, { index: false, maxAge: '1h', etag: true }))
|
|
|
|
// SPA fallback: 非 /api/* 的请求一律返回 index.html, 由前端路由处理
|
|
// 必须 no-cache: 否则浏览器/代理缓存旧 index.html, 引用的仍是旧哈希资源,
|
|
// 与部署的 dist 产生"版本不一致"(web 用户刷新也拿不到新页面)
|
|
app.get(/^\/(?!api\/).*/, (_req, res, next) => {
|
|
const indexHtml = path.join(DIST_DIR, 'index.html')
|
|
if (!fs.existsSync(indexHtml)) return next()
|
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
|
res.sendFile(indexHtml)
|
|
})
|
|
}
|
|
|
|
// 统一错误处理
|
|
app.use((err, _req, res, _next) => {
|
|
console.error('[api error]', err)
|
|
res.status(500).json({ error: err.message || '服务器内部错误' })
|
|
})
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`[wardrobe-api] listening on http://127.0.0.1:${PORT}`)
|
|
console.log(`[wardrobe-api] static dist: ${HAS_DIST ? DIST_DIR : '(not built, API only)'}`)
|
|
}) |