Files
kefu/im/deploy/宝塔非Docker环境搭建说明.md
2026-09-03 08:38:17 +08:00

19 KiB
Raw Permalink Blame History

星遇社交平台:宝塔非 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. 在宝塔安装基础环境

在“软件商店”安装:

  1. Nginx。
  2. MySQL 8.0 或 8.4。
  3. 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.com
  • https://admin.example.com
  • https://api.example.com/healthz

14.3 核心功能

至少完成以下测试:

  1. 用户注册、密码登录、验证码登录和找回密码。
  2. H5 图片上传、头像修改和动态发布。
  3. WebSocket 连接、消息发送、接收、历史消息和未读数。
  4. 管理端登录与管理员密码修改。
  5. 管理端用户、会员、订单、认证、违规、动态和消息记录查询。
  6. 短信厂商配置保存、切换和发送测试。
  7. 文件存储厂商配置保存、切换和上传测试。
  8. 第三方登录授权、回调和账号绑定。
  9. 支付下单、异步通知、订单状态和退款流程。

生产环境不能继续使用 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

如果仍然被系统杀死,应增加内存或临时交换空间,不要不断重试导致服务器失去响应。

18. 官方资料