19 KiB
星遇社交平台:宝塔非 Docker 生产环境搭建说明
本文适用于在宝塔 Linux 面板中,以原生进程方式部署本项目,不使用 Docker。
部署后的结构如下:
app.example.com
-> 宝塔 Nginx
-> uni-app H5 静态文件
admin.example.com
-> 宝塔 Nginx
-> Vben Admin 静态文件
-> /admin/v1/* 反向代理到 127.0.0.1:18888
api.example.com
-> 宝塔 Nginx
-> HTTP API、上传文件及 WebSocket
-> 反向代理到 127.0.0.1:18888
127.0.0.1:18888
-> Go 后端 systemd 服务
-> 宝塔 MySQL
为什么使用
18888:宝塔面板常使用8888,原生部署时后端不能再占用同一个宿主机端口。后端只监听127.0.0.1,不向公网开放。
1. 部署前准备
1.1 推荐配置
- Ubuntu 24.04 或 Debian 12
- 最低 2 核 4 GB,建议 4 核 8 GB
- 50 GB 以上磁盘
- 宝塔 Linux 面板
- Nginx 1.24 或更高版本
- MySQL 8.0/8.4
- Go 1.27
- Node.js 22.18 或更高版本(推荐 Node.js 22 LTS)
- pnpm 10.33.4
项目后端的 go.mod 要求 Go 1.27;管理端要求 Node.js ^22.18.0 || ^24.0.0 和 pnpm 10 或更高版本。不要使用宝塔中较旧的 Node.js 18/20 构建管理端。
1.2 域名
准备三个域名并添加 A 记录:
app.example.com -> 服务器公网 IP
admin.example.com -> 服务器公网 IP
api.example.com -> 服务器公网 IP
云服务器安全组和宝塔防火墙只需要放行:
22 SSH
80 HTTP/证书验证
443 HTTPS/WSS
不要向公网开放 MySQL 3306 和后端 18888。
1.3 规划目录
/www/wwwroot/xingyu-source/ 项目源代码
/www/wwwroot/xingyu-app/ uni-app H5 发布目录
/www/wwwroot/xingyu-admin/ 管理端发布目录
/www/wwwroot/xingyu-api-empty/ API 域名空站点目录
/www/wwwroot/xingyu-data/uploads/ 本地文件存储目录
/www/server/xingyu-im/ 后端二进制和迁移脚本
/etc/xingyu-im.env 后端生产环境变量
2. 在宝塔安装基础环境
在“软件商店”安装:
- Nginx。
- MySQL 8.0 或 8.4。
- Node 版本管理器。
不需要安装 PHP、PM2、Docker、Redis。Go 后端由 systemd 守护,两个前端都是静态文件。
进入:
网站 -> Node 项目 -> Node 版本管理器
安装 Node.js 22.18 以上版本,并设为“命令行版本”。然后在宝塔终端验证:
node --version
npm --version
npm install --global pnpm@10.33.4
pnpm --version
Node.js 必须满足项目声明的版本要求,建议使用当前 Node.js 22 LTS。
3. 安装 Go 1.27
先检查:
go version
uname -m
如果已经是 Go 1.27,可跳过本节。以下命令适用于 x86_64 服务器:
cd /tmp
curl -fLO https://go.dev/dl/go1.27.0.linux-amd64.tar.gz
echo '675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685 go1.27.0.linux-amd64.tar.gz' | sha256sum -c -
install -d /opt/go1.27.0
tar -C /opt/go1.27.0 --strip-components=1 -xzf go1.27.0.linux-amd64.tar.gz
ln -s /opt/go1.27.0/bin/go /usr/local/bin/go
ln -s /opt/go1.27.0/bin/gofmt /usr/local/bin/gofmt
go version
如果 /usr/local/bin/go 已存在,不要直接覆盖;先确认现有 Go 的安装方式和用途。ARM64 服务器应从 Go 官方下载页选择 linux-arm64 文件并校验对应 SHA256。
4. 上传源代码
在宝塔“文件”中将项目上传并解压为:
/www/wwwroot/xingyu-source/
├── admin/
├── backend/
├── mobile/
├── deploy/
└── README.md
确认不是下面这种双层目录:
/www/wwwroot/xingyu-source/im/backend/
创建运行目录:
install -d -o www -g www -m 0755 /www/wwwroot/xingyu-app
install -d -o www -g www -m 0755 /www/wwwroot/xingyu-admin
install -d -o www -g www -m 0755 /www/wwwroot/xingyu-api-empty
install -d -o www -g www -m 0750 /www/wwwroot/xingyu-data/uploads
install -d -o root -g root -m 0755 /www/server/xingyu-im
5. 创建 MySQL 数据库
进入宝塔“数据库”,创建:
数据库名:im
用户名:xingyu_app
密码:使用 openssl rand -hex 32 生成
字符集:utf8mb4
访问权限:本地服务器
不要让后端使用 MySQL root 账号。生产配置会主动拒绝 root DSN。
生成数据库密码:
openssl rand -hex 32
数据库密码建议使用十六进制随机字符串,避免 @、: 等字符破坏 MySQL DSN 格式。
6. 执行数据库迁移
本项目包含 001 到 026 的顺序迁移。非 Docker 环境可以创建下面的迁移脚本:
nano /www/server/xingyu-im/migrate.sh
脚本内容:
#!/usr/bin/env bash
set -euo pipefail
mysql_cli=/www/server/mysql/bin/mysql
database_host=127.0.0.1
database_name=im
database_user=xingyu_app
migration_dir=/www/wwwroot/xingyu-source/backend/migrations
if [ ! -x "$mysql_cli" ]; then
mysql_cli="$(command -v mysql)"
fi
if [ -z "${MYSQL_PWD:-}" ]; then
echo '请先通过 MYSQL_PWD 环境变量提供数据库密码' >&2
exit 1
fi
mysql_args=(
--host="$database_host"
--user="$database_user"
--database="$database_name"
--default-character-set=utf8mb4
--batch
--skip-column-names
)
"$mysql_cli" "${mysql_args[@]}" --execute "
CREATE TABLE IF NOT EXISTS schema_migrations (
version VARCHAR(255) NOT NULL PRIMARY KEY,
checksum CHAR(64) NOT NULL,
applied_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"
for migration_file in "$migration_dir"/*.sql; do
migration_name="$(basename "$migration_file")"
migration_checksum="$(sha256sum "$migration_file" | awk '{print $1}')"
stored_checksum="$("$mysql_cli" "${mysql_args[@]}" --execute "SELECT checksum FROM schema_migrations WHERE version='${migration_name}'" || true)"
if [ -n "$stored_checksum" ]; then
if [ "$stored_checksum" != "$migration_checksum" ]; then
echo "迁移校验和不一致:$migration_name" >&2
exit 1
fi
echo "已执行:$migration_name"
continue
fi
echo "正在执行:$migration_name"
"$mysql_cli" "${mysql_args[@]}" < "$migration_file"
"$mysql_cli" "${mysql_args[@]}" --execute "INSERT INTO schema_migrations(version, checksum) VALUES('${migration_name}', '${migration_checksum}')"
done
设置权限并执行:
chmod 0750 /www/server/xingyu-im/migrate.sh
export MYSQL_PWD='替换为数据库密码'
/www/server/xingyu-im/migrate.sh
unset MYSQL_PWD
正常情况下会依次显示 001_users.sql 到 026_report_operations.sql。已经登记过且校验和一致的迁移会跳过。
不要修改已经在生产数据库执行过的迁移文件;后续数据库变更应该新增编号更大的迁移。
7. 构建 uni-app H5
客户端 API 域名和 WSS 域名会在构建时写入产物。必须使用正式域名构建:
cd /www/wwwroot/xingyu-source/mobile
corepack enable
pnpm install --frozen-lockfile
VITE_API_ORIGIN=https://api.example.com \
VITE_WS_ORIGIN=wss://api.example.com \
VITE_APP_VERSION=1.0.0 \
pnpm build:h5
发布静态文件:
rsync -a --delete \
/www/wwwroot/xingyu-source/mobile/dist/build/h5/ \
/www/wwwroot/xingyu-app/
chown -R www:www /www/wwwroot/xingyu-app
rsync --delete 只会清理 /www/wwwroot/xingyu-app/ 中上一版本遗留的静态文件,不会操作源代码和用户上传目录。
域名修改后必须重新构建 H5,单纯修改 Nginx 不会更新已经写入 JavaScript 的 API/WSS 地址。
8. 构建 Vben 管理端
管理端生产配置使用同域名 /admin/v1,由宝塔 Nginx 转发到后端。
管理端 build 接口地址位于 admin/.env.production。如需使用独立 API 域名,编辑:
nano /www/wwwroot/xingyu-source/admin/.env.production
然后将接口地址设为:
VITE_GLOB_API_URL=https://api.example.com/admin/v1
保持同域名反向代理时无需修改,默认值就是 /admin/v1。开发接口地址位于 admin/.env.development,不会影响生产构建。
先将 admin/apps/web-ele/.env 中的下面一项改为独立随机值:
VITE_APP_STORE_SECURE_KEY=替换为独立随机字符串
该值可以使用 openssl rand -hex 32 生成。它会进入前端产物,不能替代服务端密钥,但不应继续使用示例值。
执行构建:
cd /www/wwwroot/xingyu-source/admin
pnpm install --frozen-lockfile
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @vben/web-ele build
发布静态文件:
rsync -a --delete \
/www/wwwroot/xingyu-source/admin/apps/web-ele/dist/ \
/www/wwwroot/xingyu-admin/
chown -R www:www /www/wwwroot/xingyu-admin
9. 构建和安装 Go 后端
先运行测试:
cd /www/wwwroot/xingyu-source/backend
go mod download
go test ./...
测试通过后构建静态二进制:
CGO_ENABLED=0 go build \
-trimpath \
-ldflags='-s -w' \
-o /tmp/xingyu-api \
./cmd/server
install -o root -g root -m 0755 \
/tmp/xingyu-api \
/www/server/xingyu-im/xingyu-api
生产运行不依赖 Go、Node.js 或 pnpm;它们只在构建和更新时使用。
10. 配置后端生产环境变量
生成三组不同的密钥:
openssl rand -hex 48
openssl rand -hex 48
openssl rand -hex 32
前两个分别用于 JWT 和平台配置加密,第三个可作为初始管理员密码随机部分。IM_CONFIG_ENCRYPTION_KEY 上线后必须永久、安全保存,不能随意更换。
创建配置文件:
nano /etc/xingyu-im.env
内容如下:
IM_ENV=production
IM_HOST=127.0.0.1
IM_PORT=18888
IM_DB_DSN=xingyu_app:替换为数据库密码@tcp(127.0.0.1:3306)/im?charset=utf8mb4&parseTime=True&loc=Local
IM_JWT_SECRET=替换为至少32字节的独立随机密钥
IM_CONFIG_ENCRYPTION_KEY=替换为另一个至少32字节的独立随机密钥
IM_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
IM_SEED_DEMO=false
IM_MEDIA_DIR=/www/wwwroot/xingyu-data/uploads
IM_BOOTSTRAP_ADMIN_USERNAME=admin
IM_BOOTSTRAP_ADMIN_PASSWORD=替换为至少12位且包含大小写字母数字和特殊字符的密码
IM_BOOTSTRAP_ADMIN_REAL_NAME=平台管理员
TZ=Asia/Shanghai
保护配置文件:
chown root:root /etc/xingyu-im.env
chmod 0600 /etc/xingyu-im.env
生产环境要求:
- JWT 密钥至少 32 字节。
- 配置加密密钥至少 32 字节。
- CORS 来源必须是明确的 HTTPS 地址,不能使用
*。 - 数据库账号不能是
root。 - 初始管理员密码至少 12 位,且包含大小写字母、数字和特殊字符。
- 禁止开启演示数据。
11. 创建 systemd 服务
创建:
nano /etc/systemd/system/xingyu-im.service
内容如下:
[Unit]
Description=Xingyu Social Platform API
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=www
Group=www
WorkingDirectory=/www/server/xingyu-im
EnvironmentFile=/etc/xingyu-im.env
ExecStart=/www/server/xingyu-im/xingyu-api
Restart=always
RestartSec=3
TimeoutStopSec=30
LimitNOFILE=65535
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/www/wwwroot/xingyu-data/uploads
[Install]
WantedBy=multi-user.target
启动服务:
systemctl daemon-reload
systemctl enable --now xingyu-im
systemctl status xingyu-im --no-pager
查看日志:
journalctl -u xingyu-im -n 200 --no-pager
journalctl -u xingyu-im -f
本机验证:
curl -fsS http://127.0.0.1:18888/healthz
ss -lntp | grep 18888
应该只能看到 127.0.0.1:18888,不能看到 0.0.0.0:18888。
首次启动成功并登录管理端修改密码后,可以清空 /etc/xingyu-im.env 中的:
IM_BOOTSTRAP_ADMIN_PASSWORD=
然后执行:
systemctl restart xingyu-im
12. 在宝塔创建三个网站
12.1 H5 网站
进入“网站”,新增 HTML 静态网站:
域名:app.example.com
根目录:/www/wwwroot/xingyu-app
PHP:纯静态
在站点 Nginx 配置的 server 内确保包含:
location = /index.html {
add_header Cache-Control "no-store";
}
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ {
expires 7d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
12.2 管理端网站
新增 HTML 静态网站:
域名:admin.example.com
根目录:/www/wwwroot/xingyu-admin
PHP:纯静态
在该站点 server 内加入或替换相应 location:
location ^~ /admin/v1/ {
proxy_pass http://127.0.0.1:18888;
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 60s;
}
location = /_app.config.js {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
try_files $uri =404;
}
location = /index.html {
add_header Cache-Control "no-store";
}
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ {
expires 7d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
/_app.config.js 保存管理端运行时接口地址,必须禁止浏览器和 CDN 缓存;否则修改接口域名后客户端可能继续请求旧地址。
12.3 API 和 WebSocket 网站
新增网站:
域名:api.example.com
根目录:/www/wwwroot/xingyu-api-empty
PHP:纯静态
在该站点 server 内加入:
client_max_body_size 16m;
location ^~ /ws {
proxy_pass http://127.0.0.1:18888;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 70s;
proxy_send_timeout 70s;
proxy_buffering off;
}
location / {
proxy_pass http://127.0.0.1:18888;
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 60s;
}
保存配置前点击宝塔的配置检查,或在终端执行:
nginx -t
检查成功后重载:
/etc/init.d/nginx reload
不要在宝塔中再为 API 域名创建另一条普通“反向代理”,避免宝塔生成的 location / 与上面的 WebSocket 配置冲突。
13. 配置 HTTPS
依次打开三个网站:
网站设置 -> SSL -> Let's Encrypt
分别申请证书并开启强制 HTTPS。宝塔负责证书文件和自动续签。
证书完成后验证:
curl -fsS https://api.example.com/healthz
客户端必须使用:
https://api.example.com
wss://api.example.com/ws
不能在生产页面中连接 http:// 或 ws://,否则浏览器会拦截混合内容,消息传输也没有 TLS 保护。
14. 上线验收
14.1 服务状态
systemctl is-active xingyu-im
curl -fsS http://127.0.0.1:18888/healthz
curl -fsS https://api.example.com/healthz
14.2 页面
https://app.example.comhttps://admin.example.comhttps://api.example.com/healthz
14.3 核心功能
至少完成以下测试:
- 用户注册、密码登录、验证码登录和找回密码。
- H5 图片上传、头像修改和动态发布。
- WebSocket 连接、消息发送、接收、历史消息和未读数。
- 管理端登录与管理员密码修改。
- 管理端用户、会员、订单、认证、违规、动态和消息记录查询。
- 短信厂商配置保存、切换和发送测试。
- 文件存储厂商配置保存、切换和上传测试。
- 第三方登录授权、回调和账号绑定。
- 支付下单、异步通知、订单状态和退款流程。
生产环境不能继续使用 debug 短信或 sandbox 支付。第三方厂商的回调地址必须填写正式 HTTPS 域名。
15. 日常更新流程
每次发布前先备份数据库、上传目录和 /etc/xingyu-im.env。
推荐顺序:
export MYSQL_PWD='替换为数据库密码'
/www/server/xingyu-im/migrate.sh
unset MYSQL_PWD
cd /www/wwwroot/xingyu-source/backend
go test ./...
CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/xingyu-api ./cmd/server
install -o root -g root -m 0755 /tmp/xingyu-api /www/server/xingyu-im/xingyu-api
systemctl restart xingyu-im
systemctl status xingyu-im --no-pager
然后按第 7、8 节重新构建并发布两个前端。
如后端启动失败,立即查看:
journalctl -u xingyu-im -n 200 --no-pager
不要在未确认数据库迁移兼容性的情况下直接覆盖生产版本。
16. 备份要求
至少备份:
MySQL 数据库 im
/www/wwwroot/xingyu-data/uploads
/etc/xingyu-im.env
/www/server/xingyu-im/xingyu-api
宝塔三个站点的 Nginx 配置和 SSL 证书
建议:
- MySQL 每天备份,保留 7~30 天。
- 上传目录每天增量备份。
- 备份复制到另一台服务器或对象存储,不能只留在本机。
- 定期执行恢复演练,而不是只检查“备份任务成功”。
/etc/xingyu-im.env包含敏感密钥,备份文件必须加密并限制访问。
17. 常见故障
后端启动失败
systemctl status xingyu-im --no-pager
journalctl -u xingyu-im -n 200 --no-pager
常见原因:数据库未迁移、密钥不足 32 字节、管理员密码强度不足、使用 root DSN、CORS 使用 HTTP 或 *。
管理端接口返回 404
检查管理端站点是否存在 /admin/v1/ 反向代理,以及 proxy_pass 是否为:
proxy_pass http://127.0.0.1:18888;
H5 仍然连接本机地址
说明 H5 构建时没有设置生产变量。按第 7 节重新构建,不能只修改 Nginx。
WebSocket 连接失败
检查:
- API 域名证书是否有效。
- H5 是否使用
wss://api.example.com。 /ws是否设置 Upgrade/Connection 请求头。/etc/xingyu-im.env是否允许 H5 域名来源。- 服务器时间是否准确。
上传失败
检查:
ls -ld /www/wwwroot/xingyu-data/uploads
sudo -u www test -w /www/wwwroot/xingyu-data/uploads
同时确认 API 站点配置了:
client_max_body_size 16m;
Node 构建内存不足
管理端构建建议至少提供 4 GB 可用内存。可以设置:
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @vben/web-ele build
如果仍然被系统杀死,应增加内存或临时交换空间,不要不断重试导致服务器失去响应。