'use strict'; // 静态前端:精确路径白名单(新增 public 文件需在 STATIC_MAP 登记)+ /assets/ 单段文件 const fs = require('fs'); const path = require('path'); const STATIC_MAP = { '/': ['index.html', 'text/html; charset=utf-8'], '/index.html': ['index.html', 'text/html; charset=utf-8'], '/app.js': ['app.js', 'application/javascript; charset=utf-8'], '/crawl.js': ['crawl.js', 'application/javascript; charset=utf-8'], '/gen.js': ['gen.js', 'application/javascript; charset=utf-8'], '/history.js': ['history.js', 'application/javascript; charset=utf-8'], '/tasks.js': ['tasks.js', 'application/javascript; charset=utf-8'], '/analysis-templates.js': ['analysis-templates.js', 'application/javascript; charset=utf-8'], '/reports.js': ['reports.js', 'application/javascript; charset=utf-8'], '/lake.js': ['lake.js', 'application/javascript; charset=utf-8'], '/ops.js': ['ops.js', 'application/javascript; charset=utf-8'], '/schools.js': ['schools.js', 'application/javascript; charset=utf-8'], '/multi-select.js': ['multi-select.js', 'application/javascript; charset=utf-8'], '/cockpit.js': ['cockpit.js', 'application/javascript; charset=utf-8'], '/vendor/china-map.json': ['vendor/china-map.json', 'application/json; charset=utf-8'], '/vendor/cn-universities.json': ['vendor/cn-universities.json', 'application/json; charset=utf-8'], '/vendor/cn-cities.json': ['vendor/cn-cities.json', 'application/json; charset=utf-8'], '/ask.js': ['ask.js', 'application/javascript; charset=utf-8'], '/vendor/echarts.min.js': ['vendor/echarts.min.js', 'application/javascript; charset=utf-8'], '/style.css': ['style.css', 'text/css; charset=utf-8'], }; const STATIC_TYPES = { '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.css': 'text/css', '.js': 'application/javascript', }; function serveStatic(publicDir, pathname, res) { const entry = STATIC_MAP[pathname]; if (entry) { const full = path.join(publicDir, entry[0]); fs.readFile(full, (err, data) => { if (err) { res.writeHead(404); return res.end('Not found'); } res.writeHead(200, { 'Content-Type': entry[1], 'Cache-Control': 'no-cache' }); res.end(data); }); return; } if (pathname.startsWith('/assets/')) { const rel = pathname.replace('/assets/', ''); if (rel.includes('..') || rel.includes('/')) { res.writeHead(403); return res.end('Forbidden'); } const full = path.join(publicDir, 'assets', rel); if (!fs.existsSync(full) || !fs.statSync(full).isFile()) { res.writeHead(404); return res.end('Not found'); } const ext = path.extname(full).toLowerCase(); res.writeHead(200, { 'Content-Type': STATIC_TYPES[ext] || 'application/octet-stream', 'Cache-Control': 'no-cache' }); fs.createReadStream(full).pipe(res); return; } res.writeHead(404); res.end('Not found'); } function register(app, ctx) { app.fallback((pathname, res) => serveStatic(ctx.PUBLIC_DIR, pathname, res)); } module.exports = { register };