const express = require('express'); const path = require('path'); const fs = require('fs'); const multer = require('multer'); const DataService = require('../services/data'); const router = express.Router(); const ASSETS_DIR = path.join(__dirname, '../../Assets'); const MANIFEST_PATH = path.join(ASSETS_DIR, 'manifest.json'); const getIp = (req) => (req.headers['x-forwarded-for'] || '').toString().split(',')[0].trim() || req.ip || req.connection?.remoteAddress || ''; const appendReqLog = (req, { category, type, detail, source, level } = {}) => { const entry = { ts: new Date().toISOString(), ip: getIp(req), ua: String(req.headers['user-agent'] || ''), method: req.method, path: req.originalUrl || req.path || '', category: String(category || '').trim() || 'admin', source: source == null ? undefined : String(source || '').trim(), level: level == null ? undefined : String(level || '').trim(), type: String(type || '').trim() || 'event', detail: (detail === undefined) ? null : detail }; DataService.appendLog(entry); }; const ensureDir = (p) => { if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); }; const readManifest = () => { try { if (!fs.existsSync(MANIFEST_PATH)) return {}; const raw = fs.readFileSync(MANIFEST_PATH, 'utf8'); const data = JSON.parse(raw || '{}'); return (data && typeof data === 'object') ? data : {}; } catch (e) { return {}; } }; const writeManifest = (m) => { ensureDir(ASSETS_DIR); const out = (m && typeof m === 'object') ? m : {}; fs.writeFileSync(MANIFEST_PATH, JSON.stringify(out, null, 2), 'utf8'); }; const removeByPrefix = (prefix) => { ensureDir(ASSETS_DIR); const list = fs.readdirSync(ASSETS_DIR); for (const name of list) { if (!name.startsWith(prefix + '.')) continue; const fp = path.join(ASSETS_DIR, name); try { fs.unlinkSync(fp); } catch (e) {} } }; const storageForPrefix = (prefix) => multer.diskStorage({ destination: (req, file, cb) => { ensureDir(ASSETS_DIR); cb(null, ASSETS_DIR); }, filename: (req, file, cb) => { const ext = path.extname(file.originalname || '').toLowerCase(); removeByPrefix(prefix); cb(null, `${prefix}${ext || ''}`); } }); const buildUploader = ({ prefix, allowedExts }) => multer({ storage: storageForPrefix(prefix), fileFilter: (req, file, cb) => { const ext = path.extname(file.originalname || '').toLowerCase(); if (!ext || !allowedExts.includes(ext)) return cb(new Error('不支持的文件类型')); cb(null, true); }, limits: { fileSize: 20 * 1024 * 1024 } }); const uploadRouteMap = buildUploader({ prefix: 'route-map', allowedExts: ['.png', '.jpg', '.jpeg', '.webp', '.svg'] }); const uploadFareTable = buildUploader({ prefix: 'fare-table', allowedExts: ['.csv', '.json'] }); router.get('/manifest', (req, res) => { const m = readManifest(); const routeMap = m.routeMap || null; const fareTable = m.fareTable || null; res.json({ ok: true, routeMap, fareTable, updatedAt: m.updatedAt || null }); }); router.post('/route-map', uploadRouteMap.single('file'), (req, res) => { if (!req.file) return res.status(400).json({ ok: false, error: '缺少文件' }); const m = readManifest(); m.routeMap = path.basename(req.file.filename); m.updatedAt = Date.now(); writeManifest(m); appendReqLog(req, { category: 'admin', type: 'asset_upload_route_map', detail: { file: m.routeMap } }); res.json({ ok: true, routeMap: m.routeMap, updatedAt: m.updatedAt }); }); router.post('/fare-table', uploadFareTable.single('file'), (req, res) => { if (!req.file) return res.status(400).json({ ok: false, error: '缺少文件' }); const m = readManifest(); m.fareTable = path.basename(req.file.filename); m.updatedAt = Date.now(); writeManifest(m); appendReqLog(req, { category: 'admin', type: 'asset_upload_fare_table', detail: { file: m.fareTable } }); res.json({ ok: true, fareTable: m.fareTable, updatedAt: m.updatedAt }); }); router.delete('/route-map', (req, res) => { const m = readManifest(); if (m.routeMap) { try { fs.unlinkSync(path.join(ASSETS_DIR, m.routeMap)); } catch (e) {} } removeByPrefix('route-map'); m.routeMap = null; m.updatedAt = Date.now(); writeManifest(m); appendReqLog(req, { category: 'admin', type: 'asset_delete_route_map', detail: { ok: true } }); res.json({ ok: true }); }); router.delete('/fare-table', (req, res) => { const m = readManifest(); if (m.fareTable) { try { fs.unlinkSync(path.join(ASSETS_DIR, m.fareTable)); } catch (e) {} } removeByPrefix('fare-table'); m.fareTable = null; m.updatedAt = Date.now(); writeManifest(m); appendReqLog(req, { category: 'admin', type: 'asset_delete_fare_table', detail: { ok: true } }); res.json({ ok: true }); }); module.exports = router;