const http = require('http'); const fs = require('fs'); const path = require('path'); const mysql = require('mysql2/promise'); const OssUploader = require('./oss-uploader'); const port = 3008; // 数据库配置 const dbConfig = { host: process.env.DB_HOST || '1.95.162.61', user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || 'schl@2025', database: process.env.DB_NAME || 'wechat_app', waitForConnections: true, connectionLimit: 20, queueLimit: 0, connectTimeout: 10000, timezone: '+08:00' }; // 创建数据库连接池 const pool = mysql.createPool(dbConfig); // 测试数据库连接 async function testDbConnection() { try { const connection = await pool.getConnection(); console.log('数据库连接成功'); connection.release(); } catch (error) { console.error('数据库连接失败:', error.message); } } testDbConnection(); const server = http.createServer(async (req, res) => { // 健康检查端点 if (req.method === 'GET' && req.url === '/health') { try { // 测试数据库连接 const [rows] = await pool.execute('SELECT 1'); // 测试OSS连接 const ossStatus = await OssUploader.testConnection(); res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }); res.end(JSON.stringify({ success: true, status: 'healthy', database: 'connected', oss: ossStatus.success ? 'connected' : 'disconnected', timestamp: new Date().toISOString() })); } catch (error) { console.error('健康检查失败:', error.message); res.writeHead(503, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }); res.end(JSON.stringify({ success: false, status: 'unhealthy', error: error.message, timestamp: new Date().toISOString() })); } return; } // 处理GET请求,获取最新的合格证信息 if (req.method === 'GET' && req.url.startsWith('/getLatestCertificate')) { try { // 解析URL参数,获取sessionId const urlParams = new URLSearchParams(req.url.split('?')[1] || ''); const sessionId = urlParams.get('sessionId'); console.log('获取合格证信息,sessionId:', sessionId); // 从数据库获取对应会话的最新合格证信息 let query, params; if (sessionId) { query = 'SELECT company as subjectName, phoneNumber as contact, productName, grossWeight as weight, commitBasis as basis, origin, DATE_FORMAT(issueDate, "%Y-%m-%d") as date, signature, qrCodeUrl FROM certificate WHERE sessionId = ? ORDER BY id DESC LIMIT 1'; params = [sessionId]; } else { query = 'SELECT company as subjectName, phoneNumber as contact, productName, grossWeight as weight, commitBasis as basis, origin, DATE_FORMAT(issueDate, "%Y-%m-%d") as date, signature, qrCodeUrl FROM certificate WHERE sessionId IS NULL ORDER BY id DESC LIMIT 1'; params = []; } const [rows] = await pool.execute(query, params); const certificate = rows.length > 0 ? rows[0] : null; console.log('从数据库获取的最新合格证信息:', certificate); // 返回成功响应 res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }); res.end(JSON.stringify({ success: true, certificate })); } catch (error) { console.error('获取合格证信息失败:', error.message); res.writeHead(500, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }); res.end(JSON.stringify({ success: false, error: '获取失败,请重试' })); } return; } // 处理POST请求 if (req.method === 'POST' && req.url === '/submit') { try { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', async () => { try { // 解析表单数据 const formData = new URLSearchParams(body); // 使用当前时间作为开具日期,数据库连接已配置为北京时间 const now = new Date(); const sessionId = formData.get('sessionId'); const certificate = { subjectName: formData.get('subjectName'), contact: formData.get('contact'), productName: formData.get('productName'), weight: formData.get('weight'), basis: formData.get('basis'), origin: formData.get('origin'), date: now, signature: formData.get('signature'), sessionId: sessionId }; console.log('接收到的表单数据:', { subjectName: certificate.subjectName, contact: certificate.contact, productName: certificate.productName, weight: certificate.weight, basis: certificate.basis, origin: certificate.origin, date: certificate.date.toISOString(), hasSignature: !!certificate.signature, sessionId: sessionId }); // 处理手写签名,上传到OSS let signatureUrl = null; if (certificate.signature) { try { // 从base64字符串中提取图片数据 const base64Data = certificate.signature.replace(/^data:image\/png;base64,/, ''); const buffer = Buffer.from(base64Data, 'base64'); // 生成唯一的文件名,包含合格证信息的标识 const timestamp = Date.now(); const filename = `certificate_signature_${timestamp}.png`; // 上传到OSS,指定文件夹为certificate/signatures signatureUrl = await OssUploader.uploadBuffer(buffer, filename, 'certificate/signatures', 'image'); console.log('手写签名上传到OSS成功:', signatureUrl); } catch (error) { console.error('上传手写签名到OSS失败:', error.message); // 即使上传失败,也继续处理,将签名数据存储为base64 signatureUrl = certificate.signature; } } // 生成查看页面的URL,包含会话ID const viewUrl = `http://8.137.125.67:3008/view.html?sessionId=${encodeURIComponent(sessionId)}`; // 生成二维码并上传到OSS let qrCodeUrl = null; try { // 使用Google Charts API生成二维码 const qrCodeApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(viewUrl)}`; // 下载二维码图片 const https = require('https'); const qrCodeBuffer = await new Promise((resolve, reject) => { https.get(qrCodeApiUrl, (response) => { const chunks = []; response.on('data', (chunk) => chunks.push(chunk)); response.on('end', () => resolve(Buffer.concat(chunks))); response.on('error', reject); }).on('error', reject); }); // 生成唯一的文件名 const timestamp = Date.now(); const qrCodeFilename = `certificate_qrcode_${timestamp}.png`; // 上传到OSS,指定文件夹为certificate/qrcodes qrCodeUrl = await OssUploader.uploadBuffer(qrCodeBuffer, qrCodeFilename, 'certificate/qrcodes', 'image'); console.log('二维码上传到OSS成功:', qrCodeUrl); } catch (error) { console.error('上传二维码到OSS失败:', error.message); // 即使上传失败,也继续处理 } // 插入数据到数据库 await pool.execute( 'INSERT INTO certificate (company, phoneNumber, productName, grossWeight, commitBasis, origin, issueDate, signature, sessionId, qrCodeUrl) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [certificate.subjectName, certificate.contact, certificate.productName, certificate.weight, certificate.basis, certificate.origin, certificate.date, signatureUrl, certificate.sessionId, qrCodeUrl] ); console.log('数据插入数据库成功'); // 返回成功响应,确保返回的signature是OSS URL,日期只显示到日 const responseCertificate = { ...certificate, signature: signatureUrl, qrCodeUrl: qrCodeUrl, // 格式化日期为YYYY-MM-DD格式 date: certificate.date.toISOString().split('T')[0] }; res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }); res.end(JSON.stringify({ success: true, certificate: responseCertificate })); } catch (error) { console.error('处理表单数据失败:', error.message); res.writeHead(500, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }); res.end(JSON.stringify({ success: false, error: '处理数据失败,请重试' })); } }); } catch (error) { console.error('保存合格证信息失败:', error.message); res.writeHead(500, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }); res.end(JSON.stringify({ success: false, error: '保存失败,请重试' })); } return; } // 处理GET请求 // 解析请求路径,移除查询参数 let filePath = '.' + req.url.split('?')[0]; if (filePath === './') { filePath = './certificate.html'; } // 获取文件扩展名 const extname = String(path.extname(filePath)).toLowerCase(); // 定义MIME类型 const mimeTypes = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.wav': 'audio/wav', '.mp4': 'video/mp4', '.woff': 'application/font-woff', '.ttf': 'application/font-ttf', '.eot': 'application/vnd.ms-fontobject', '.otf': 'application/font-otf', '.wasm': 'application/wasm' }; // 获取对应的MIME类型 const contentType = mimeTypes[extname] || 'application/octet-stream'; // 读取文件 fs.readFile(filePath, (error, content) => { if (error) { if(error.code == 'ENOENT') { // 文件不存在 fs.readFile('./404.html', (error, content) => { res.writeHead(404, { 'Content-Type': 'text/html' }); res.end(content, 'utf-8'); }); } else { // 服务器错误 res.writeHead(500); res.end('Sorry, check with the site admin for error: ' + error.code + ' ..\n'); res.end(); } } else { // 成功读取文件 res.writeHead(200, { 'Content-Type': contentType }); res.end(content, 'utf-8'); } }); }); // 启动服务器 server.listen(port, () => { console.log(`Server running at http://localhost:${port}/`); }); console.log(`Server starting on port ${port}...`);