const express = require('express'); const multer = require('multer'); const cors = require('cors'); const path = require('path'); const fs = require('fs'); const https = require('https'); const app = express(); const PORT = 3000; const API_KEY = 'sk-cp-eS_WJSIREBtekg3RTYbCgTxbG_FA_EEWEb5ta4scgH9miP0o4pSApLSXZMpzYcYSRXXP79AcTkS279vyk49EK6ihYJYf-S_g4-OBNnO_3pIonWg1NoBRXBg'; // 中间件 app.use(cors({ origin: ['https://www.airobotcto.com', 'http://www.airobotcto.com', 'http://118.24.135.162'], methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type'], })); app.use(express.json({ limit: '100mb' })); app.use(express.static('public')); // 目录 const DIRS = ['uploads', 'results', 'data', 'templates', 'frames']; DIRS.forEach(dir => { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); }); // 数据文件 const DB = { orders: path.join('data', 'orders.json'), users: path.join('data', 'users.json'), templates: path.join('data', 'templates.json'), frames: path.join('data', 'frames.json'), waiters: path.join('data', 'waiters.json'), admins: path.join('data', 'admins.json'), }; const initDB = () => { Object.values(DB).forEach(f => { if (!fs.existsSync(f)) fs.writeFileSync(f, JSON.stringify([], null, 2)); }); }; initDB(); const readDB = (key) => { try { return JSON.parse(fs.readFileSync(DB[key], 'utf8')); } catch { return []; } }; const writeDB = (key, data) => fs.writeFileSync(DB[key], JSON.stringify(data, null, 2)); // 保存 base64 图片 function saveBase64Image(base64Data, subDir = 'uploads') { const matches = base64Data.match(/^data:image\/(\w+);base64,(.+)$/); if (!matches) return null; const filename = Date.now() + '-' + Math.round(Math.random() * 100000) + '.' + matches[1]; const filepath = path.join(subDir, filename); fs.writeFileSync(filepath, Buffer.from(matches[2], 'base64')); return filepath; } // MiniMax 生成 function callMiniMax(imagePath, prompt) { return new Promise((resolve, reject) => { const imageBase64 = fs.readFileSync(imagePath).toString('base64'); const imageDataUrl = `data:image/png;base64,${imageBase64}`; const body = JSON.stringify({ model: 'image-01', image_size: '16:9', num_images: 1, prompt: prompt, image_url: imageDataUrl }); const options = { hostname: 'api.minimaxi.com', path: '/v1/images/generations', method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }; const req = https.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', async () => { try { const result = JSON.parse(data); if (result.data && result.data[0] && result.data[0].url) { const filename = Date.now() + '-result.png'; const localPath = path.join('results', filename); const file = fs.createWriteStream(localPath); https.get(result.data[0].url, (r) => { r.pipe(file); file.on('finish', () => resolve('/results/' + filename)); }); } else if (result.data && result.data[0] && result.data[0].b64_json) { const filename = Date.now() + '-result.png'; const localPath = path.join('results', filename); fs.writeFileSync(localPath, Buffer.from(result.data[0].b64_json, 'base64')); resolve('/results/' + filename); } else { reject(new Error(JSON.stringify(result))); } } catch (e) { reject(e); } }); }); req.on('error', reject); req.write(body); req.end(); }); } // ============ 公开接口 ============ // 健康检查 app.get('/api/health', (req, res) => res.json({ status: 'ok', time: new Date().toISOString() })); // 用户注册(不验证) app.post('/api/user/register', (req, res) => { const { phone, waiterCode } = req.body; if (!phone) return res.status(400).json({ error: '请填写手机号' }); const users = readDB('users'); const exist = users.find(u => u.phone === phone); if (exist) return res.json({ success: true, userId: exist.id, phone: exist.phone }); const user = { id: Date.now().toString(), phone, waiterCode: waiterCode || '', createdAt: new Date().toISOString() }; users.push(user); writeDB('users', users); res.json({ success: true, userId: user.id, phone: user.phone }); }); // 服务员注册 app.post('/api/waiter/register', (req, res) => { const { phone, name } = req.body; if (!phone) return res.status(400).json({ error: '请填写手机号' }); const waiters = readDB('waiters'); const exist = waiters.find(w => w.phone === phone); if (exist) return res.json({ success: true, waiterId: exist.id, phone: exist.phone }); const waiter = { id: Date.now().toString(), phone, name: name || phone, code: 'W' + Date.now().toString().slice(-6), createdAt: new Date().toISOString() }; waiters.push(waiter); writeDB('waiters', waiters); res.json({ success: true, waiterId: waiter.id, phone: waiter.phone, code: waiter.code }); }); // 获取模板列表 app.get('/api/templates', (req, res) => { const templates = readDB('templates'); res.json({ success: true, templates }); }); // 获取相框列表 app.get('/api/frames', (req, res) => { const frames = readDB('frames'); res.json({ success: true, frames }); }); // 上传模板(管理员) app.post('/api/admin/template', upload.single('image'), (req, res) => { const { group, name } = req.body; if (!req.file) return res.status(400).json({ error: '请上传图片' }); const templates = readDB('templates'); const t = { id: Date.now().toString(), group: group || 'default', name: name || '', image: '/templates/' + req.file.filename, createdAt: new Date().toISOString() }; templates.push(t); writeDB('templates', templates); res.json({ success: true, template: t }); }); // 上传相框(管理员) app.post('/api/admin/frame', upload.single('image'), (req, res) => { const { name, price, type } = req.body; if (!req.file) return res.status(400).json({ error: '请上传图片' }); const frames = readDB('frames'); const f = { id: Date.now().toString(), name: name || '', price: parseFloat(price) || 19, type: type || 'normal', image: '/frames/' + req.file.filename, createdAt: new Date().toISOString() }; frames.push(f); writeDB('frames', frames); res.json({ success: true, frame: f }); }); // AI 生成全家福 app.post('/api/generate', async (req, res) => { try { const { template, faces, userId } = req.body; if (!template || !faces || !Array.isArray(faces) || faces.length === 0) { return res.status(400).json({ error: '缺少参数' }); } const templatePath = saveBase64Image(template); const facePaths = faces.map(f => saveBase64Image(f)); const prompt = faces.length === 1 ? '替换图中人物的面部为新的面部,保持原图姿势、场景、构图完全不变' : `替换图中所有${faces.length}个人物的面部,生成一张全家福合影,保持原图的场景、背景、姿态、构图完全不变`; const resultUrl = await callMiniMax(templatePath, prompt); facePaths.forEach(p => { try { fs.unlinkSync(p); } catch(e){} }); try { fs.unlinkSync(templatePath); } catch(e){} const orders = readDB('orders'); const order = { id: Date.now().toString(), userId, templatePath, faceCount: facePaths.length, resultUrl, status: 'generated', createdAt: new Date().toISOString() }; orders.push(order); writeDB('orders', orders); res.json({ success: true, imageUrl: 'https://www.airobotcto.com' + resultUrl, orderId: order.id }); } catch (error) { console.error('生成失败:', error); res.status(500).json({ error: error.message }); } }); // 创建订单 app.post('/api/order/create', (req, res) => { const { userId, templateId, frameId, faceCount, totalPrice } = req.body; const orders = readDB('orders'); const order = { id: Date.now().toString(), userId, templateId, frameId, faceCount, totalPrice: parseFloat(totalPrice) || 0, status: 'pending', createdAt: new Date().toISOString() }; orders.push(order); writeDB('orders', orders); res.json({ success: true, order }); }); // 获取用户订单 app.get('/api/orders/:userId', (req, res) => { const orders = readDB('orders').filter(o => o.userId === req.params.userId); res.json({ success: true, orders }); }); // 服务员订单列表 app.get('/api/waiter/orders', (req, res) => { const orders = readDB('orders'); res.json({ success: true, orders }); }); // 服务员确认首单付款 app.post('/api/waiter/confirm', (req, res) => { const { orderId } = req.body; const orders = readDB('orders'); const order = orders.find(o => o.id === orderId); if (!order) return res.status(404).json({ error: '订单不存在' }); order.status = 'paid_by_waiter'; order.waiterPaidAt = new Date().toISOString(); writeDB('orders', orders); res.json({ success: true }); }); // 服务员确认发货 app.post('/api/waiter/deliver', (req, res) => { const { orderId } = req.body; const orders = readDB('orders'); const order = orders.find(o => o.id === orderId); if (!order) return res.status(404).json({ error: '订单不存在' }); order.status = 'delivered'; order.deliveredAt = new Date().toISOString(); writeDB('orders', orders); res.json({ success: true }); }); // 管理员:订单列表 app.get('/api/admin/orders', (req, res) => { res.json({ success: true, orders: readDB('orders') }); }); // 管理员:删除模板 app.delete('/api/admin/template/:id', (req, res) => { let templates = readDB('templates'); templates = templates.filter(t => t.id !== req.params.id); writeDB('templates', templates); res.json({ success: true }); }); // 管理员:删除相框 app.delete('/api/admin/frame/:id', (req, res) => { let frames = readDB('frames'); frames = frames.filter(f => f.id !== req.params.id); writeDB('frames', frames); res.json({ success: true }); }); // 服务员登录 app.post('/api/waiter/login', (req, res) => { const { code } = req.body; const waiters = readDB('waiters'); const waiter = waiters.find(w => w.code === code); if (!waiter) return res.status(401).json({ error: '工号错误' }); res.json({ success: true, waiterId: waiter.id, name: waiter.name, code: waiter.code }); }); // 管理员登录 app.post('/api/admin/login', (req, res) => { const { password } = req.body; if (password !== 'admin2026') return res.status(401).json({ error: '密码错误' }); res.json({ success: true, adminId: 'admin', name: '管理员' }); }); // 静态文件 app.use('/uploads', express.static('uploads')); app.use('/results', express.static('results')); app.use('/templates', express.static('templates')); app.use('/frames', express.static('frames')); app.listen(PORT, () => { console.log(`全家福AI服务已启动: http://localhost:${PORT}`); });