初始提交
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user