# 从 0 部署微信小程序后端:Ubuntu + Node.js + MySQL + Nginx 实战
> 本文记录“顺手补”微信小程序从微信云开发迁移到自有服务器的完整过程。教程面向第一次接触服务器、数据库和反向代理的开发者,并集中整理真实部署中遇到的问题与解决方法。
## 一、最终架构
这套部署方案的请求链路如下:
```text
微信小程序
↓ HTTPS 请求
https://你的域名/shunshoubu/api/*
↓ Nginx 反向代理
127.0.0.1:3100
↓ Node.js + Express
MySQL 数据库
```
各部分职责:
- 微信小程序:页面展示、交互和 wx.login。
- Nginx:处理 HTTPS、域名和请求转发。
- Node.js:处理登录、家庭、补货任务等业务逻辑。
- MySQL:保存用户、家庭、成员和补货记录。
- systemd:让 Node.js 后端开机启动、异常自动重启。
本文示例环境:
- Ubuntu Server 24.04 LTS
- 2 核 CPU、2 GB 内存
- Node.js 18 或更高版本
- MySQL 8
- Nginx
- 已备案并解析到服务器的域名
## 二、部署前准备
你需要准备:
1. 一台有公网 IPv4 的 Ubuntu 服务器。
2. 一个已经解析到服务器的域名。
3. 微信小程序 AppID 和 AppSecret。
4. 可以正常运行的小程序前端代码。
5. 后端源码和数据库初始化 SQL。
安全提醒:
- AppSecret 只能保存在服务器,绝不能写进小程序前端。
- MySQL 密码不要发送到聊天、截图或 Git 仓库。
- 数据库 3306 端口不需要向公网开放。
- Node.js 服务只监听 127.0.0.1,由 Nginx 对外提供 HTTPS。
## 三、连接服务器
在 Mac 或 Linux 终端执行:
```bash
ssh ubuntu@你的服务器IP
```
推荐使用 SSH 密钥,不建议长期使用密码登录。
### 常见错误ssh-rsa: command not found
这通常是把公钥内容直接粘贴到了服务器命令行。公钥不是命令,应该写入:
```text
~/.ssh/authorized_keys
```
可以在本机使用:
```bash
ssh-copy-id ubuntu@你的服务器IP
```
或在云服务商控制台绑定密钥后重启实例。
## 四、安装运行环境
更新软件包:
```bash
sudo apt update
sudo apt upgrade -y
```
安装基础软件:
```bash
sudo apt install -y nodejs npm mysql-server nginx unzip
```
检查版本:
```bash
node -v
npm -v
mysql --version
nginx -v
```
启动并设置开机启动:
```bash
sudo systemctl enable --now mysql
sudo systemctl enable --now nginx
```
如果使用宝塔面板安装的 Nginx 或 MySQL,不要再通过 apt 重复安装另一套。先用下面的命令确认当前运行的是哪一个实例:
```bash
sudo ss -lntp | grep -E '3306|80|443'
sudo ps -ef | grep '[m]ysqld'
```
## 五、上传后端代码
本文后端目录结构如下:
```text
server/
├── package.json
├── .env.example
├── sql/
│ └── schema.sql
└── src/
└── index.js
```
在服务器创建部署目录和专用系统用户:
```bash
sudo useradd --system --home /opt/shunshoubu --shell /usr/sbin/nologin shunshoubu
sudo mkdir -p /opt/shunshoubu
sudo chown -R shunshoubu:shunshoubu /opt/shunshoubu
```
在本机项目目录上传代码:
```bash
scp -r server/* ubuntu@你的服务器IP:/tmp/shunshoubu-server/
```
回到服务器安装代码:
```bash
sudo cp -R /tmp/shunshoubu-server/. /opt/shunshoubu/
sudo chown -R shunshoubu:shunshoubu /opt/shunshoubu
cd /opt/shunshoubu
sudo -u shunshoubu npm install --omit=dev
```
## 六、创建 MySQL 数据库
“顺手补”使用五张表:
- users:微信用户。
- families:家庭。
- family_members:家庭成员关系。
- common_items:常用补货物品。
- supply_tasks:补货任务和历史记录。
使用管理员账号导入数据库结构:
```bash
mysql -uroot -p < /opt/shunshoubu/sql/schema.sql
```
进入 MySQL 后创建独立应用账户:
```sql
CREATE USER IF NOT EXISTS 'shunshoubu'@'localhost'
IDENTIFIED BY '请替换为强密码';
GRANT ALL PRIVILEGES ON shunshoubu.*
TO 'shunshoubu'@'localhost';
FLUSH PRIVILEGES;
```
应用账户只需要访问 shunshoubu 数据库,不要直接使用 root 运行后端。
### 使用宝塔面板时
可以在“数据库”页面创建:
```text
数据库名:shunshoubu
用户名:shunshoubu
访问权限:本地服务器
密码:随机强密码
```
如果数据库已经存在,不要重复删除数据库。只需要确保用户密码、权限和后端 .env 一致。
## 七、配置后端环境变量
复制示例配置:
```bash
sudo cp /opt/shunshoubu/.env.example /opt/shunshoubu/.env
sudo chmod 600 /opt/shunshoubu/.env
sudo chown shunshoubu:shunshoubu /opt/shunshoubu/.env
sudo nano /opt/shunshoubu/.env
```
填写:
```ini
PORT=3100
HOST=127.0.0.1
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=shunshoubu
MYSQL_PASSWORD=你的应用数据库密码
MYSQL_DATABASE=shunshoubu
WECHAT_APP_ID=你的小程序AppID
WECHAT_APP_SECRET=你的小程序AppSecret
SERVER_SESSION_SECRET=一串足够长的随机字符
```
生成会话密钥:
```bash
openssl rand -hex 32
```
注意MYSQL_PASSWORD 必须与 MySQL 中 shunshoubu@localhost 的密码完全一致。
先单独测试数据库连接:
```bash
sudo bash -c 'set -a; source /opt/shunshoubu/.env; MYSQL_PWD="$MYSQL_PASSWORD" mysql -h127.0.0.1 -u"$MYSQL_USER" "$MYSQL_DATABASE" -e "SELECT 1;"'
```
正确输出:
```text
+---+
| 1 |
+---+
| 1 |
+---+
```
只有这个测试通过,才继续启动后端。
## 八、使用 systemd 常驻运行
创建服务文件:
```bash
sudo nano /etc/systemd/system/shunshoubu.service
```
填写:
```ini
[Unit]
Description=Shunshoubu WeChat Mini Program API
After=network.target mysql.service
[Service]
Type=simple
User=shunshoubu
Group=shunshoubu
WorkingDirectory=/opt/shunshoubu
EnvironmentFile=/opt/shunshoubu/.env
ExecStart=/usr/bin/node /opt/shunshoubu/src/index.js
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
```
加载并启动:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now shunshoubu
sudo systemctl status shunshoubu --no-pager
```
看到 active (running) 表示服务已启动。
检查本机健康接口:
```bash
curl -sS http://127.0.0.1:3100/health
```
正确结果:
```json
{"ok":true,"service":"shunshoubu-server"}
```
查看日志:
```bash
sudo journalctl -u shunshoubu -f
```
## 九、配置 Nginx 反向代理
后端监听 127.0.0.1:3100,Nginx 将外部路径 /shunshoubu/ 转发到它。
在域名对应的 Nginx 配置中加入:
```nginx
location /shunshoubu/ {
proxy_pass http://127.0.0.1:3100/;
proxy_http_version 1.1;
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;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
```
这里最容易写错的是 proxy_pass 末尾的 /。按上面的写法:
```text
/shunshoubu/api/login
```
会被正确转发为:
```text
/api/login
```
检查并重载:
```bash
sudo nginx -t
sudo systemctl reload nginx
```
公网测试:
```bash
curl -sS https://你的域名/shunshoubu/health
```
## 十、配置 HTTPS
微信小程序正式环境必须使用可信 HTTPS。
可以使用宝塔面板申请 Let's Encrypt 证书,也可以使用 Certbot:
```bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d 你的域名
```
申请后再次测试:
```bash
curl -I https://你的域名/shunshoubu/health
```
不要使用自签名证书,也不要忽略证书过期问题。
## 十一、小程序端配置
前端配置文件:
```js
module.exports = {
USE_SERVER: true,
API_BASE_URL: "https://你的域名/shunshoubu",
USE_CLOUD: true
}
```
在微信公众平台进入:
```text
开发管理 → 开发设置 → 服务器域名
```
在 request 合法域名 中填写:
```text
https://你的域名
```
注意:这里只填域名,不能填写 /shunshoubu 路径。
正式测试时应开启合法域名校验,不要长期依赖开发者工具的“不校验合法域名”选项。
## 十二、登录流程原理
微信登录过程如下:
1. 小程序调用 wx.login() 获取临时 code。
2. 小程序把 code 发送到后端 /api/login。
3. 后端使用 AppID、AppSecret 和 code 请求微信 jscode2session。
4. 微信返回用户的 openid。
5. 后端把用户写入 MySQL,并生成 JWT 登录令牌。
6. 小程序保存令牌,后续请求通过 Authorization: Bearer ... 鉴权。
AppSecret 全程只存在服务器端。
## 十三、完整验收流程
部署完成后,不要只看健康接口。至少完成以下测试:
1. 首次打开自动登录。
2. 创建家庭并生成邀请码。
3. 退出家庭。
4. 第二个微信账号通过邀请码加入。
5. 添加补货任务。
6. 第二个账号认领任务。
7. 取消认领。
8. 再次认领并标记已买到。
9. 历史记录正常显示。
10. 完全退出小程序后重新打开,登录和家庭仍然存在。
## 十四、真实故障排查记录
### 1. 健康接口正常,但小程序一直显示“连接超时”
最初容易误判为域名、IPv6 或微信接口故障。实际应先打开微信开发者工具 Console,查看真正的 HTTP 状态和后端错误。
本次真实错误是:
```text
POST /shunshoubu/api/login 500
Access denied for user 'shunshoubu'@'localhost' (using password: YES)
```
结论:Nginx 和 Node.js 都正常,后端在访问 MySQL 时认证失败。
修复原则:
1. 确认 .env 中的 MYSQL_USER。
2. 确认 MySQL 中存在相同的 user@host。
3. 重新设置数据库用户密码。
4. 给该用户授权目标数据库。
5. 让 .env 密码与 MySQL 密码完全一致。
6. 先运行 SELECT 1 测试,再重启后端。
### 2. Operation ALTER USER failed for 'shunshoubu'@'localhost'
原因:要修改的账号不存在,或者存在的是另一个 Host,例如 %127.0.0.1。
正确顺序:
```sql
CREATE USER IF NOT EXISTS 'shunshoubu'@'localhost'
IDENTIFIED BY '新密码';
ALTER USER 'shunshoubu'@'localhost'
IDENTIFIED BY '新密码';
GRANT ALL PRIVILEGES ON shunshoubu.*
TO 'shunshoubu'@'localhost';
FLUSH PRIVILEGES;
```
### 3. root@localhost Access denied
原因可能包括:
- 输入的是服务器密码,而不是 MySQL root 密码。
- 宝塔记录的 root 密码与实际 MySQL 不一致。
- 服务器同时安装了两套 MySQL,面板管理的不是当前 3306 实例。
- root 账号的 Host 或认证插件不同。
先确认实例:
```bash
sudo ss -lntp | grep 3306
sudo ps -ef | grep '[m]ysqld'
```
宝塔环境中可通过“数据库 → root密码”重置,再使用:
```bash
mysql -h127.0.0.1 -P3306 -uroot -p
```
### 4. phpMyAdmin 返回 #1227 CREATE USER privilege
这说明 phpMyAdmin 登录的是普通应用账户,不是 root。普通账户不能创建或删除 MySQL 用户。
解决方法:退出 phpMyAdmin,使用 MySQL root 登录后再执行用户管理 SQL;或者直接使用宝塔数据库管理页面修改应用用户密码和权限。
### 5. Nginx 日志中没有 /api/login
执行:
```bash
sudo grep '/shunshoubu/api/login' /var/log/nginx/access.log | tail
```
如果完全没有输出,说明请求根本没到 Nginx,应检查:
- 小程序 API_BASE_URL。
- 微信 request 合法域名。
- 本机 DNS 和网络。
- 开发者工具代理设置。
- 是否打开了正确的项目目录和 AppID。
如果有请求记录:
- 200:后端正常。
- 400/401:参数、登录 code 或认证问题。
- 500:后端代码或数据库错误。
- 499/504:客户端取消或上游超时。
### 6. IPv4 正常,IPv6 失败
分别测试:
```bash
curl -4 -sS --max-time 10 https://你的域名/shunshoubu/health
curl -6 -sS --max-time 10 https://你的域名/shunshoubu/health
```
如果 DNS 配置了 AAAA,但 IPv6 服务没有正确监听,部分客户端可能优先走 IPv6 并超时。可以完善 IPv6 防火墙和 Nginx 配置,或在暂不使用 IPv6 时删除错误的 AAAA 记录。
### 7. systemd 显示运行,但接口仍不可用
检查最近日志:
```bash
sudo journalctl -u shunshoubu --since "10 minutes ago" --no-pager
```
检查端口:
```bash
sudo ss -lntp | grep 3100
```
检查本机接口:
```bash
curl -v http://127.0.0.1:3100/health
```
如果本机接口正常而公网失败,问题通常在 Nginx、HTTPS、安全组或域名解析。
## 十五、上线前安全检查
- .env 权限设为 600。
- AppSecret 不进入 Git。
- 不向公网开放 MySQL 3306。
- Node.js 只监听 127.0.0.1。
- 使用独立 MySQL 账户,不使用 root。
- JWT 密钥使用随机长字符串。
- 定期备份数据库。
- 删除生产页面中的调试按钮和内部错误详情。
- 上传小程序时排除 server/cloudfunctions/.env、SQL 和部署脚本。
- 开启 HTTPS 证书自动续期。
数据库备份示例:
```bash
mysqldump -h127.0.0.1 -ushunshoubu -p shunshoubu \
| gzip > "shunshoubu-$(date +%F).sql.gz"
```
## 十六、发布小程序
完成真机验收后:
1. 微信开发者工具点击“上传”。
2. 填写版本号,例如 1.1.0。
3. 微信公众平台进入“版本管理”。
4. 先设为体验版,用两个微信账号测试。
5. 提交审核。
6. 审核通过后发布。
已经上线的旧版本不会自动使用本地新代码,必须重新上传、审核和发布。
## 总结
部署微信小程序自有后端时,最重要的不是一次写完所有配置,而是按请求链路逐层验证:
```text
MySQL SELECT 1
↓
Node.js 127.0.0.1 健康接口
↓
Nginx HTTPS 公网健康接口
↓
微信 wx.login
↓
创建家庭和业务写入
```
每一层通过后再检查下一层。这样即使页面只显示“连接超时”,也能快速定位到底是小程序、Nginx、Node.js、微信接口还是 MySQL 出了问题。
评论