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; } // 登录接口 if (req.method === 'POST' && req.url === '/login') { try { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', async () => { try { // 解析表单数据 const formData = new URLSearchParams(body); const userName = formData.get('userName'); const password = formData.get('password'); const projectName = formData.get('projectName'); console.log('接收到的登录请求:', { userName, projectName }); // 验证输入 if (!userName || !password || !projectName) { res.writeHead(400, { '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; } // 连接到userlogin数据库 const userloginPool = mysql.createPool({ ...dbConfig, database: 'userlogin' }); // 验证用户名、密码和职位名称 const [loginRows] = await userloginPool.execute( 'SELECT * FROM login WHERE userName = ? AND password = ? AND projectName = ?', [userName, password, projectName] ); if (loginRows.length === 0) { res.writeHead(401, { '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; } const loginInfo = loginRows[0]; const managerId = loginInfo.managerId; // 获取用户详细信息 const [personnelRows] = await userloginPool.execute( 'SELECT * FROM personnel WHERE managerId = ?', [managerId] ); let userInfo = { userName: loginInfo.userName, projectName: loginInfo.projectName, managerId: loginInfo.managerId }; if (personnelRows.length > 0) { const personnelInfo = personnelRows[0]; userInfo = { ...userInfo, name: personnelInfo.name, phoneNumber: personnelInfo.phoneNumber, managercompany: personnelInfo.managercompany, managerdepartment: personnelInfo.managerdepartment, organization: personnelInfo.organization, alias: personnelInfo.alias }; } console.log('登录成功,用户信息:', userInfo); // 返回成功响应 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, user: userInfo })); // 关闭临时连接池 await userloginPool.end(); } 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; } // 获取二维码合集接口 if (req.method === 'GET' && req.url === '/getQrCollection') { try { // 解析URL参数,获取用户信息 const urlParams = new URLSearchParams(req.url.split('?')[1] || ''); const userName = urlParams.get('userName'); const projectName = urlParams.get('projectName'); console.log('获取二维码合集请求,用户信息:', { userName, projectName }); // 判断用户角色:如果职位名称包含"管理员"或"Admin",则视为管理员 const isAdmin = projectName && (projectName.includes('管理员') || projectName.includes('Admin')); console.log('用户角色判断:', { isAdmin }); // 构建SQL查询 let query, params; if (isAdmin) { // 管理员可以查看所有二维码 query = 'SELECT inviter, inviter_phone as inviterPhone, projectName as inviterProjectName, sessionId, qrCodeUrl, DATE_FORMAT(issueDate, "%Y-%m-%d %H:%i:%s") as createdAt FROM certificate WHERE qrCodeUrl IS NOT NULL GROUP BY sessionId ORDER BY issueDate DESC'; params = []; } else if (userName) { // 采购员只能查看自己的二维码 query = 'SELECT inviter, inviter_phone as inviterPhone, projectName as inviterProjectName, sessionId, qrCodeUrl, DATE_FORMAT(issueDate, "%Y-%m-%d %H:%i:%s") as createdAt FROM certificate WHERE inviter = ? AND qrCodeUrl IS NOT NULL GROUP BY sessionId ORDER BY issueDate DESC'; params = [userName]; } else { // 未登录用户,返回空列表 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, qrCodes: [] })); return; } // 从数据库获取合格证信息 const [rows] = await pool.execute(query, params); // 生成二维码URL列表 const qrCodes = rows.map(row => { // 生成包含会话ID的URL const url = `http://8.137.125.67:3008/certificate.html?sessionId=${encodeURIComponent(row.sessionId)}&inviter=${encodeURIComponent(row.inviter)}&inviterPhone=${encodeURIComponent(row.inviterPhone)}&inviterProjectName=${encodeURIComponent(row.inviterProjectName)}`; return { inviter: row.inviter, inviterPhone: row.inviterPhone, inviterProjectName: row.inviterProjectName, sessionId: row.sessionId, qrCodeUrl: row.qrCodeUrl, createdAt: row.createdAt, url: url }; }); console.log('获取二维码合集成功,数量:', qrCodes.length); 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, qrCodes: qrCodes, isAdmin: isAdmin })); } 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请求,获取最新的合格证信息 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, inviter, inviter_phone as inviterPhone, projectName as inviterProjectName 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, inviter, inviter_phone as inviterPhone, projectName as inviterProjectName 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, inviter: formData.get('inviter'), inviterPhone: formData.get('inviterPhone'), inviterProjectName: formData.get('inviterProjectName') }; 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, inviter: certificate.inviter, inviterPhone: certificate.inviterPhone, inviterProjectName: certificate.inviterProjectName }); // 处理手写签名,上传到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, inviter, inviter_phone, projectName) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [certificate.subjectName, certificate.contact, certificate.productName, certificate.weight, certificate.basis, certificate.origin, certificate.date, signatureUrl, certificate.sessionId, qrCodeUrl, certificate.inviter, certificate.inviterPhone, certificate.inviterProjectName] ); console.log('数据插入数据库成功'); // 返回成功响应,确保返回的signature是OSS URL,日期只显示到日 const responseCertificate = { ...certificate, signature: signatureUrl, qrCodeUrl: qrCodeUrl, inviter: certificate.inviter, inviterPhone: certificate.inviterPhone, inviterProjectName: certificate.inviterProjectName, // 格式化日期为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 = './invite.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}...`);