# 星遇社交平台:宝塔非 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)