外观
宝塔面板部署
适合谁
服务器上已经装了(或者准备装)宝塔 Linux 面板,希望尽量用鼠标完成部署的新手。 全程只有少量命令需要在宝塔的“终端”里粘贴执行。
最终效果:
https://shop.example.com/→ 用户前台https://shop.example.com/admin/→ 管理后台- 后端程序由宝塔的「进程守护管理器」守护,崩溃或重启服务器后自动拉起。
准备
| 项目 | 要求 |
|---|---|
| 服务器 | 64 位 Linux(CentOS 7+/Debian/Ubuntu 均可),1 核 1 GB 内存起步 |
| 域名 | 例如 shop.example.com,已添加 A 记录指向服务器 IP |
| 宝塔面板 | 7.x 或更新版本,已安装 Nginx(在软件商店安装,版本任意) |
| 文件 | zebra-store 程序、storefront/dist、admin/dist、config.example.yml(见下方“获取程序和网页”) |
获取程序和网页
一共需要三样东西:
| 东西 | 是什么 | 怎么得到 |
|---|---|---|
zebra-store | 后端程序(一个文件) | 在 backend/ 编译 |
storefront/dist | 用户前台网页 | 在 storefront/ 打包 |
admin/dist | 管理后台网页 | 在 admin/ 打包,必须带 --base=/admin/ |
在你自己的电脑(装好 Rust ≥ 1.90 和 Node.js ≥ 20)上执行:
bash
git clone <仓库地址> zebra-store && cd zebra-store
# 1) 后端:给 Linux 服务器编译一个不依赖任何系统库的静态程序
cargo install cargo-zigbuild # 第一次需要;macOS 还要 brew install zig
cd backend
rustup target add x86_64-unknown-linux-musl
cargo zigbuild --release -p zs-server --target x86_64-unknown-linux-musl
# 产物:backend/target/x86_64-unknown-linux-musl/release/zebra-store
cd ..
# 2) 用户前台
cd storefront && npm ci && npm run build && cd .. # 产物:storefront/dist
# 3) 管理后台(放在 /admin/ 路径下)
cd admin && npm ci && npx vite build --base=/admin/ && cd .. # 产物:admin/dist直接在 Linux 服务器上编译
如果你就在 x86_64 Linux 服务器上编译,后端可以简单地用 cargo build --release -p zs-server,产物在 backend/target/release/zebra-store。 服务器是 ARM(arm64)的话,把上面的 x86_64-unknown-linux-musl 换成 aarch64-unknown-linux-musl。
为什么后台要加 --base=/admin/
后台网页默认假设自己放在网站根目录。我们把它放在 https://你的域名/admin/, 所以打包时要告诉它“我的根路径是 /admin/”,否则打开后台时 JS 文件会 404,页面一片空白。
步骤
1. 安装宝塔和 Nginx
如果还没装宝塔,按 宝塔官网 的一键安装命令安装,装完用浏览器登录面板。 第一次登录时,宝塔会推荐安装 LNMP / LAMP 套件:只勾选 Nginx 即可(数据库默认用 SQLite,不需要 MySQL;想用 MySQL 再勾选)。
宝塔软件商店安装 Nginx
2. 放开防火墙端口
宝塔左侧 安全 → 确认 80 和 443 端口已放行。 不要放行 8081:后端只给本机的 Nginx 访问。如果云服务商有“安全组”,也要放行 80 和 443。
3. 创建网站
宝塔左侧 网站 → 添加站点:
- 域名:
shop.example.com - 根目录:保持默认
/www/wwwroot/shop.example.com - FTP:不创建;数据库:不创建;PHP 版本:纯静态
宝塔添加站点
4. 上传前台和后台网页
宝塔左侧 文件 → 进入 /www/wwwroot/shop.example.com:
- 删除宝塔自动生成的
index.html、404.html(保留.user.ini等隐藏文件即可); - 把
storefront/dist里面的所有文件上传到这个目录(上传后这里应该直接有index.html和assets/); - 在这个目录里新建文件夹
admin,把admin/dist里面的所有文件上传进去(上传后应有admin/index.html)。
上传压缩包更快
先在本机把 dist 打成 zip,上传后在宝塔文件管理器里右键 解压。
上传后的网站目录
5. 上传后端程序
在 文件 里新建目录 /www/zebra,上传:
zebra-store(后端程序)config.example.yml,上传后重命名为config.yml
然后打开宝塔 终端(左侧菜单),粘贴执行:
bash
cd /www/zebra
mkdir -p data uploads
chmod +x zebra-store
chown -R www:www /www/zebra
chmod 600 config.ymlwww 是宝塔运行网站用的用户,后端也用它运行,这样它能读写 data/ 和 uploads/。
6. 修改配置文件
在宝塔文件管理器里双击 /www/zebra/config.yml 编辑。
写配置文件
从仓库复制 backend/config.example.yml,改名为 config.yml,至少改下面这些 (每一项的含义见 配置文件详解):
yaml
app:
secret_key: 第一串随机字符 # openssl rand -hex 24 生成
server:
host: 127.0.0.1 # 只让本机的反向代理访问后端
port: 8081
mode: release
jwt:
secret: 第二串随机字符
user_jwt:
secret: 第三串随机字符
bootstrap:
default_admin_username: admin
default_admin_password: 你的管理员密码 # 只在第一次启动时用来创建超级管理员
database:
url: sqlite://data/zebra.db?mode=rwc # 相对“运行目录”,即 <程序目录>/data/zebra.db
upload:
dir: uploads # 相对“运行目录”
cors:
allowed_origins: ["https://shop.example.com"]三个密钥
app.secret_key、jwt.secret、user_jwt.secret 必须是三个不同的值,每个至少 16 个字符, 不能保留示例里的 change-me-…,否则程序拒绝启动并在日志里写明是哪一项。 app.secret_key 用来加密数据库里的敏感字段(支付密钥等),上线后不要再改,改了之前加密的数据就解不开了。
宝塔部署时 config.yml 放在 /www/zebra,下面守护进程的“运行目录”也设成 /www/zebra, 所以数据库文件是 /www/zebra/data/zebra.db,图片在 /www/zebra/uploads/。
7. 用「进程守护管理器」运行后端
- 宝塔 软件商店 → 搜索 进程守护管理器(Supervisor)→ 安装;
- 打开它 → 添加守护进程:
| 字段 | 填写 |
|---|---|
| 名称 | zebra-store |
| 启动用户 | www |
| 运行目录 | /www/zebra |
| 启动命令 | /www/zebra/zebra-store --config /www/zebra/config.yml serve |
| 进程数量 | 1(必须是 1) |
- 保存后状态应为“运行中”。点 日志 能看到启动日志,出现
listening和0.0.0.0:8081或127.0.0.1:8081就成功了。
进程守护管理器添加 zebra-store
在终端里再确认一次:
bash
curl -s http://127.0.0.1:8081/api/v1/public/config | head -c 120能看到以 {"status_code":0 开头的内容就说明后端正常。
不想装进程守护管理器?用 systemd
在终端里创建 /etc/systemd/system/zebra-store.service:
ini
[Unit]
Description=Zebra Store
After=network-online.target
Wants=network-online.target
[Service]
User=www
Group=www
WorkingDirectory=/www/zebra
ExecStart=/www/zebra/zebra-store --config /www/zebra/config.yml serve
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target然后执行 systemctl daemon-reload && systemctl enable --now zebra-store。两种方式只能选一种,否则会抢同一个端口。
8. 配置 Nginx:转发接口 + 单页应用
哪些路径要转给后端
前台和后台只是静态网页,所有数据都来自后端。反向代理必须把下面这些路径转发给后端 127.0.0.1:8081, 并保留原始的 Host 头:
| 路径 | 用途 |
|---|---|
/api/ | 所有接口,包括支付回调和对接接口 |
/uploads/ | 上传的图片 |
/sitemap.xml、/robots.txt | 搜索引擎 |
/shared/ | 异次元发卡把本站当上游时调用(异次元对接) |
/plugin/open-api/ | 萌次元协议把本站当上游时调用(萌次元对接) |
其余路径:/admin/ 开头的返回后台网页,其他的返回前台网页;找不到文件时返回对应的 index.html (前台和后台都是单页应用,刷新 /products/xxx 这类地址时服务器上并没有这个文件)。
一定要保留 Host 头
后端靠 Host 判断访问的是主站还是哪个分站,生成支付回调地址时也会用到。
宝塔自带的“反向代理”页面一次只能转发一个目录,而且会改写一些默认设置,所以最稳妥的做法是直接改站点配置文件:
网站 → 点 shop.example.com 的 设置 → 配置文件,找到 server { … } 里 #REWRITE-END 这一行,在它的下面粘贴:
nginx
# ---------- Zebra Store ----------
client_max_body_size 20m;
location ~ ^/(api|uploads|shared|plugin/open-api)/ {
proxy_pass http://127.0.0.1:8081;
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_read_timeout 120s;
}
location = /sitemap.xml { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; }
location = /robots.txt { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; }
# 管理后台(单页应用)
location = /admin { return 308 /admin/; }
location /admin/ {
try_files $uri $uri/ /admin/index.html;
}
# 用户前台(单页应用)
location / {
try_files $uri $uri/ /index.html;
}
# ---------- Zebra Store end ----------保存。如果宝塔提示 location / 重复,说明站点的 伪静态 里已经有 location /:到 伪静态 标签页把内容清空保存,再回来保存配置文件。
不要开启宝塔的“缓存”
如果之前用宝塔的「反向代理」页面加过 /api,请删除那条反向代理,改用上面的配置。 接口响应一旦被缓存,买家会看到别人的数据或过期的订单状态。
宝塔站点配置文件
9. 申请 SSL 证书
网站 → shop.example.com 的 设置 → SSL → Let's Encrypt → 勾选域名 → 申请。 申请成功后打开右上角的 强制 HTTPS。宝塔会自动续期。
宝塔申请 Let's Encrypt 证书
让后端拿到买家的真实 IP
风控、限流和登录日志都需要买家的真实 IP。反向代理通过 X-Forwarded-For 头把 IP 传给后端, 但后端只相信 server.trusted_proxies 里列出的代理。默认值是 127.0.0.1/32 和 ::1/128, 反向代理和后端在同一台机器上时不用改。
反向代理在别的机器或者 Docker 容器里时,把它的 IP 或网段加进去,例如:
yaml
server:
trusted_proxies: ["127.0.0.1/32", "::1/128", "172.28.0.0/24"]0.0.0.0/0 这种“相信所有人”的写法会被拒绝,因为那样任何人都能伪造 IP。
宝塔的 Nginx 和后端在同一台机器上,保持默认即可。
10. 数据库选择
SQLite(默认,推荐新手):什么都不用做,数据就在
/www/zebra/data/zebra.db。MySQL:宝塔左侧 数据库 → 添加数据库:数据库名
zebra、用户名zebra、设置密码、访问权限选 本地服务器, 字符集选 utf8mb4。然后把config.yml改成:yamldatabase: url: mysql://zebra:你的数据库密码@127.0.0.1:3306/zebra在进程守护管理器里 重启
zebra-store,表会自动创建。密码里有@、:、/、#等符号时要先做 URL 编码 (例如@写成%40),或者干脆换一个只含字母数字的密码。更多说明见 切换数据库。
切换数据库不会搬数据
从 SQLite 换成 MySQL 后是一个全新的空库,原来的数据不会自动迁移。请在开张之前决定好。
验证
逐项检查:
- [ ]
https://你的域名/能打开前台,标题是你的站点名; - [ ]
https://你的域名/admin/能打开后台登录页,用admin和配置里的密码能登录; - [ ]
https://你的域名/api/v1/public/config返回一段 JSON(开头是{"status_code":0); - [ ] 在
https://你的域名/products页面按 F5 刷新,不会出现 404; - [ ] 后台 内容管理 → 素材管理 上传一张图片,能正常显示(说明
/uploads/转发正常); - [ ] 浏览器地址栏有小锁,证书有效。
登录后台后,按 部署完成后要做的事 继续。
升级
- 先按下面的方法备份;
- 在本机编译新版本,得到新的
zebra-store、storefront/dist、admin/dist; - 进程守护管理器里 停止
zebra-store; - 用新文件覆盖
/www/zebra/zebra-store(覆盖后在终端执行chmod +x /www/zebra/zebra-store); - 覆盖网站目录里的前台文件、
admin/里的后台文件(旧的assets/可以先删除再上传); - 启动
zebra-store。新版本启动时会自动补齐新表和新字段,业务数据保留。
备份
- 宝塔 计划任务 → 添加 备份目录:目录选
/www/zebra(包含数据库、图片和配置),周期每天; - 用 MySQL 时再加一条 备份数据库 任务;
- 建议同时勾选上传到对象存储或另一台机器。
备份
要备份的只有三样:数据库、上传目录 uploads/、配置文件 config.yml(里面有加密密钥)。
bash
# SQLite 在线备份(不用停服务;需要 sqlite3:apt install sqlite3)
sqlite3 数据目录/zebra.db ".backup '/root/backup/zebra-$(date +%F).db'"
tar czf /root/backup/uploads-$(date +%F).tgz -C 程序目录 uploads
cp 程序目录/config.yml /root/backup/config-$(date +%F).ymlMySQL / PostgreSQL、定时备份和恢复步骤见 备份与升级。
常见问题
打开网站是宝塔的默认页 / 404 网站根目录里还留着宝塔生成的 index.html,或者 storefront/dist 是整个文件夹传上去的(变成了 dist/index.html)。 根目录下应该直接是 index.html 和 assets/。
打开 /admin/ 一片空白 后台打包时没有加 --base=/admin/,或者后台文件没放在 admin/ 子目录里。按 F12 看控制台,如果 JS 文件 404 就是这个原因。
页面能打开但一直加载、提示网络错误 后端没有运行或 /api/ 没有转发。在终端执行 curl http://127.0.0.1:8081/api/v1/public/config 检查后端; 再检查配置文件里的 location ~ ^/(api|… 是否粘贴在了 server { } 里面。
进程守护管理器显示已停止,日志提示 secret 三个密钥没改或者相同,按日志提示修改 config.yml 后重启。
日志提示 Permission denied / unable to open database file/www/zebra 的所有者不是 www,重新执行 chown -R www:www /www/zebra。
上传图片失败 检查 client_max_body_size 20m; 是否加上;再检查 /www/zebra/uploads 是否属于 www。