From 01c7b2b673d648284ff68cdca8c299ae40c385c8 Mon Sep 17 00:00:00 2001 From: huanglinhuan Date: Mon, 8 Dec 2025 18:26:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- package.json | 2 +- routes/docs.js | 166 +++++++++++++++++++++++++++++++++++++++++++++++++ server.js | 14 +++-- 4 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 routes/docs.js diff --git a/README.md b/README.md index d07db62..ca7cb32 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 机型信息管理系统 - 后端API +# 通信技术部 - 后端API ## 技术栈 - Node.js diff --git a/package.json b/package.json index d970039..b7e6ff1 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "department-api", "version": "1.0.0", - "description": "机型信息管理系统后端API", + "description": "通信技术部后端API", "main": "server.js", "scripts": { "start": "node server.js", diff --git a/routes/docs.js b/routes/docs.js new file mode 100644 index 0000000..53d6fe5 --- /dev/null +++ b/routes/docs.js @@ -0,0 +1,166 @@ +const express = require('express'); +const router = express.Router(); +const path = require('path'); +const fs = require('fs'); + +const sanitizeSegment = (seg) => String(seg || '').replace(/[^a-zA-Z0-9_\-]/g, ''); + +router.get('/list', (req, res) => { + try { + const series = sanitizeSegment(req.query.series); + const sub = sanitizeSegment(req.query.sub); + + if (!series || !sub) { + return res.status(400).json({ error: '缺少必要参数 series 或 sub' }); + } + + const modelsDocsDir = path.join(__dirname, '..', 'uploads', 'models', series, sub, 'docs'); + const legacyDocsDir = path.join(__dirname, '..', 'uploads', 'docs', series, sub); + const docsDir = fs.existsSync(modelsDocsDir) ? modelsDocsDir : legacyDocsDir; + + if (!fs.existsSync(docsDir)) { + return res.json({ success: true, data: [] }); + } + + const entries = fs.readdirSync(docsDir, { withFileTypes: true }); + const files = entries + .filter((e) => e.isFile()) + .filter((e) => /\.(pdf|doc|docx|xls|xlsx|ppt|pptx)$/i.test(e.name)) + .map((e) => { + const fullPath = path.join(docsDir, e.name); + let size = 0; + try { + size = fs.statSync(fullPath).size; + } catch {} + const base = docsDir === modelsDocsDir ? `/uploads/models/${series}/${sub}/docs` : `/uploads/docs/${series}/${sub}`; + const url = `${base}/${e.name}`; + return { name: e.name, url, size }; + }); + + res.json({ success: true, data: files }); + } catch (error) { + console.error('获取资料列表错误:', error); + res.status(500).json({ error: '服务器内部错误' }); + } +}); + +// 图片列表(支持每机型统一文件夹结构) +router.get('/images', (req, res) => { + try { + const series = sanitizeSegment(req.query.series); + const sub = sanitizeSegment(req.query.sub); + + if (!series || !sub) { + return res.status(400).json({ error: '缺少必要参数 series 或 sub' }); + } + + const candidates = [ + path.join(__dirname, '..', 'uploads', 'models', series, sub, 'image'), + path.join(__dirname, '..', 'uploads', 'models', series, sub, 'images'), + path.join(__dirname, '..', 'uploads', 'images', series, sub), + path.join(__dirname, '..', 'uploads', 'image', series, sub) + ]; + const imgDir = candidates.find((p) => fs.existsSync(p)); + + if (!fs.existsSync(imgDir)) { + return res.json({ success: true, data: [] }); + } + + const entries = fs.readdirSync(imgDir, { withFileTypes: true }); + const files = entries + .filter((e) => e.isFile()) + .filter((e) => /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(e.name)) + .map((e) => { + const fullPath = path.join(imgDir, e.name); + let size = 0; + try { size = fs.statSync(fullPath).size; } catch {} + let base = `/uploads/images/${series}/${sub}`; + if (imgDir.includes(path.join('uploads', 'models'))) { + if (imgDir.endsWith(path.join(series, sub, 'image'))) { + base = `/uploads/models/${series}/${sub}/image`; + } else if (imgDir.endsWith(path.join(series, sub, 'images'))) { + base = `/uploads/models/${series}/${sub}/images`; + } + } else if (imgDir.includes(path.join('uploads', 'image'))) { + base = `/uploads/image/${series}/${sub}`; + } + const url = `${base}/${e.name}`; + return { name: e.name, url, size }; + }); + + res.json({ success: true, data: files }); + } catch (error) { + console.error('获取图片列表错误:', error); + res.status(500).json({ error: '服务器内部错误' }); + } +}); + +module.exports = router; +// 使用相同 router 添加规格读取接口 +router.get('/specs', (req, res) => { + try { + const series = sanitizeSegment(req.query.series); + const sub = sanitizeSegment(req.query.sub); + + if (!series || !sub) { + return res.status(400).json({ error: '缺少必要参数 series 或 sub' }); + } + + const dirCandidates = [ + path.join(__dirname, '..', 'uploads', 'models', series, sub, 'specs'), + path.join(__dirname, '..', 'uploads', 'specs', series, sub) + ]; + + const parseCsvFile = (filePath) => { + const raw = fs.readFileSync(filePath, 'utf8'); + const lines = raw.replace(/^\uFEFF/, '').split(/\r?\n/).filter((l) => l.trim().length); + const normalize = (s) => String(s || '').trim().replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1'); + const parseLine = (l) => { + const replaced = l.replace(/;/g, ';').replace(/,/g, ',').replace(/\uff0c/g, ',').replace(/\uff1b/g, ';'); + let tokens = replaced.match(/"([^"]*)"|[^,;\t]+/g) || []; + if (tokens.length < 2) { + const parts = replaced.split(/:|:/); + if (parts.length >= 2) { + tokens = [parts[0], parts.slice(1).join(':')]; + } + } + const key = normalize(tokens[0] || ''); + const val = normalize(tokens[1] || ''); + return { key, value: val }; + }; + return lines.map(parseLine).filter((r) => r.key.length); + }; + + const dirPath = dirCandidates.find((p) => fs.existsSync(p) && fs.statSync(p).isDirectory()); + if (dirPath) { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + const csvFiles = entries.filter((e) => e.isFile() && /\.csv$/i.test(String(e.name).trim())); + if (csvFiles.length) { + const sections = csvFiles.map((e) => { + const fp = path.join(dirPath, e.name); + const title = e.name.replace(/\.csv$/i, ''); + return { title, rows: parseCsvFile(fp) }; + }); + return res.json({ success: true, data: sections }); + } + } + + const fileCandidates = [ + path.join(__dirname, '..', 'uploads', 'models', series, sub, 'specs', 'specs.csv'), + path.join(__dirname, '..', 'uploads', 'models', series, sub, 'specs.csv'), + path.join(__dirname, '..', 'uploads', 'specs', series, `${sub}.csv`), + path.join(__dirname, '..', 'uploads', 'specs', series, sub, 'specs.csv') + ]; + const filePath = fileCandidates.find((p) => fs.existsSync(p)); + + if (!filePath) { + return res.json({ success: true, data: [] }); + } + + const specs = parseCsvFile(filePath); + res.json({ success: true, data: specs }); + } catch (error) { + console.error('读取规格CSV错误:', error); + res.status(500).json({ error: '服务器内部错误' }); + } +}); diff --git a/server.js b/server.js index 21f6a6c..bbde71b 100755 --- a/server.js +++ b/server.js @@ -2,8 +2,9 @@ const express = require('express'); const cors = require('cors'); const path = require('path'); const authRoutes = require('./routes/auth'); -const aircraftRoutes = require('./routes/aircraft'); -const pdfRoutes = require('./routes/pdf'); +const aircraftRoutes = require('./routes/aircraft'); +const pdfRoutes = require('./routes/pdf'); +const docsRoutes = require('./routes/docs'); const { authenticateToken } = require('./middleware/auth'); const app = express(); @@ -17,10 +18,11 @@ app.use(express.urlencoded({ extended: true })); // 静态文件服务 - 用于提供PDF文件 app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); -// 路由 -app.use('/api/auth', authRoutes); -app.use('/api/aircraft', authenticateToken, aircraftRoutes); -app.use('/api/pdf', authenticateToken, pdfRoutes); +// 路由 +app.use('/api/auth', authRoutes); +app.use('/api/aircraft', authenticateToken, aircraftRoutes); +app.use('/api/pdf', authenticateToken, pdfRoutes); +app.use('/api/docs', docsRoutes); // 健康检查 app.get('/api/health', (req, res) => {