From 4a0851628ab979f2a83090454793bedf05db4503 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 5 Jun 2026 17:01:03 +0800 Subject: [PATCH] Fix section 404 by rewriting README pages to index.html Generate VitePress rewrites for subdirectory README.md and simplify Express page resolution. Co-authored-by: Cursor --- .vitepress/config.mts | 2 ++ .vitepress/rewrites.mts | 46 +++++++++++++++++++++++++++++++++ DEPLOY.md | 16 ++++++++++-- server/index.js | 56 ++++++++++++++++++++++++++--------------- 4 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 .vitepress/rewrites.mts diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 30ae699..2a02659 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -2,6 +2,7 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import { defineConfig } from 'vitepress' import { missingAssetsPlugin } from './missing-assets-plugin.mjs' +import { generateRewrites } from './rewrites.mts' import { generateSidebar } from './sidebar.mts' const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') @@ -11,6 +12,7 @@ export default defineConfig({ description: '传统文化典籍资料库', lang: 'zh-CN', srcDir: '.', + rewrites: generateRewrites(), srcExclude: [ '**/node_modules/**', '**/.vitepress/**', diff --git a/.vitepress/rewrites.mts b/.vitepress/rewrites.mts new file mode 100644 index 0000000..4415b86 --- /dev/null +++ b/.vitepress/rewrites.mts @@ -0,0 +1,46 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') + +const EXCLUDE_DIRS = new Set([ + 'node_modules', + '.vitepress', + 'server', + 'scripts', + 'assets', + 'images', + '.git', + '金瓶梅', + '黄帝内经', + '健康学习到150岁 - 人体系统调优不完全指南', + '梅花', +]) + +export function generateRewrites(): Record { + const rewrites: Record = {} + + function walk(dir: string, relativeDir: string) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory()) { + if (EXCLUDE_DIRS.has(entry.name)) continue + walk(path.join(dir, entry.name), path.join(relativeDir, entry.name)) + continue + } + + if (!/readme\.md$/i.test(entry.name)) continue + + const from = path.join(relativeDir, entry.name).replace(/\\/g, '/') + const to = from.replace(/README\.md$/i, 'index.md') + rewrites[from] = to + } + } + + for (const entry of fs.readdirSync(root, { withFileTypes: true })) { + if (!entry.isDirectory() || EXCLUDE_DIRS.has(entry.name)) continue + walk(path.join(root, entry.name), entry.name) + } + + return rewrites +} diff --git a/DEPLOY.md b/DEPLOY.md index 8d2ed63..f480811 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -330,14 +330,26 @@ npm run build 部分章节引用的图片若仓库中不存在,页面可正常访问,仅图片为空。 -### Q4:端口被占用 +### Q4:导航栏「五行 / 周易 / 中医」打开是 404 + +**必须重新构建**,`.vitepress/dist` 不会随 `git pull` 自动更新: + +```bash +git pull +npm run build # 关键步骤,不可省略 +npm run start # 或 systemctl restart dao-de-jing +``` + +构建完成后,目录下应存在例如 ` .vitepress/dist/周易/index.html`(不是只有 `README.html`)。 + +### Q5:端口被占用 ```bash ss -tlnp | grep 12100 # 结束占用进程或修改 server/index.js 中的 PORT 常量 ``` -### Q5:内网穿透后外网无法访问 +### Q6:内网穿透后外网无法访问 1. 确认 Ubuntu 上 `npm run start` 正常运行 2. 确认 NPS 客户端在线,TCP 隧道状态正常 diff --git a/server/index.js b/server/index.js index a026151..945baa5 100644 --- a/server/index.js +++ b/server/index.js @@ -34,6 +34,36 @@ function loadAuthConfig() { return config } +function normalizeRoutePath(urlPath) { + let route = decodeURIComponent(urlPath) + if (!route.startsWith('/')) route = `/${route}` + if (route.length > 1 && route.endsWith('/')) route = route.slice(0, -1) + return route === '' ? '/' : route +} + +function resolveHtmlPath(urlPath) { + const route = normalizeRoutePath(urlPath) + const rel = route === '/' ? 'index' : route.replace(/^\//, '') + + const candidates = [ + path.join(distPath, `${rel}.html`), + path.join(distPath, rel, 'index.html'), + path.join(distPath, rel, 'README.html'), + ] + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate + } + } + + return null +} + +function isAssetRequest(urlPath) { + return /\.[a-zA-Z0-9]+$/.test(urlPath) && !urlPath.endsWith('.html') +} + const authConfig = loadAuthConfig() const app = express() @@ -117,31 +147,17 @@ if (!fs.existsSync(distPath)) { app.use( express.static(distPath, { - index: ['index.html', 'README.html'], - extensions: ['html'], - redirect: true, + index: false, + fallthrough: true, }), ) -function resolveHtmlPath(urlPath) { - const rel = decodeURIComponent(urlPath).replace(/^\//, '').replace(/\/$/, '') || 'index' - const candidates = [ - path.join(distPath, `${rel}.html`), - path.join(distPath, rel, 'index.html'), - path.join(distPath, rel, 'README.html'), - ] - - for (const candidate of candidates) { - if (fs.existsSync(candidate)) { - return candidate - } +app.use((req, res, next) => { + if (req.method !== 'GET' && req.method !== 'HEAD') { + return next() } - return null -} - -app.get('*', (req, res, next) => { - if (req.path.includes('.')) { + if (isAssetRequest(req.path)) { return next() }