本地正常的WhatsApp批量消息API部署至线上服务器异常求助
问题描述
我开发了一款可通过CSV文件批量发送WhatsApp消息与媒体文件的API,本地服务器运行正常,但部署至Render等线上服务器时,Chromium浏览器及核心代码出现异常,无任何执行迹象。以下是初始代码及修改后的代码,请求排查部署后的异常问题。
初始代码
const express = require('express'); const multer = require('multer'); const { promisify } = require('util'); const fs = require('fs'); const bodyParser = require('body-parser'); const { Client, LocalAuth } = require('whatsapp-web.js'); const { error } = require('console'); const { Session } = require('inspector'); const { MessageMedia } = require('whatsapp-web.js'); const { forEach } = require('lodash'); const cors = require('cors'); const app = express(); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()) const storage = multer.diskStorage({ destination: (req, file, cb)=>{ cb(null,'Csv/'); }, filename:(req, file, cb)=>{ cb(null, file.originalname); } }); const Csv = multer({ storage: storage }) const Storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, './uploads'); // The directory where uploaded files will be saved }, filename: (req, file, cb) => { cb(null, file.originalname); // Use the original file name }, }); const upload = multer({ storage: Storage }); // Initialize the WhatsApp client app.get('/send', (req, res) => { const client = new Client({ authStrategy: new LocalAuth(), // Options for the client puppeteer: { headless: false, args:[ '--no-sandbox', '--disable-setuid-sandbox' ] } }); client.on('qr', (qr) => { // Generate and display the QR code in the terminal console.log('QR Code:', qr); }); client.on('ready', () => { console.log('Client is ready!'); }); client.initialize(); }) app.post('/send-message', Csv.single('numbers'), async (req, res) => { try { const message = req.body.message; const numbers = await promisify(fs.readFile)(req.file.path); console.log(message); console.log(numbers); let number = numbers.toString().split('\r\n'); for (let index = 0; index < number.length; index++) { let test = number[index] console.log(test); let code = await client.sendMessage('234' + test + '@c.us', message); console.log(code); } // Send the message to each number // console.log('Message sent to:', number); // res.status(200).send('Messages sent successfully!'); } catch (error) { console.error('Error:', error); res.status(500).send('Failed to send messages!'); } }); app.post('/send-media', upload.fields([{ name: 'media', maxCount: 1 }, { name: 'csv', maxCount: 1 }]), async(req, res) => { const csvFile = await promisify(fs.readFile)(req.files['csv'][0].path); const mediaFile = req.files['media'][0]; const message = req.body.message; console.log(mediaFile); console.log(message); try{ let number = csvFile.toString().split('\r\n'); console.log(number); for (let index = 0; index < number.length; index++) { let csv = number[index] const chat = await client.getChatById('234' + csv + '@c.us'); console.log(chat); const media = MessageMedia.fromFilePath(mediaFile.path); chat.sendMessage(media, { caption: message }); }; res.status(200).json({ success: true, message: 'Media sent' }) } catch (error) { res.status(500).json({ success: false, error: 'Failed to send media' }); } }); const port = process.env.PORT || 3000; app.listen(port, () => console.log(`Server is running on port ${port}`));
修改后的代码
const express = require('express'); const multer = require('multer'); const { promisify } = require('util'); const fs = require('fs'); const bodyParser = require('body-parser'); const puppeteer = require('puppeteer'); const { Client, LocalAuth } = require('whatsapp-web.js'); const { error } = require('console'); const { Session } = require('inspector'); const { MessageMedia } = require('whatsapp-web.js'); const { forEach } = require('lodash'); const cors = require('cors'); const qrcode = require('qrcode-terminal'); const app = express(); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()) const storage = multer.diskStorage({ destination: (req, file, cb)=>{ cb(null,'Csv/'); }, filename:(req, file, cb)=>{ cb(null, file.originalname); } }); const Csv = multer({ storage: storage }) const Storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, './uploads'); // The directory where uploaded files will be saved }, filename: (req, file, cb) => { cb(null, file.originalname); // Use the original file name }, }); const upload = multer({ storage: Storage }); // Initialize the WhatsApp client const client = new Client({ puppeteer: { headless: false, args: [ '--no-sandbox', '--disable-setuid-sandbox' ], } // Options for the client }); client.on('qr', (qr) => { // Generate and display the QR code in the terminal console.log('QR Code:', qr); }); client.on('ready', () => { console.log('Client is ready!'); }); client.initialize(); app.post('/send-message', Csv.single('numbers'), async (req, res) => { try { const message = req.body.message; const numbers = await promisify(fs.readFile)(req.file.path); console.log(message); console.log(numbers); let number = numbers.toString().split('\r\n'); for (let index = 0; index < number.length; index++) { let test = number[index] console.log(test); let code = await client.sendMessage('234' + test + '@c.us', message); console.log(code); } // Send the message to each number // console.log('Message sent to:', number); // res.status(200).send('Messages sent successfully!'); } catch (error) { console.error('Error:', error); res.status(500).send('Failed to send messages!'); } }); app.post('/send-media', upload.fields([{ name: 'media', maxCount: 1 }, { name: 'csv', maxCount: 1 }]), async(req, res) => { const csvFile = await promisify(fs.readFile)(req.files['csv'][0].path); const mediaFile = req.files['media'][0]; const message = req.body.message; console.log(mediaFile); console.log(message); try{ let number = csvFile.toString().split('\r\n'); console.log(number); for (let index = 0; index < number.length; index++) { let csv = number[index] const chat = await client.getChatById('234' + csv + '@c.us'); console.log(chat); const media = MessageMedia.fromFilePath(mediaFile.path); chat.sendMessage(media, { caption: message }); }; res.status(200).json({ success: true, message: 'Media sent' }) } catch (error) { res.status(500).json({ success: false, error: 'Failed to send media' }); } }); const port = process.env.PORT || 3000; app.listen(port, () => console.log(`Server is running on port ${port}`));
问题排查与解决方案
核心问题分析
线上服务器无图形界面、资源受限,与本地环境差异导致Chromium启动失败或核心逻辑无法执行,具体原因包括:
- Headless模式错误:线上无图形界面,
headless: false会导致Chromium无法启动,必须使用无头模式。 - Puppeteer启动参数不足:仅
--no-sandbox和--disable-setuid-sandbox不足以适配线上环境,缺少GPU禁用、共享内存优化等参数。 - 会话持久化失败:
LocalAuth依赖本地文件系统存储会话,线上服务器文件系统多为临时存储,重启后会话丢失,且可能无写入权限。 - 上传目录未创建:multer指定的
Csv/和./uploads目录在服务器上可能不存在,导致文件无法保存。 - 系统依赖缺失:线上服务器默认缺少Chromium运行所需的系统库(如libnss3、libatk1.0-0等)。
具体修复步骤
1. 修正Puppeteer配置
将无头模式设为新版'new',补充必要启动参数:
const client = new Client({ authStrategy: new LocalAuth(), puppeteer: { headless: 'new', // 新版无头模式,兼容性更好 args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', // 禁用GPU,适配无图形环境 '--disable-dev-shm-usage', // 避免共享内存不足 '--single-process', // 单进程模式,减少资源占用 '--no-zygote' // 禁用zygote进程,避免权限问题 ] } });
2. 确保上传目录存在
启动时自动创建所需目录,避免文件保存失败:
// 在初始化app后添加 const ensureDir = (dirPath) => { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); // 递归创建目录 } }; ensureDir('./Csv'); ensureDir('./uploads');
3. 增强错误日志捕捉
添加客户端错误事件监听,获取Chromium启动失败的具体信息:
client.on('error', (err) => { console.error('WhatsApp Client 错误:', err); }); client.on('disconnected', (reason) => { console.log('客户端断开连接:', reason); // 可选:自动重连 client.initialize(); });
4. 检查客户端就绪状态
在接口中确保客户端已就绪再处理请求,避免未初始化时调用发送逻辑:
// 全局标记客户端状态 let isClientReady = false; client.on('ready', () => { console.log('Client is ready!'); isClientReady = true; }); // 修改send-message接口 app.post('/send-message', Csv.single('numbers'), async (req, res) => { if (!isClientReady) { return res.status(503).send('客户端未就绪,请稍后重试'); } // 原有逻辑... });
5. 安装系统依赖
在Render等平台的构建命令中添加系统依赖安装步骤(以Ubuntu为例):
sudo apt-get update && sudo apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2
6. 优化会话持久化(可选)
线上环境建议改用RemoteAuth将会话存储到Redis等持久化存储,避免依赖本地文件:
// 示例:使用Redis存储会话 const { RemoteAuth } = require('whatsapp-web.js'); const Redis = require('ioredis'); const redisClient = new Redis(process.env.REDIS_URL); const client = new Client({ authStrategy: new RemoteAuth({ clientId: 'your-client-id', store: redisClient, backupSyncIntervalMs: 300000 }), // puppeteer配置... });
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

