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

789 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 星遇社交平台:宝塔非 Docker 生产环境搭建说明
本文适用于在宝塔 Linux 面板中,以原生进程方式部署本项目,不使用 Docker。
部署后的结构如下:
```text
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 记录:
```text
app.example.com -> 服务器公网 IP
admin.example.com -> 服务器公网 IP
api.example.com -> 服务器公网 IP
```
云服务器安全组和宝塔防火墙只需要放行:
```text
22 SSH
80 HTTP/证书验证
443 HTTPS/WSS
```
不要向公网开放 MySQL `3306` 和后端 `18888`。
### 1.3 规划目录
```text
/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 守护,两个前端都是静态文件。
进入:
```text
网站 -> Node 项目 -> Node 版本管理器
```
安装 Node.js 22.18 以上版本,并设为“命令行版本”。然后在宝塔终端验证:
```bash
node --version
npm --version
npm install --global pnpm@10.33.4
pnpm --version
```
Node.js 必须满足项目声明的版本要求,建议使用当前 Node.js 22 LTS。
## 3. 安装 Go 1.27
先检查:
```bash
go version
uname -m
```
如果已经是 Go 1.27,可跳过本节。以下命令适用于 `x86_64` 服务器:
```bash
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. 上传源代码
在宝塔“文件”中将项目上传并解压为:
```text
/www/wwwroot/xingyu-source/
├── admin/
├── backend/
├── mobile/
├── deploy/
└── README.md
```
确认不是下面这种双层目录:
```text
/www/wwwroot/xingyu-source/im/backend/
```
创建运行目录:
```bash
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 数据库
进入宝塔“数据库”,创建:
```text
数据库名:im
用户名:xingyu_app
密码:使用 openssl rand -hex 32 生成
字符集:utf8mb4
访问权限:本地服务器
```
不要让后端使用 MySQL `root` 账号。生产配置会主动拒绝 `root` DSN。
生成数据库密码:
```bash
openssl rand -hex 32
```
数据库密码建议使用十六进制随机字符串,避免 `@`、`:` 等字符破坏 MySQL DSN 格式。
## 6. 执行数据库迁移
本项目包含 `001` 到 `026` 的顺序迁移。非 Docker 环境可以创建下面的迁移脚本:
```bash
nano /www/server/xingyu-im/migrate.sh
```
脚本内容:
```bash
#!/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
```
设置权限并执行:
```bash
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 域名会在构建时写入产物。必须使用正式域名构建:
```bash
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
```
发布静态文件:
```bash
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 域名,编辑:
```bash
nano /www/wwwroot/xingyu-source/admin/.env.production
```
然后将接口地址设为:
```dotenv
VITE_GLOB_API_URL=https://api.example.com/admin/v1
```
保持同域名反向代理时无需修改,默认值就是 `/admin/v1`。开发接口地址位于 `admin/.env.development`,不会影响生产构建。
先将 `admin/apps/web-ele/.env` 中的下面一项改为独立随机值:
```dotenv
VITE_APP_STORE_SECURE_KEY=替换为独立随机字符串
```
该值可以使用 `openssl rand -hex 32` 生成。它会进入前端产物,不能替代服务端密钥,但不应继续使用示例值。
执行构建:
```bash
cd /www/wwwroot/xingyu-source/admin
pnpm install --frozen-lockfile
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @vben/web-ele build
```
发布静态文件:
```bash
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 后端
先运行测试:
```bash
cd /www/wwwroot/xingyu-source/backend
go mod download
go test ./...
```
测试通过后构建静态二进制:
```bash
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. 配置后端生产环境变量
生成三组不同的密钥:
```bash
openssl rand -hex 48
openssl rand -hex 48
openssl rand -hex 32
```
前两个分别用于 JWT 和平台配置加密,第三个可作为初始管理员密码随机部分。`IM_CONFIG_ENCRYPTION_KEY` 上线后必须永久、安全保存,不能随意更换。
创建配置文件:
```bash
nano /etc/xingyu-im.env
```
内容如下:
```dotenv
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
```
保护配置文件:
```bash
chown root:root /etc/xingyu-im.env
chmod 0600 /etc/xingyu-im.env
```
生产环境要求:
- JWT 密钥至少 32 字节。
- 配置加密密钥至少 32 字节。
- CORS 来源必须是明确的 HTTPS 地址,不能使用 `*`。
- 数据库账号不能是 `root`。
- 初始管理员密码至少 12 位,且包含大小写字母、数字和特殊字符。
- 禁止开启演示数据。
## 11. 创建 systemd 服务
创建:
```bash
nano /etc/systemd/system/xingyu-im.service
```
内容如下:
```ini
[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
```
启动服务:
```bash
systemctl daemon-reload
systemctl enable --now xingyu-im
systemctl status xingyu-im --no-pager
```
查看日志:
```bash
journalctl -u xingyu-im -n 200 --no-pager
journalctl -u xingyu-im -f
```
本机验证:
```bash
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` 中的:
```dotenv
IM_BOOTSTRAP_ADMIN_PASSWORD=
```
然后执行:
```bash
systemctl restart xingyu-im
```
## 12. 在宝塔创建三个网站
### 12.1 H5 网站
进入“网站”,新增 HTML 静态网站:
```text
域名:app.example.com
根目录:/www/wwwroot/xingyu-app
PHP:纯静态
```
在站点 Nginx 配置的 `server` 内确保包含:
```nginx
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 静态网站:
```text
域名:admin.example.com
根目录:/www/wwwroot/xingyu-admin
PHP:纯静态
```
在该站点 `server` 内加入或替换相应 `location`:
```nginx
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 网站
新增网站:
```text
域名:api.example.com
根目录:/www/wwwroot/xingyu-api-empty
PHP:纯静态
```
在该站点 `server` 内加入:
```nginx
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;
}
```
保存配置前点击宝塔的配置检查,或在终端执行:
```bash
nginx -t
```
检查成功后重载:
```bash
/etc/init.d/nginx reload
```
不要在宝塔中再为 API 域名创建另一条普通“反向代理”,避免宝塔生成的 `location /` 与上面的 WebSocket 配置冲突。
## 13. 配置 HTTPS
依次打开三个网站:
```text
网站设置 -> SSL -> Let's Encrypt
```
分别申请证书并开启强制 HTTPS。宝塔负责证书文件和自动续签。
证书完成后验证:
```bash
curl -fsS https://api.example.com/healthz
```
客户端必须使用:
```text
https://api.example.com
wss://api.example.com/ws
```
不能在生产页面中连接 `http://` 或 `ws://`,否则浏览器会拦截混合内容,消息传输也没有 TLS 保护。
## 14. 上线验收
### 14.1 服务状态
```bash
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`。
推荐顺序:
```bash
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 节重新构建并发布两个前端。
如后端启动失败,立即查看:
```bash
journalctl -u xingyu-im -n 200 --no-pager
```
不要在未确认数据库迁移兼容性的情况下直接覆盖生产版本。
## 16. 备份要求
至少备份:
```text
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. 常见故障
### 后端启动失败
```bash
systemctl status xingyu-im --no-pager
journalctl -u xingyu-im -n 200 --no-pager
```
常见原因:数据库未迁移、密钥不足 32 字节、管理员密码强度不足、使用 root DSN、CORS 使用 HTTP 或 `*`。
### 管理端接口返回 404
检查管理端站点是否存在 `/admin/v1/` 反向代理,以及 `proxy_pass` 是否为:
```nginx
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 域名来源。
- 服务器时间是否准确。
### 上传失败
检查:
```bash
ls -ld /www/wwwroot/xingyu-data/uploads
sudo -u www test -w /www/wwwroot/xingyu-data/uploads
```
同时确认 API 站点配置了:
```nginx
client_max_body_size 16m;
```
### Node 构建内存不足
管理端构建建议至少提供 4 GB 可用内存。可以设置:
```bash
NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @vben/web-ele build
```
如果仍然被系统杀死,应增加内存或临时交换空间,不要不断重试导致服务器失去响应。
## 18. 官方资料
- [宝塔安装基础环境](https://docs.bt.cn/getting-started/install-basic-environment)
- [宝塔安装 Web 服务器](https://docs.bt.cn/user-guide/site/install-webserver)
- [宝塔网站 SSL 配置](https://docs.bt.cn/user-guide/site/php/site-config/ssl)
- [Go 官方下载](https://go.dev/dl/)
- [Node.js 22 官方下载](https://nodejs.org/en/download/archive/v22)
- [pnpm 官方安装说明](https://pnpm.io/installation)