static.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. 'use strict';
  2. // 静态前端:精确路径白名单(新增 public 文件需在 STATIC_MAP 登记)+ /assets/ 单段文件
  3. const fs = require('fs');
  4. const path = require('path');
  5. const STATIC_MAP = {
  6. '/': ['index.html', 'text/html; charset=utf-8'],
  7. '/index.html': ['index.html', 'text/html; charset=utf-8'],
  8. '/app.js': ['app.js', 'application/javascript; charset=utf-8'],
  9. '/crawl.js': ['crawl.js', 'application/javascript; charset=utf-8'],
  10. '/gen.js': ['gen.js', 'application/javascript; charset=utf-8'],
  11. '/history.js': ['history.js', 'application/javascript; charset=utf-8'],
  12. '/tasks.js': ['tasks.js', 'application/javascript; charset=utf-8'],
  13. '/analysis-templates.js': ['analysis-templates.js', 'application/javascript; charset=utf-8'],
  14. '/reports.js': ['reports.js', 'application/javascript; charset=utf-8'],
  15. '/lake.js': ['lake.js', 'application/javascript; charset=utf-8'],
  16. '/ops.js': ['ops.js', 'application/javascript; charset=utf-8'],
  17. '/schools.js': ['schools.js', 'application/javascript; charset=utf-8'],
  18. '/multi-select.js': ['multi-select.js', 'application/javascript; charset=utf-8'],
  19. '/cockpit.js': ['cockpit.js', 'application/javascript; charset=utf-8'],
  20. '/report.js': ['report.js', 'application/javascript; charset=utf-8'],
  21. '/vendor/china-map.json': ['vendor/china-map.json', 'application/json; charset=utf-8'],
  22. '/vendor/cn-universities.json': ['vendor/cn-universities.json', 'application/json; charset=utf-8'],
  23. '/vendor/cn-cities.json': ['vendor/cn-cities.json', 'application/json; charset=utf-8'],
  24. '/ask.js': ['ask.js', 'application/javascript; charset=utf-8'],
  25. '/vendor/echarts.min.js': ['vendor/echarts.min.js', 'application/javascript; charset=utf-8'],
  26. '/style.css': ['style.css', 'text/css; charset=utf-8'],
  27. };
  28. const STATIC_TYPES = {
  29. '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml',
  30. '.ico': 'image/x-icon', '.css': 'text/css', '.js': 'application/javascript',
  31. };
  32. function serveStatic(publicDir, pathname, res) {
  33. const entry = STATIC_MAP[pathname];
  34. if (entry) {
  35. const full = path.join(publicDir, entry[0]);
  36. fs.readFile(full, (err, data) => {
  37. if (err) { res.writeHead(404); return res.end('Not found'); }
  38. res.writeHead(200, { 'Content-Type': entry[1], 'Cache-Control': 'no-cache' });
  39. res.end(data);
  40. });
  41. return;
  42. }
  43. if (pathname.startsWith('/assets/')) {
  44. const rel = pathname.replace('/assets/', '');
  45. if (rel.includes('..') || rel.includes('/')) { res.writeHead(403); return res.end('Forbidden'); }
  46. const full = path.join(publicDir, 'assets', rel);
  47. if (!fs.existsSync(full) || !fs.statSync(full).isFile()) { res.writeHead(404); return res.end('Not found'); }
  48. const ext = path.extname(full).toLowerCase();
  49. res.writeHead(200, { 'Content-Type': STATIC_TYPES[ext] || 'application/octet-stream', 'Cache-Control': 'no-cache' });
  50. fs.createReadStream(full).pipe(res);
  51. return;
  52. }
  53. res.writeHead(404);
  54. res.end('Not found');
  55. }
  56. function register(app, ctx) {
  57. app.fallback((pathname, res) => serveStatic(ctx.PUBLIC_DIR, pathname, res));
  58. }
  59. module.exports = { register };