Skip to main content

Node.js/Express 服务器配置

本指南介绍如何使用 Node.js 和 Express 框架部署 H5 应用,并配置微信小程序域名校验文件的访问。

推荐架构

在生产环境中,推荐使用 Nginx 作为反向代理处理 HTTPS,Node.js 应用监听 HTTP 端口(如 3000)。这样可以让 Nginx 处理 SSL、负载均衡等,Node.js 专注于应用逻辑。

典型架构: 云平台 HTTPS → Nginx (80/443) → Node.js (3000)

前提条件

  • 已安装 Node.js(建议版本 18+)
  • 已安装 npm 或 yarn
  • 已下载微信域名校验文件(如 a1b2c3d4e5f6.txt
  • H5 应用已构建完成

校验文件放置方案

与 Nginx 类似,您可以选择两种方案:

方案一:放到 H5 项目根目录(推荐)

将校验文件直接放在 H5 应用的静态资源目录中。

目录结构:

project/
├── public/
│ ├── a1b2c3d4e5f6.txt ← 校验文件(微信生成的随机文件名)
│ ├── index.html
│ └── assets/
│ ├── js/
│ ├── css/
│ └── images/
├── package.json
└── server.js

优点:

  • 配置简单,无需额外路由
  • 适合大多数场景

缺点:

  • 校验文件与业务代码混在一起

方案二:单独目录存放

将校验文件放在专门的目录中。

目录结构:

project/
├── weixin-verify/
│ └── a1b2c3d4e5f6.txt ← 校验文件(微信生成的随机文件名)
├── public/
│ ├── index.html
│ └── assets/
│ ├── js/
│ ├── css/
│ └── images/
├── package.json
└── server.js

优点:

  • 结构清晰,便于管理
  • 校验文件与业务代码分离

缺点:

  • 需要配置额外的路由

Express 配置

方案一配置(校验文件在项目根目录)

推荐用于: 配合 Nginx 反向代理使用

const express = require('express');
const path = require('path');

const app = express();

// 静态文件目录(包含校验文件和 H5 页面)
app.use(express.static(path.join(__dirname, 'public')));

// SPA 路由回退
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 启动 HTTP 服务器(由 Nginx 处理 HTTPS)
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
配合 Nginx 使用

如果使用 Nginx 作为反向代理,Node.js 只需监听 HTTP 端口。Nginx 配置示例:

server {
listen 443 ssl http2;
server_name h5.example.com;

# SSL 由云平台或 Nginx 处理

location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

方案二配置(校验文件单独存放)

const express = require('express');
const path = require('path');

const app = express();

// 微信校验文件专用路径(优先匹配 .txt 文件)
// 注意:必须放在其他静态资源之前
app.use('/*.txt', express.static(path.join(__dirname, 'weixin-verify')));

// H5 页面静态资源
app.use(express.static(path.join(__dirname, 'public')));

// SPA 路由回退
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 启动 HTTP 服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
注意
  • app.use('/*.txt', ...) 确保所有根路径下的 .txt 请求优先匹配到校验文件目录
  • 路由顺序很重要:校验文件路由 → 静态资源 → SPA 路由
  • 必须放在 app.get('*', ...) 之前,否则会被 SPA 路由拦截
  • 此配置只匹配根路径的 .txt 文件(如 /xxx.txt),不影响子目录的 .txt 文件

完整示例项目

package.json

{
"name": "h5-miniprogram-server",
"version": "1.0.0",
"description": "H5 server for WeChat MiniProgram WebView",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"helmet": "^7.1.0",
"compression": "^1.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}

server.js(生产环境增强版)

const express = require('express');
const path = require('path');
const helmet = require('helmet');
const compression = require('compression');

const app = express();

// 安全头
app.use(helmet({
contentSecurityPolicy: false, // 根据实际需求配置
}));

// Gzip 压缩
app.use(compression());

// 日志中间件
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
next();
});

// 微信校验文件路由(方案二,如使用方案一可删除此段)
app.use('/*.txt', express.static(path.join(__dirname, 'weixin-verify')));

// 静态资源配置
app.use(express.static(path.join(__dirname, 'public'), {
maxAge: '1y', // 缓存 1 年
etag: true,
lastModified: true,
setHeaders: (res, filePath) => {
// HTML 文件不缓存
if (filePath.endsWith('.html')) {
res.setHeader('Cache-Control', 'no-cache');
}
}
}));

// 健康检查端点
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: Date.now() });
});

// SPA 路由回退
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 错误处理中间件
app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(500).json({ error: 'Internal Server Error' });
});

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`✅ Server running on port ${PORT}`);
});

使用环境变量

创建 .env 文件(推荐使用 dotenv 包):

# .env
PORT=3000
NODE_ENV=production
// 在 server.js 顶部添加
require('dotenv').config();

部署步骤

1. 初始化项目

# 创建项目目录
mkdir h5-miniprogram-server
cd h5-miniprogram-server

# 初始化 package.json
npm init -y

# 安装依赖
npm install express helmet compression
npm install --save-dev nodemon

2. 准备文件

# 创建目录(方案一)
mkdir public

# 或(方案二)
mkdir public weixin-verify

# 将 H5 构建产物复制到 public/
# 将校验文件复制到对应目录

3. 启动服务

# 开发环境
npm run dev

# 生产环境
npm start

使用 PM2 管理进程

安装 PM2

npm install -g pm2

PM2 配置文件

创建 ecosystem.config.js

module.exports = {
apps: [{
name: 'h5-server',
script: './server.js',
instances: 'max', // 使用所有 CPU 核心
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
autorestart: true,
max_memory_restart: '1G'
}]
};

PM2 命令

# 启动应用
pm2 start ecosystem.config.js

# 查看状态
pm2 status

# 查看日志
pm2 logs h5-server

# 重启
pm2 restart h5-server

# 停止
pm2 stop h5-server

# 设置开机自启
pm2 startup
pm2 save

验证配置

验证校验文件可访问

# 使用 curl 测试(替换为实际文件名)
curl https://h5.example.com/a1b2c3d4e5f6.txt

# 查看详细响应
curl -I https://h5.example.com/a1b2c3d4e5f6.txt

# 预期输出:
# HTTP/2 200
# content-type: text/plain

验证 H5 页面可访问

# 测试主页
curl -I https://h5.example.com

# 预期返回 200 状态码

验证 HTTPS 重定向

# 测试 HTTP 是否自动跳转到 HTTPS
curl -I http://h5.example.com

# 预期输出:
# HTTP/1.1 301 Moved Permanently
# Location: https://h5.example.com/

常见问题

端口权限问题

在 Linux 上,非 root 用户无法监听 80 和 443 端口。

推荐方案:使用 Nginx 反向代理

Node.js 监听 3000 端口,Nginx 监听 80/443 并转发到 3000。参考前面的 Nginx 配置示例。

备选方案:使用 authbind

# 安装 authbind
sudo apt-get install authbind

# 允许 Node.js 使用 80 和 443 端口
sudo touch /etc/authbind/byport/80
sudo touch /etc/authbind/byport/443
sudo chmod 500 /etc/authbind/byport/80
sudo chmod 500 /etc/authbind/byport/443
sudo chown youruser /etc/authbind/byport/80
sudo chown youruser /etc/authbind/byport/443

# 使用 authbind 启动
authbind --deep node server.js

文件找不到

检查路径:

// 添加调试日志
console.log('__dirname:', __dirname);
console.log('Public path:', path.join(__dirname, 'public'));

// 检查文件是否存在
const fs = require('fs');
const publicPath = path.join(__dirname, 'public');
console.log('Files in public:', fs.readdirSync(publicPath));

相关资源