跳转到内容

多店铺运维

rCommerce 的设计目标是一个二进制,多家店铺。每家店铺是一个 独立实例——各自的端口、配置文件、数据库和服务——共享同一个 部署的二进制。本文以同一台主机上运行两家示例店铺 shop-a (store-a.example.com)和 shop-b(store-b.example.com)为例。

店铺端口配置数据库Uploads日志
shop-a8080/usr/local/etc/shop-a/config.tomlshop_a/var/db/shop-a/uploads/var/log/shop-a/
shop-b8081/usr/local/etc/shop-b/config.tomlshop_b/var/db/shop-b/uploads/var/log/shop-b/

每个 config.toml 承载该店铺的全部专属配置:自己的 [database] 凭据、[branding](名称、客服邮箱、站点 URL、logo、主题色——驱动 邮件模板和账单品牌)、自己的 [payment.*] 支付网关密钥、 [notifications.email] 下自己的邮件服务商 token,以及店铺级行为如 [orders] auto_cancel_pending_minutes。配置文件为 root:wheel 644;服务以 rcommerce 用户运行,需要能够读取它们。

每个店铺的 API 密钥保存在自己的数据库中,因此为某店铺签发的密钥 永远无法触碰其他店铺。

在 FreeBSD 上每个实例是一个 rc.d 服务。按店铺复制服务脚本,修改 名称、配置路径、数据目录和日志文件。示例 /usr/local/etc/rc.d/rcommerce-b:

#!/bin/sh
#
# PROVIDE: rcommerce_b
# REQUIRE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name="rcommerce_b"
desc="rCommerce API server (shop-b)"
rcommand="/usr/local/sbin/rcommerce"
pidfile="/var/run/${name}/${name}.pid"
load_rc_config $name
: ${rcommerce_b_enable="NO"}
: ${rcommerce_b_config="/usr/local/etc/shop-b/config.toml"}
: ${rcommerce_b_data_dir="/var/db/shop-b"}
: ${rcommerce_b_log_file="/var/log/shop-b/server.log"}
start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"
rcommerce_b_start()
{
/usr/bin/install -d -o rcommerce -g rcommerce -m 750 /var/run/${name}
/usr/bin/install -d -o rcommerce -g rcommerce -m 750 ${rcommerce_b_data_dir}
/usr/bin/install -d -o rcommerce -g rcommerce -m 750 $(dirname ${rcommerce_b_log_file})
echo "Starting ${desc}."
/usr/sbin/daemon \
-f \
-S \
-p ${pidfile} \
-u rcommerce \
-t ${name} \
env RCOMMERCE_CONFIG="${rcommerce_b_config}" \
IMAGE_BASE_URL="https://images.store-b.example.com" \
${rcommand}
}
rcommerce_b_stop()
{
echo "Stopping ${desc}."
/usr/bin/kill -TERM $(cat ${pidfile})
}
rcommerce_b_status()
{
if [ -f ${pidfile} ]; then
if /bin/pkill -0 -F ${pidfile} >/dev/null 2>&1; then
echo "${desc} is running as pid $(cat ${pidfile})."
return 0
else
echo "${desc} is not running but pid file exists."
return 1
fi
else
echo "${desc} is not running."
return 1
fi
}
run_rc_command "$1"

在 /etc/rc.conf 中启用:

Terminal window
rcommerce_a_enable="YES"
rcommerce_b_enable="YES"

在 Linux 上同样的模式对应每个店铺一个 systemd unit(复制 Systemd 服务的 unit 文件,修改 Description、ExecStart 的配置路径和 unit 名称)。

任何重启之后,务必确认新进程真的替换了旧进程 (ps aux | grep "rcommerce server" —— PID 和启动时间必须是新 的;“重启成功”背后藏着一个没死透的旧进程的坑已经踩过一次),并 对每个端口请求 /health。

一个 Caddyfile 服务所有店铺。每个 vhost 终结 TLS(Caddy 自动申请 Let’s Encrypt 证书,包括 DNS 走 Cloudflare 代理时通过 HTTP-01 申请), 为 /uploads/* 服务该店铺 uploads 目录下的静态文件,其余请求 反向代理到该店铺的端口。注意 root 指向 uploads/ 的父 目录:

api.store-a.example.com {
root * /var/db/shop-a
file_server /uploads/*
reverse_proxy 127.0.0.1:8080
}
api.store-b.example.com {
root * /var/db/shop-b
file_server /uploads/*
reverse_proxy 127.0.0.1:8081
}

file_server 块是 R2/CDN 图片托管之前遗留的图片 URL 的兜底—— 即使图片迁移到 R2 之后也要保留。

所有实例共享 /usr/local/sbin/rcommerce,因此一次升级需要重启 所有店铺。针对宿主机目标原生构建——绝不要在生产机上构建。 FreeBSD 示例:

Terminal window
# 1. 在构建机上构建(例如有源码 checkout 的远程构建机)
cargo build --release --bin rcommerce --target x86_64-unknown-freebsd
# 仓库的 .cargo/config.toml 可能默认其他宿主 target,
# 务必显式传入 target。
# 2. 传输二进制
scp target/x86_64-unknown-freebsd/release/rcommerce shopserver:/tmp/rcommerce-new
# 3. 重启所有实例(逐个停、替换、逐个起)
ssh shopserver '
for s in rcommerce-a rcommerce-b; do sudo service $s stop; sleep 2; done
sudo mv /tmp/rcommerce-new /usr/local/sbin/rcommerce && sudo chmod +x /usr/local/sbin/rcommerce
for s in rcommerce-a rcommerce-b; do sudo service $s start; done'

迁移在每次实例启动时自动执行,作用于它自己的数据库——如果有店铺 存在待执行的 schema 变更,请在低峰期升级。

部署后检查清单:

  1. ps aux | grep "rcommerce server" —— 所有实例都显示新的 PID。
  2. 对每个店铺 curl -sf https://api.<store>.com/health。
  3. 查看 /var/log/<store>/server.log 有无迁移或启动错误。

另见:二进制部署、反向代理。