Files
2026-06-21 10:00:13 +08:00

152 lines
4.7 KiB
JavaScript

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;