chore(init): 初始化解忧商行项目并完成 Phase 0 基座
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
# 生产部署说明
|
||||
|
||||
目标访问地址:
|
||||
|
||||
```text
|
||||
https://主站域名 # CADDY_DOMAIN,哈夫币系统
|
||||
https://选号网域名 # CADDY_SHOW_DOMAIN(可选),hfb_show 静态选号站
|
||||
https://monitor.主站域名 # Beszel 监控
|
||||
```
|
||||
|
||||
生产部署使用 Caddy 作为公网入口,只暴露宿主机 `80`、`443` 端口。Caddy 会自动申请和续签 HTTPS 证书,并直接托管前端静态资源(主站 `dist` 与可选的选号网 `show-dist` 在构建 Caddy 镜像时打入);后端、MySQL、Redis 均通过 Docker 内网通信,图片对象存储使用阿里云 OSS。选号网与主站**共用同一 backend**,浏览器访问选号网域名下的 `/api/*` 由 Caddy 反代到本机 backend,**不会直连数据库**。
|
||||
|
||||
部署前请确认:
|
||||
|
||||
- 主站域名 A 记录已解析到服务器公网 IP;启用选号网时,选号网域名也要解析到同一 IP。
|
||||
- 服务器安全组或防火墙已放行 `80/tcp` 和 `443/tcp`。
|
||||
- 服务器上没有其他服务占用 `80`、`443`。
|
||||
- 国内服务器如需对外访问,请先确认域名备案和云厂商限制。
|
||||
|
||||
## 1. 准备环境变量
|
||||
|
||||
```bash
|
||||
cp backend/.env.prod.example backend/.env
|
||||
```
|
||||
|
||||
编辑 `backend/.env`,至少替换以下占位值:
|
||||
|
||||
```text
|
||||
CADDY_DOMAIN
|
||||
CADDY_EMAIL
|
||||
MYSQL_ROOT_PASSWORD
|
||||
MYSQL_PASSWORD
|
||||
MYSQL_DSN
|
||||
JWT_SECRET
|
||||
STORAGE_ACCESS_KEY_ID
|
||||
STORAGE_SECRET_ACCESS_KEY
|
||||
BACKEND_UID
|
||||
BACKEND_GID
|
||||
BACKUP_DIR
|
||||
BACKUP_STATUS_DIR
|
||||
BACKUP_RESTORE_DIR
|
||||
BACKUP_OSS_URI
|
||||
BACKUP_PASSPHRASE
|
||||
BACKUP_MYSQL_PASSWORD
|
||||
```
|
||||
|
||||
`BACKEND_UID`、`BACKEND_GID` 填写服务器业务用户的数值 ID,例如:
|
||||
|
||||
```bash
|
||||
id -u yml
|
||||
id -g yml
|
||||
```
|
||||
|
||||
后端容器会使用该 UID/GID 运行。部署脚本由 root 执行时会自动把已有日志修正为该属主;非 root 执行且发现旧的 root 日志时,会给出需要执行的 `chown` 命令后停止。
|
||||
|
||||
### 对象存储:阿里云 OSS
|
||||
|
||||
图片等业务文件存储在私有 OSS Bucket,`backend/.env` 的 `STORAGE_*` 直接指向 OSS:
|
||||
|
||||
```text
|
||||
STORAGE_ENDPOINT=https://oss-cn-hangzhou-internal.aliyuncs.com
|
||||
STORAGE_BUCKET=hfb-sys-assets
|
||||
STORAGE_ACCESS_KEY_ID=change-oss-access-key-id
|
||||
STORAGE_SECRET_ACCESS_KEY=change-oss-access-key-secret
|
||||
STORAGE_REGION=cn-hangzhou
|
||||
STORAGE_BUCKET_LOOKUP=dns
|
||||
```
|
||||
|
||||
RAM 凭证只授予该 Bucket 的列举、读取、写入对象权限,不要使用阿里云主账号 AccessKey。杭州 ECS 必须使用内网 Endpoint,避免跨公网访问。
|
||||
|
||||
历史 MinIO 数据已通过 `scripts/migrate-minio-to-oss.sh` 迁移完成,生产编排不再包含 MinIO;旧 MinIO volume 观察期结束后可删除。
|
||||
|
||||
### 数据库在线备份与时间点恢复
|
||||
|
||||
生产 MySQL 开启了 `ROW` 格式 binlog 和崩溃安全刷盘。备份期间业务持续读写:每日用 XtraBackup 做物理全量备份,调度器每分钟检查 binlog 位点,仅在有新增已提交事务时轮转并归档。备份对象先做客户端加密,再由 Bucket 的服务器端加密保护;远端仅在 payload、checksum 和 manifest 都上传成功后写入 `complete.env`。
|
||||
|
||||
部署脚本会创建或更新 `BACKUP_MYSQL_USER`,该用户只有 XtraBackup 所需的全局备份权限和业务库只读权限,不开放远程 root。
|
||||
|
||||
备份 Bucket 必须独立于业务 Bucket,开启“OSS 完全托管”服务器端加密和版本控制。备份 RAM 身份仅授予该 Bucket 的列举、读写对象权限;若填写 `BACKUP_KMS_KEY_ID`,再授予该 Key 的加解密权限。推荐配置不可变保留策略。
|
||||
|
||||
部署机需安装 `ossutil`、`openssl` 和 `flock`,并使用与 cron 相同的业务用户完成 `ossutil` 凭证配置;部署脚本会在启动服务前检查这些依赖。
|
||||
|
||||
```bash
|
||||
# 每日全量热备,本地留存并上传 OSS 备份桶
|
||||
./scripts/backup-online.sh full
|
||||
|
||||
# 检查并归档有新增事务的关闭 binlog,配合全量实现时间点恢复
|
||||
./scripts/archive-binlog.sh archive
|
||||
|
||||
# 校验本地状态对应的远端完整对象 / 清理过期本地备份
|
||||
./scripts/archive-binlog.sh verify
|
||||
./scripts/backup-online.sh status
|
||||
./scripts/backup-online.sh prune
|
||||
```
|
||||
|
||||
后台“数据备份”页面只读取 `${BACKUP_STATUS_DIR}/status.json`;该目录以只读方式挂载给 backend,不包含备份载荷。首次部署后先生成状态摘要:
|
||||
|
||||
```bash
|
||||
./scripts/backup-status.sh write
|
||||
```
|
||||
|
||||
推荐 cron(以部署用户运行,并把日志写到受限目录):
|
||||
|
||||
```cron
|
||||
# 每分钟读取后台定时配置,按需归档 binlog 和执行全量热备。
|
||||
* * * * * cd /srv/hfb_jysh && ./scripts/backup-scheduler.sh >> /data/backups/backup-scheduler.log 2>&1
|
||||
15 5 * * * cd /srv/hfb_jysh && ./scripts/backup-online.sh prune >> /data/backups/prune.log 2>&1
|
||||
10 5 * * 0 cd /srv/hfb_jysh && ./scripts/archive-binlog.sh verify >> /data/backups/archive-verify.log 2>&1
|
||||
```
|
||||
|
||||
全量备份时间、是否启用定时任务和 binlog 间隔在后台“数据备份”页面配置;宿主机 cron 必须保留每分钟的调度器入口。不要将 Docker socket、备份目录或恢复操作暴露给 backend。
|
||||
|
||||
OSS 生命周期建议:`mysql/full/daily/` 30 天、`weekly/` 60 天、`monthly/` 365 天、`mysql/binlog/` 至少 45 天。时间点恢复窗口受最早全量备份和 binlog 保留期共同限制。
|
||||
|
||||
每周必须在隔离目录做一次恢复演练,绝不向生产容器回放:
|
||||
|
||||
```bash
|
||||
# 从 status 输出选取一个带 complete.env 的全量备份 URI。
|
||||
./scripts/restore-online.sh prepare oss://hfb-backup/mysql/full/daily/<server_uuid>/<timestamp>/complete.env
|
||||
./scripts/restore-online.sh fetch-all /data/restore/<server_uuid>/<timestamp>
|
||||
./scripts/restore-online.sh start /data/restore/<server_uuid>/<timestamp>
|
||||
./scripts/restore-online.sh replay /data/restore/<server_uuid>/<timestamp> hfb-restore-<timestamp> '2026-08-16 20:00:00'
|
||||
```
|
||||
|
||||
恢复容器使用 `--network none`,只可通过 `docker exec` 检查。演练结束后手动删除名为 `hfb-restore-*` 的恢复容器和 `/data/restore` 对应目录;生产容器不在恢复工具的可选目标中。
|
||||
|
||||
### 同机部署选号网(hfb_show,可选)
|
||||
|
||||
在 `backend/.env` 增加(域名不要带 `https://`):
|
||||
|
||||
```text
|
||||
CADDY_SHOW_DOMAIN=show.example.com
|
||||
HFB_SHOW_DIR=../hfb_show
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
1. 服务器上 `hfb_show` 与 `hfb_jysh` 同级目录(或 `HFB_SHOW_DIR` 指向真实路径)。
|
||||
2. 部署机有 Docker 即可;**不必安装 Node/npm**(无 npm 时脚本用 `node:24-alpine` 容器构建选号网)。
|
||||
3. 选号网域名 DNS 已指向本机。
|
||||
|
||||
一键脚本会自动:
|
||||
|
||||
- `npm ci && npm run build` 选号网;
|
||||
- 将 `dist` 同步到 `deploy/caddy/show-dist`;
|
||||
- 生成 Caddy 站点配置 `deploy/caddy/conf.d/show.caddy`;
|
||||
- 构建 Caddy 镜像时打入主站 + 选号网静态资源;
|
||||
- 为选号网域名申请 HTTPS,并把 `/api/*` 反代到 `backend:8080`。
|
||||
|
||||
不配置 `CADDY_SHOW_DOMAIN` 则行为与原来一致,仅部署主站。
|
||||
|
||||
如果正式对外运营,还需要配置真实短信和实名服务,避免继续使用测试配置。
|
||||
|
||||
## 2. 一键部署
|
||||
|
||||
```bash
|
||||
./scripts/deploy-prod.sh
|
||||
```
|
||||
|
||||
脚本会自动完成:
|
||||
|
||||
- 检查 `backend/.env` 是否仍指向 `127.0.0.1`、`localhost`、占位值或已下线的 MinIO。
|
||||
- 构建并启动生产容器。
|
||||
- 使用配置的非 root UID/GID 运行后端,并检查日志目录属主。
|
||||
- 通过 Caddy 自动申请或续签 HTTPS 证书。
|
||||
- 等待 MySQL、Redis 就绪。
|
||||
- 按顺序执行尚未应用的数据库迁移。
|
||||
- 重启后端并在容器内检查 `http://127.0.0.1:8080/api/health`。默认不依赖公网 DNS、TLS 或 Caddy;如需检查公网入口,可设置 `PROD_HEALTH_URL=https://你的域名/api/health`。
|
||||
|
||||
如需部署完成后直接跟随查看后端日志:
|
||||
|
||||
```bash
|
||||
./scripts/deploy-prod.sh --logs
|
||||
```
|
||||
|
||||
后端运行日志会写入宿主机:
|
||||
|
||||
```text
|
||||
backend/logs/app-YYYY-MM-DD.log
|
||||
```
|
||||
|
||||
测试阶段需要清空并重建数据库时:
|
||||
|
||||
```bash
|
||||
./scripts/deploy-prod.sh --reset-db
|
||||
```
|
||||
|
||||
## 3. 手动启动服务
|
||||
|
||||
```bash
|
||||
set -a
|
||||
source backend/.env
|
||||
set +a
|
||||
docker compose -f deploy/docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
手动执行 `docker compose` 时需要先导出 `CADDY_DOMAIN` 和 `CADDY_EMAIL`,否则 Caddy 无法读取证书域名配置。优先推荐使用一键部署脚本,它会自动从 `backend/.env` 导出这两个变量。
|
||||
|
||||
## 4. 手动执行数据库迁移
|
||||
|
||||
通常直接使用一键部署脚本即可。确实需要手动迁移时,使用后端镜像内置的 goose:
|
||||
|
||||
```bash
|
||||
MYSQL_DSN="$(awk -F= '$1=="MYSQL_DSN"{sub(/^[^=]*=/,""); print; exit}' backend/.env)"
|
||||
case "$MYSQL_DSN" in
|
||||
*multiStatements=*) GOOSE_DSN="$MYSQL_DSN" ;;
|
||||
*\?*) GOOSE_DSN="${MYSQL_DSN}&multiStatements=true" ;;
|
||||
*) GOOSE_DSN="${MYSQL_DSN}?multiStatements=true" ;;
|
||||
esac
|
||||
|
||||
docker compose -f deploy/docker-compose.prod.yml run --rm --no-deps backend \
|
||||
/app/goose -dir /app/migrations mysql "$GOOSE_DSN" up
|
||||
```
|
||||
|
||||
## 5. 验证
|
||||
|
||||
```bash
|
||||
curl https://主站域名/health
|
||||
curl https://主站域名/health/ready
|
||||
curl https://主站域名/api/health
|
||||
# 若启用了选号网:
|
||||
curl -I https://选号网域名/
|
||||
curl https://选号网域名/api/health
|
||||
curl "https://选号网域名/api/listings?page=1&page_size=1"
|
||||
```
|
||||
|
||||
浏览器访问:
|
||||
|
||||
```text
|
||||
https://主站域名
|
||||
https://主站域名/你配置的后台入口/login
|
||||
https://选号网域名 # 选号网首页
|
||||
```
|
||||
|
||||
Caddy 证书和 ACME 账号数据保存在 Docker 卷 `caddy_data`、`caddy_config` 中。不要随意删除这两个卷,否则 Caddy 会重新申请证书,频繁重建可能触发证书签发频率限制。
|
||||
@@ -0,0 +1,152 @@
|
||||
{
|
||||
email {$CADDY_EMAIL}
|
||||
on_demand_tls {
|
||||
ask http://backend:8080/api/distribution/check-domain
|
||||
}
|
||||
}
|
||||
|
||||
# 反代到后端时,用 Caddy 实测到的对端地址覆盖 X-Real-IP,
|
||||
# 防止客户端伪造 X-Forwarded-For 影响限流、审计和支付下单的 client_ip。
|
||||
(backend_proxy) {
|
||||
header_up X-Real-IP {remote_host}
|
||||
}
|
||||
|
||||
# 公开 API 反代片段:主站与选号网共用。
|
||||
(public_api) {
|
||||
@api_sse {
|
||||
path /api/chats/events /api/admin/chats/events
|
||||
}
|
||||
|
||||
handle @api_sse {
|
||||
reverse_proxy backend:8080 {
|
||||
flush_interval -1
|
||||
import backend_proxy
|
||||
}
|
||||
}
|
||||
|
||||
handle /api/* {
|
||||
request_body {
|
||||
max_size 11MB
|
||||
}
|
||||
reverse_proxy backend:8080 {
|
||||
import backend_proxy
|
||||
}
|
||||
}
|
||||
|
||||
handle /r/* {
|
||||
reverse_proxy backend:8080 {
|
||||
import backend_proxy
|
||||
}
|
||||
}
|
||||
|
||||
@health path /health /health/ready
|
||||
handle @health {
|
||||
reverse_proxy backend:8080 {
|
||||
import backend_proxy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 前台商城静态资源与安全头配置(主站与所有分销站点共享):
|
||||
# 前端通过 window.location.hostname 自动向 /api/distribution/site-info 请求分销配置。
|
||||
(storefront) {
|
||||
encode zstd gzip
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "DENY"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Permissions-Policy "camera=(), microphone=(), geolocation=()"
|
||||
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'"
|
||||
}
|
||||
|
||||
import public_api
|
||||
|
||||
# 平台校验文件(如 /e82f...txt)在 /m/<文件名>.txt 下也要能取到真实内容。
|
||||
# /m 是前端路由前缀,未命中静态文件时 try_files 会回退到 index.html,
|
||||
# 导致 /m/*.txt 返回 HTML;这里显式改写回根目录的同名文件。
|
||||
@mobile_verify_txt path_regexp ^/m/([^/]+\.txt)$
|
||||
handle @mobile_verify_txt {
|
||||
rewrite * /{re.mobile_verify_txt.1}
|
||||
root * /srv/dist
|
||||
file_server
|
||||
}
|
||||
|
||||
# 主站后台隐藏入口:storefront 片段同时服务主站和所有分销站点域名,
|
||||
# 这里统一 404,仅主站域名在下方主站 block 用 @main_admin 放行。
|
||||
@platform_admin path {$ADMIN_BASE_PATH:/console-jy8k3m} {$ADMIN_BASE_PATH:/console-jy8k3m}/*
|
||||
handle @platform_admin {
|
||||
respond 404
|
||||
}
|
||||
|
||||
# 生产后台页面使用构建时配置的隐藏入口,旧 /admin 入口直接返回 404。
|
||||
@legacy_admin path /admin /admin/*
|
||||
handle @legacy_admin {
|
||||
respond 404
|
||||
}
|
||||
|
||||
# 其余流量返回主站/分销前台前端静态资源。
|
||||
handle {
|
||||
root * /srv/dist
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
||||
# www 域名统一 301 到主域名,避免重复内容与后台入口分散。
|
||||
www.{$CADDY_DOMAIN} {
|
||||
redir https://{$CADDY_DOMAIN}{uri} permanent
|
||||
}
|
||||
|
||||
# 解忧商行主站。
|
||||
{$CADDY_DOMAIN} {
|
||||
# 主站后台隐藏入口只在本域名提供静态资源;分销站点域名由 storefront 片段统一 404。
|
||||
@main_admin path {$ADMIN_BASE_PATH:/console-jy8k3m} {$ADMIN_BASE_PATH:/console-jy8k3m}/*
|
||||
handle @main_admin {
|
||||
root * /srv/dist
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
|
||||
import storefront
|
||||
}
|
||||
|
||||
# 分销管理后台使用独立 React + Ant Design 静态资源,API 与主站复用 backend。
|
||||
distribution.{$CADDY_DOMAIN} {
|
||||
encode zstd gzip
|
||||
import public_api
|
||||
handle {
|
||||
root * /srv/distribution-admin
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
||||
# 选号网(可选独立站点):仅当 CADDY_SHOW_DOMAIN 有值时由部署脚本启用 conf.d/show.caddy。
|
||||
# 未配置时 conf.d 为空,不影响主站。
|
||||
import /etc/caddy/conf.d/*.caddy
|
||||
|
||||
# Beszel 自带登录与权限控制;单独子域避免与业务站点的 CSP、路由相互影响。
|
||||
monitor.{$CADDY_DOMAIN} {
|
||||
encode zstd gzip
|
||||
|
||||
request_body {
|
||||
max_size 10MB
|
||||
}
|
||||
|
||||
reverse_proxy beszel:8090 {
|
||||
transport http {
|
||||
read_timeout 360s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 分销站点动态域名入口:
|
||||
# 支持任意二级域名及分销商独立域名,首次访问时通过 on_demand_tls 触发后端白名单校验并自动签发 SSL 证书。
|
||||
https:// {
|
||||
tls {
|
||||
on_demand
|
||||
}
|
||||
import storefront
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
# 第一阶段:构建主站前端静态资源
|
||||
FROM node:24-alpine AS build
|
||||
|
||||
ARG VITE_ADMIN_BASE_PATH=/console-jy8k3m
|
||||
ENV VITE_ADMIN_BASE_PATH=${VITE_ADMIN_BASE_PATH}
|
||||
# 主站后台里「打开独立分销后台」按钮与开通凭证使用的分销后台地址。
|
||||
ARG VITE_DISTRIBUTION_ADMIN_URL=https://distribution.zuhaoc.com
|
||||
ENV VITE_DISTRIBUTION_ADMIN_URL=${VITE_DISTRIBUTION_ADMIN_URL}
|
||||
|
||||
WORKDIR /src
|
||||
COPY frontend/package*.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci --prefer-offline --no-audit --fund=false
|
||||
COPY frontend/ .
|
||||
RUN npm run build
|
||||
|
||||
# 第二套独立分销管理后台(React + Ant Design)
|
||||
FROM node:24-alpine AS distribution-build
|
||||
WORKDIR /src
|
||||
COPY distribution-admin/package*.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci --prefer-offline --no-audit --fund=false
|
||||
COPY distribution-admin/ .
|
||||
RUN npm run build
|
||||
|
||||
# 第三阶段:Caddy 作为公网入口,托管主站、分销后台和选号网静态资源
|
||||
FROM caddy:2.10-alpine
|
||||
|
||||
COPY --from=build /src/dist /srv/dist
|
||||
# show-dist 由 deploy-prod.sh 在 build 前准备(hfb_show 构建产物或占位页)
|
||||
COPY deploy/caddy/show-dist /srv/show-dist
|
||||
COPY --from=distribution-build /src/dist /srv/distribution-admin
|
||||
|
||||
EXPOSE 80 443
|
||||
@@ -0,0 +1,4 @@
|
||||
# 占位片段:保证 import conf.d/*.caddy 至少匹配一个文件。
|
||||
# 选号网站点由 deploy-prod.sh 写入 conf.d/show.caddy。
|
||||
(optional_sites_base) {
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>选号网未构建</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
max-width: 40rem;
|
||||
margin: 4rem auto;
|
||||
padding: 0 1rem;
|
||||
color: #17233d;
|
||||
line-height: 1.6;
|
||||
}
|
||||
code {
|
||||
background: #f1f5f9;
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>选号网静态资源尚未构建</h1>
|
||||
<p>
|
||||
请配置 <code>CADDY_SHOW_DOMAIN</code> 与 <code>HFB_SHOW_DIR</code> 后重新执行
|
||||
<code>./scripts/deploy-prod.sh</code>,或手动构建
|
||||
<code>hfb_show</code> 并将 <code>dist/</code> 复制到
|
||||
<code>deploy/caddy/show-dist/</code>。
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
# 由 scripts/deploy-prod.sh 根据 CADDY_SHOW_DOMAIN 生成到 conf.d/show.caddy
|
||||
# 选号网静态页 + 同源 /api 反代到 hfb_jysh backend(本机 Docker 网络,非公网绕行)
|
||||
|
||||
__CADDY_SHOW_DOMAIN__ {
|
||||
encode zstd gzip
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
}
|
||||
|
||||
# 与主站共用后端公开接口;浏览器访问选号网域名下的 /api/* 即可,无需跨域。
|
||||
handle /api/* {
|
||||
request_body {
|
||||
max_size 11MB
|
||||
}
|
||||
reverse_proxy backend:8080 {
|
||||
header_up X-Real-IP {remote_host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
}
|
||||
}
|
||||
|
||||
handle /health {
|
||||
reverse_proxy backend:8080 {
|
||||
header_up X-Real-IP {remote_host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
}
|
||||
}
|
||||
|
||||
handle {
|
||||
root * /srv/show-dist
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
name: hfb_jysh_dev
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.4
|
||||
container_name: jysh-mysql
|
||||
restart: unless-stopped
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootsecret
|
||||
MYSQL_DATABASE: hfb_jysh
|
||||
MYSQL_USER: hfb
|
||||
MYSQL_PASSWORD: secret
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- "${DEV_MYSQL_PORT:-23306}:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-prootsecret"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
redis:
|
||||
image: redis:7.4-alpine
|
||||
container_name: jysh-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${DEV_REDIS_PORT:-26379}:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||
container_name: jysh-minio
|
||||
restart: unless-stopped
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- "${DEV_MINIO_API_PORT:-29090}:9000"
|
||||
- "${DEV_MINIO_CONSOLE_PORT:-29091}:9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
minio_data:
|
||||
@@ -0,0 +1,182 @@
|
||||
name: hfb_jysh
|
||||
|
||||
services:
|
||||
caddy:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/caddy/Dockerfile
|
||||
args:
|
||||
VITE_ADMIN_BASE_PATH: ${VITE_ADMIN_BASE_PATH:-/console-jy8k3m}
|
||||
# 解忧商行分销后台固定子域;如需改名通过环境变量覆盖。
|
||||
VITE_DISTRIBUTION_ADMIN_URL: ${VITE_DISTRIBUTION_ADMIN_URL:-https://distribution.zuhaoc.com}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
CADDY_DOMAIN: ${CADDY_DOMAIN:?请先导出 CADDY_DOMAIN,或使用 scripts/deploy-prod.sh 部署}
|
||||
CADDY_EMAIL: ${CADDY_EMAIL:?请先导出 CADDY_EMAIL,或使用 scripts/deploy-prod.sh 部署}
|
||||
# Caddyfile 用它放行主站后台隐藏入口,并与前端构建期的 VITE_ADMIN_BASE_PATH 保持一致。
|
||||
ADMIN_BASE_PATH: ${VITE_ADMIN_BASE_PATH:-/console-jy8k3m}
|
||||
# 选号网域名可选;未配置时 conf.d 为空,不影响主站
|
||||
CADDY_SHOW_DOMAIN: ${CADDY_SHOW_DOMAIN:-}
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./caddy/conf.d:/etc/caddy/conf.d:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
cpus: '0.5'
|
||||
reservations:
|
||||
memory: 128M
|
||||
cpus: '0.25'
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
mysql:
|
||||
image: mysql:8.4
|
||||
restart: unless-stopped
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
env_file:
|
||||
- ../backend/.env
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
cpus: '2.0'
|
||||
reservations:
|
||||
memory: 512M
|
||||
cpus: '1.0'
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$${MYSQL_ROOT_PASSWORD}\""]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
redis:
|
||||
image: redis:7.4-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
cpus: '0.5'
|
||||
reservations:
|
||||
memory: 128M
|
||||
cpus: '0.25'
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ../backend
|
||||
restart: unless-stopped
|
||||
# 使用宿主机业务用户的 UID/GID,避免绑定挂载的日志被 root 创建。
|
||||
user: "${BACKEND_UID:?请在 backend/.env 配置 BACKEND_UID}:${BACKEND_GID:?请在 backend/.env 配置 BACKEND_GID}"
|
||||
env_file:
|
||||
- ../backend/.env
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- ../backend/logs:/app/logs
|
||||
# 仅挂载备份状态摘要,后端无法读取备份文件、Docker socket 或 OSS 凭证文件。
|
||||
- ${BACKUP_STATUS_DIR:-/data/backups/status}:/var/run/jieyou-backup-status:ro
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: '1.0'
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: '0.5'
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
expose:
|
||||
- "8080"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O /dev/null -T 5 http://127.0.0.1:8080/api/health"]
|
||||
interval: 10s
|
||||
timeout: 6s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
|
||||
beszel:
|
||||
image: henrygd/beszel:0.18.7
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
APP_URL: https://monitor.${CADDY_DOMAIN}
|
||||
CHECK_UPDATES: "false"
|
||||
ports:
|
||||
# 仅供同主机的 Agent 通过回环地址注册,公网访问统一经过 Caddy。
|
||||
- "127.0.0.1:8090:8090"
|
||||
volumes:
|
||||
- beszel_data:/beszel_data
|
||||
- beszel_socket:/beszel_socket
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
cpus: '0.25'
|
||||
reservations:
|
||||
memory: 32M
|
||||
cpus: '0.05'
|
||||
healthcheck:
|
||||
test: ["CMD", "/beszel", "health", "--url", "http://localhost:8090"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
beszel-agent:
|
||||
image: henrygd/beszel-agent:0.18.7
|
||||
profiles: ["beszel-agent"]
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- beszel_agent_data:/var/lib/beszel-agent
|
||||
- beszel_socket:/beszel_socket
|
||||
# Beszel 只读取 Docker 状态和指标,使用只读 Socket 降低风险。
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
LISTEN: /beszel_socket/beszel.sock
|
||||
HUB_URL: http://127.0.0.1:8090
|
||||
SYSTEM_NAME: hfb-production
|
||||
# 在 Hub 的“添加系统”页面生成后写入 backend/.env。
|
||||
KEY: ${BESZEL_AGENT_KEY:-}
|
||||
TOKEN: ${BESZEL_AGENT_TOKEN:-}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
cpus: '0.25'
|
||||
reservations:
|
||||
memory: 16M
|
||||
cpus: '0.05'
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
beszel_data:
|
||||
beszel_socket:
|
||||
beszel_agent_data:
|
||||
@@ -0,0 +1,26 @@
|
||||
[client]
|
||||
default-character-set = utf8mb4
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
|
||||
[mysqld]
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
init-connect = 'SET NAMES utf8mb4'
|
||||
|
||||
# 生产慢查询观测:应用日志记录 SQL 形状,MySQL 原生日志记录超过阈值的语句。
|
||||
# performance_schema 默认开启,使用 events_statements_summary_by_digest 做聚合分析。
|
||||
slow_query_log = ON
|
||||
long_query_time = 0.5
|
||||
log_slow_admin_statements = ON
|
||||
|
||||
# 时间点恢复基础:binlog 由 scripts/archive-binlog.sh 每分钟归档到 OSS 备份桶。
|
||||
# 本地按 7 天自动过期,归档与本地保留策略见 scripts/backup-online.sh。
|
||||
server-id = 1
|
||||
log_bin = mysql-bin
|
||||
binlog_format = ROW
|
||||
binlog_row_image = FULL
|
||||
binlog_expire_logs_seconds = 604800
|
||||
sync_binlog = 1
|
||||
innodb_flush_log_at_trx_commit = 1
|
||||
Reference in New Issue
Block a user