refactor(storage): 对象存储从 MinIO 换成 SeaweedFS(钉 4.47)

动机:MinIO 的 GitHub 仓库已于 2026-02 归档,不再产出安全补丁,已累积多个
CVE 且不修;本项目存身份证 / 实名材料 / 用户照片,无人修 CVE 才是真正驱动因素
(Docker Hub 下架只是表象,quay.io 仍在提供)。

变更:
- 三套 compose(dev / staging / prod)的 minio 服务替换为 seaweedfs 4.47:
  覆盖 entrypoint 渲染 -s3.config 注入 identity(S3 网关默认不鉴权,必须显式开启),
  -volume.max=100 解除第二个 bucket 写入时的 InternalError。
- 新增一次性 storage-init 容器,经 filer 接口预建 public / private 桶
  (SeaweedFS PutObject 到不存在的 bucket 直接失败;全仓库原无任何 createBucket)。
- S3Config 显式开启 path-style:AWS SDK v1 默认 virtual-host 会把 bucket 拼进
  主机名,容器名在 DNS 中不存在必然失败(本机有 http_proxy 时易误判为 502 拒绝)。
- ci.yml:镜像校验与起栈步骤改为 seaweedfs + storage-init。
- 运维脚本 health-check.sh / run-uat.sh 的 MinIO 探针改为 SeaweedFS
  (S3 网关开启鉴权后匿名根请求返回 403,200/403 均算存活)。

注:path-style 与建桶属换存储的必要项;presigned URL 的 host 须对外可达这一
既有缺陷(原 minio:9000 直发客户端不可达)留待下一轮「改缺陷」处理。
This commit is contained in:
WorkBuddy
2026-09-22 14:26:48 +08:00
parent aa1114338e
commit 797b1c46cf
8 changed files with 249 additions and 76 deletions
+13 -10
View File
@@ -249,15 +249,15 @@ jobs:
working-directory: infrastructure
run: docker compose config --quiet
# 2026-09-22:compose 里的 minio 已从 Docker Hub 迁到 quay.io 并钉版
# (RELEASE.2025-09-07T16-13-09Z,版本由本步骤上一轮的实测输出确认)。
# 2026-09-22:compose 的对象存储已换成 chrislusf/seaweedfs:4.47 并钉版。
# 单独保留这一步的理由:镜像拉取失败在 compose up 里会埋进几十行输出,
# 这里能在起栈前秒级暴露「上游镜像下架」这类外部依赖失效 ——
# 上一轮正是靠日志里的 pull access denied 才定位到 MinIO 已移除 Docker Hub 仓库。
- name: 校验 minio 钉版镜像可拉取并打印版本
# 上一轮正是靠日志里的 pull access denied 才定位到 MinIO 已移除 Docker Hub 仓库
# (minio/minio 现返回 404;教训是「第三方镜像」本身就是外部依赖风险点)。
- name: 校验 seaweedfs 钉版镜像可拉取并打印版本
run: |
docker pull quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
docker run --rm quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z --version
docker pull chrislusf/seaweedfs:4.47
docker run --rm chrislusf/seaweedfs:4.47 version
# 保证库为全新:seed 用 `ON CONFLICT (username) DO NOTHING`,若卷是上一轮残留,
# 账号密码会停留在上一轮的 `E2E_OPERATOR_PASSWORD` 上 → 本轮登录 401(表现为各种业务失败)。
@@ -270,11 +270,14 @@ jobs:
working-directory: infrastructure
run: docker compose build backend
- name: 起真实栈(postgres + redis + minio + backend)
# 显式列出 minio:backend 的 depends_on 虽会隐式拉起它,但隐式依赖不利于阅读,
# 且 minio 的 healthcheck(`mc ready local`)是 backend 能否启动的前置条件。
- name: 起真实栈(postgres + redis + seaweedfs + 建桶 + backend)
# 显式列出 seaweedfs 与 storage-init:backend 的 depends_on 虽会隐式拉起它们,
# 但隐式依赖不利于阅读,且 seaweedfs 的 healthcheck 是 backend 启动的前置条件。
# storage-init 是**一次性**建桶容器 —— SeaweedFS 不会自动建桶,
# PutObject 到不存在的 bucket 会直接失败,少了它照片链路就是断的
# (E2E 对存储零覆盖,不会替你发现)。
working-directory: infrastructure
run: docker compose up -d postgres redis minio backend
run: docker compose up -d postgres redis seaweedfs storage-init backend
- name: 等待 backend 就绪(超时即失败,不静默跳过)
# 窗口 90×5s=7.5min:首次构建后还需 Spring 启动 + Flyway 46 个迁移,5min 偏紧。
@@ -37,6 +37,21 @@ public class S3Config {
new AwsClientBuilder.EndpointConfiguration(endpoint, region))
.withCredentials(new AWSStaticCredentialsProvider(
new BasicAWSCredentials(accessKey, secretKey)))
// 必须显式开启 path-style。
//
// AWS SDK v1 默认是 virtual-host 风格,会把 bucket 拼进主机名:
// endpoint=http://seaweedfs:8333 -> http://private.seaweedfs:8333/...
// 而 `seaweedfs` 是 Docker 内部服务名,`private.seaweedfs` 没有任何 DNS 记录,
// 请求必然失败。private 部署的对象存储几乎都用 IP 或容器名,
// virtual-host 天然不可用 —— path-style(http://host:port/bucket/key)才是正解。
//
// 这一点在换存储前就已埋着:MinIO 时代之所以没暴露,是因为对象存储链路
// 从未被任何测试真正走过(E2E 对存储零覆盖),而不是因为配置正确。
//
// 排查提醒:本机有 http_proxy 时,*.localhost 这类 virtual-host 域名
// 不在 NO_PROXY 内会走代理并返回 502,看起来像「存储不支持」,
// 实为代理假象 —— 判据里 502 不能当作「被拒绝」。
.withPathStyleAccessEnabled(true)
.build();
}
}
+62 -13
View File
@@ -39,23 +39,67 @@ services:
- danyang-net
restart: unless-stopped
minio:
# 同 docker-compose.yml 的 minio 条目:Docker Hub 上 minio/minio 仓库已被整体移除
# (MinIO 自 2025-10 起停止发布容器镜像,仅源码分发),生产环境同样拉不到。
# 改为仍在提供镜像的 quay.io —— 这是真实的部署阻塞,不只是 CI 问题。
# 版本与本地/CI 保持一致,钉死为 CI 实测确认可用的 RELEASE.2025-09-07T16-13-09Z。
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: danyang-match-minio
command: server /data --console-address ":9001"
seaweedfs:
# 同 docker-compose.yml:对象存储已从 MinIO 换成 SeaweedFS。
# 生产换的动力比 CI 更强 —— MinIO 仓库已归档、不再产出安全补丁,
# 而这里存的是身份证与实名材料。详见 docker-compose.yml 中的完整说明。
# 版本与本地/CI 一致(钉 4.47),避免各环境跑不同实现。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-seaweedfs
# 必须覆盖 entrypoint:镜像 ENTRYPOINT 是 /usr/bin/weed,只写 command 会被追加成
# `weed sh -c ...` → `weed: unknown subcommand "sh"`(本地实测踩到)。
entrypoint: ["sh", "-c"]
command:
- |
cat > /tmp/s3cfg.json <<CFG
{"identities":[{"name":"app","credentials":[{"accessKey":"$${MINIO_ACCESS_KEY}","secretKey":"$${MINIO_SECRET_KEY}"}],"actions":["Admin","Read","Write","List","Tagging"]}]}
CFG
# -volume.max=100:bucket 名即 collection,默认上限 8 个 volume 只够一个 bucket
# (第二个桶写入会 InternalError)。详见 docker-compose.yml 中的说明。
exec weed server -s3 -s3.port=8333 -s3.config=/tmp/s3cfg.json -dir=/data -volume.max=100
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?请设置MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?请设置MINIO_SECRET_KEY}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请设置MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请设置MINIO_SECRET_KEY}
volumes:
- minio_data:/data
- seaweedfs_data:/data
networks:
- danyang-net
healthcheck:
# 与本地一致:鉴权开启后匿名列桶返回 403,故 200/403 都算健康。
test: ["CMD-SHELL", "code=$$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8333/); [ \"$$code\" = 200 ] || [ \"$$code\" = 403 ]"]
interval: 5s
timeout: 5s
retries: 24
restart: unless-stopped
storage-init:
# SeaweedFS 不会自动建桶,PutObject 到不存在的 bucket 直接失败。
# 生产同样必须预建 public / private,否则照片与实名材料全部上传不了。
# 走 filer 目录接口,无需签名,复用同一镜像的 curl。幂等:201 新建 / 409 已存在。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-storage-init
depends_on:
seaweedfs:
condition: service_healthy
entrypoint:
- sh
- -c
- |
for b in public private; do
code=$$(curl -s -o /dev/null -w '%{http_code}' "http://seaweedfs:8888/buckets/$$b/" || echo 000)
if [ "$$code" = "200" ]; then echo "bucket $$b: already exists"; continue; fi
i=0
while [ $$i -lt 30 ]; do
code=$$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://seaweedfs:8888/buckets/$$b/" || echo 000)
case "$$code" in 200|201|409) break ;; esac
i=$$((i+1)); sleep 2
done
echo "bucket $$b: create -> $$code"
done
networks:
- danyang-net
restart: "no"
backend:
build:
context: ../backend
@@ -66,6 +110,10 @@ services:
condition: service_healthy
redis:
condition: service_healthy
seaweedfs:
condition: service_healthy
storage-init:
condition: service_completed_successfully
environment:
SPRING_PROFILES_ACTIVE: docker,production
SPRING_CONFIG_IMPORT: configtree:/run/secrets/
@@ -76,7 +124,8 @@ services:
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:?请设置REDIS_PASSWORD}
MINIO_ENDPOINT: http://minio:9000
# 变量名仍为 MINIO_*(服务端配置键未改),实际指向 SeaweedFS 的 S3 网关。
MINIO_ENDPOINT: http://seaweedfs:8333
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请设置MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请设置MINIO_SECRET_KEY}
JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET:?请设置JWT_ACCESS_SECRET}
@@ -138,7 +187,7 @@ services:
volumes:
postgres_data:
redis_data:
minio_data:
seaweedfs_data:
networks:
danyang-net:
+54 -13
View File
@@ -31,20 +31,61 @@ services:
timeout: 5s
retries: 5
minio:
# 同 docker-compose.yml 的 minio 条目:Docker Hub 上 minio/minio 仓库已被整体移除
# (MinIO 自 2025-10 起停止发布容器镜像,仅源码分发),staging 环境同样拉不到。
# 改为仍在提供镜像的 quay.io,并钉死为与本地/CI 一致的 RELEASE.2025-09-07T16-13-09Z。
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
command: server /data --console-address ":9001"
seaweedfs:
# 同 docker-compose.yml:对象存储已从 MinIO 换成 SeaweedFS(钉 4.47)。
# staging 与本地/生产保持同一实现,避免「只有预发用的是另一套存储」这类偏差。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-staging-seaweedfs
# 必须覆盖 entrypoint:镜像 ENTRYPOINT 是 /usr/bin/weed,只写 command 会被追加成
# `weed sh -c ...` → `weed: unknown subcommand "sh"`(本地实测踩到)。
entrypoint: ["sh", "-c"]
command:
- |
cat > /tmp/s3cfg.json <<CFG
{"identities":[{"name":"app","credentials":[{"accessKey":"$${MINIO_ACCESS_KEY}","secretKey":"$${MINIO_SECRET_KEY}"}],"actions":["Admin","Read","Write","List","Tagging"]}]}
CFG
# -volume.max=100:bucket 名即 collection,默认上限 8 个 volume 只够一个 bucket
# (第二个桶写入会 InternalError)。详见 docker-compose.yml 中的说明。
exec weed server -s3 -s3.port=8333 -s3.config=/tmp/s3cfg.json -dir=/data -volume.max=100
environment:
MINIO_ROOT_USER: staging
MINIO_ROOT_PASSWORD: staging_dev_password
MINIO_ACCESS_KEY: staging
MINIO_SECRET_KEY: staging_dev_password
ports:
- "9010:9000"
- "9011:9001"
# 与生产错开一位(8080/8081、5432/5433 同理):本地栈的 8333 已被占,故用 8334。
- "8334:8333"
- "9334:9333"
- "8889:8888"
volumes:
- minio_staging_data:/data
- seaweedfs_staging_data:/data
healthcheck:
test: ["CMD-SHELL", "code=$$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8333/); [ \"$$code\" = 200 ] || [ \"$$code\" = 403 ]"]
interval: 5s
timeout: 5s
retries: 24
storage-init:
# 同 docker-compose.yml:SeaweedFS 不自动建桶,必须预建 public / private。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-staging-storage-init
depends_on:
seaweedfs:
condition: service_healthy
entrypoint:
- sh
- -c
- |
for b in public private; do
code=$$(curl -s -o /dev/null -w '%{http_code}' "http://seaweedfs:8888/buckets/$$b/" || echo 000)
if [ "$$code" = "200" ]; then echo "bucket $$b: already exists"; continue; fi
i=0
while [ $$i -lt 30 ]; do
code=$$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://seaweedfs:8888/buckets/$$b/" || echo 000)
case "$$code" in 200|201|409) break ;; esac
i=$$((i+1)); sleep 2
done
echo "bucket $$b: create -> $$code"
done
restart: "no"
backend:
build:
@@ -66,7 +107,7 @@ services:
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: staging-redis-pass-change-me
MINIO_ENDPOINT: http://minio:9000
MINIO_ENDPOINT: http://seaweedfs:8333
MINIO_ACCESS_KEY: staging
MINIO_SECRET_KEY: staging_dev_password
JWT_ACCESS_SECRET: staging-access-secret-dev-only
@@ -103,4 +144,4 @@ services:
volumes:
postgres_staging_data:
redis_staging_data:
minio_staging_data:
seaweedfs_staging_data:
+95 -33
View File
@@ -32,44 +32,99 @@ services:
timeout: 5s
retries: 5
minio:
# 2026-09-22 修正:镜像源从 Docker Hub 迁到 quay.io。
seaweedfs:
# 2026-09-22:对象存储从 MinIO 换成 SeaweedFS。
#
# 原 `minio/minio:RELEASE.2025-09-07T16-13-09Z` 在 CI 上根本拉不到,Actions 报:
# pull access denied for minio/minio, repository does not exist
# 问题不在 tag,而在**整个仓库已从 Docker Hub 移除**:MinIO 自 2025-10 起停止发布
# 容器镜像(改为仅源码分发),其 GitHub 仓库于 2026-02 归档;Docker Hub 上
# minio/minio 与 minio/mc 现均返回 404。quay.io/minio/minio 仍在提供镜像。
# 动机不是「拉不到镜像」。MinIO 的 GitHub 仓库已于 2026-02 归档,**不再产出安全补丁**,
# 已累积 CVE-2025-62506 / CVE-2026-39414 / CVE-2026-40344 / CVE-2026-42600 且不修。
# 本项目存身份证、实名材料与用户照片 —— 无人修 CVE 才是真正的驱动因素。
# (Docker Hub 上 minio/minio 仓库自 2026-09-12 起返回 404 只是表象;quay.io 仍在提供,
# 2026-09-22 核实时发现 quay 上仍有 2026-04 的 hotfix tag,故「换源」可以再拖,
# 「无人修 CVE」不能拖。)
#
# 本机此前正是用 quay.io 的镜像重打 tag 顶替才跑通的(见 known-risks KR-01),
# 但那只是**本地绕过**、没有同步回仓库,于是「本地能起栈、CI 拉不到」。
# 现在统一改为 quay.io,让本地与 CI 拉同一个镜像,消除这处不一致。
# 选型依据来自本机实测而非文档推断:本项目只用到极窄的 S3 操作面
# —— presigned PUT/GET + putObject + 两个 bucket,无 versioning / lifecycle / policy。
# SeaweedFS 4.47 在 path-style 下这三项全部通过且**字节一致**。
#
# 版本来源(2026-09-22 于 CI 实测):迁移初期先用了 quay.io 的 latest,
# 并在 e2e job 里打印实际版本,得到
# minio version RELEASE.2025-09-07T16-13-09Z (commit-id=07c3a429bfed…)
# 即 latest 当前正指向这个版本 —— 与本仓库原先在 Docker Hub 上钉的 tag 完全一致。
# 现在据此钉死,恢复「可复现」这一原则(若日后 quay 上的 latest 滚动到别的版本,
# 本配置不受影响)。
# 长期风险:quay 上的 MinIO 镜像已冻结(最后发布 2025-09),同样可能下架,
# 届时需自建镜像或迁移到其他 S3 兼容存储 ——
# 「依赖第三方镜像」本身就是一条外部依赖风险。
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: danyang-match-minio
command: server /data --console-address ":9001"
# 必须显式启用鉴权:SeaweedFS 的 S3 网关**默认完全不鉴权** —— 实测匿名 GET 私有对象
# 200、匿名 PUT 200、匿名列举桶 200,AK/SK 形同虚设。对本项目的敏感数据不可接受。
# 故用 -s3.config 注入 identity;配好后匿名访问回到 403(已实测)。
#
# 版本钉 4.47(实测 `weed version` = 4.47 c50733600),不用 latest:
# 滚动 tag 会让 CI 拉到与本地验证时不同的镜像,属不可复现的偶发失败来源。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-seaweedfs
# 凭据不入库:compose 无法把变量插值进挂载文件的内容,故在容器内把环境变量
# 渲染成 s3cfg.json 再交给 weed。(未加引号的 heredoc 是故意的:需要展开变量。)
# 必须覆盖 entrypoint:该镜像的 ENTRYPOINT 是 /usr/bin/weed,
# 只写 command 会被追加成 `weed sh -c ...` → `weed: unknown subcommand "sh"`(实测踩到)。
entrypoint: ["sh", "-c"]
command:
- |
cat > /tmp/s3cfg.json <<CFG
{"identities":[{"name":"app","credentials":[{"accessKey":"$${MINIO_ACCESS_KEY}","secretKey":"$${MINIO_SECRET_KEY}"}],"actions":["Admin","Read","Write","List","Tagging"]}]}
CFG
# -volume.max=100 是必需的,不是调优:
# SeaweedFS 把 **bucket 名当作 collection**,每个 collection 至少要分到一个 volume。
# 而 volume server 默认上限只有 8 个(实测 /dir/status:Volumes 8、Max 8、Free 0),
# 于是第二个 bucket 写入时 master 报
# failed to find writable volumes for collection:public … topo failed to pick 1 from 0 node candidates
# 表现为 put_object 返回 InternalError —— 错误信息完全不提 volume 配额,
# 不知道这条就只会反复重试。volume 是按需增长的(/data 下空卷仅 8 字节),
# 故调大上限不占实际空间。
exec weed server -s3 -s3.port=8333 -s3.config=/tmp/s3cfg.json -dir=/data -volume.max=100
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?请在本地 .env 设置 MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?请在本地 .env 设置 MINIO_SECRET_KEY}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请在本地 .env 设置 MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请在本地 .env 设置 MINIO_SECRET_KEY}
ports:
- "9003:9000"
- "9004:9001"
- "8333:8333" # S3 网关:后端签名、客户端直传都走它
- "9333:9333" # master:集群状态 / 排查用
- "8888:8888" # filer:storage-init 建桶用
volumes:
- minio_data:/data
- seaweedfs_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
# 不能用 curl -f:鉴权开启后匿名列桶返回 403,而 403 恰恰说明网关正在服务。
# 因此显式接受 200(未鉴权时)与 403(已鉴权时)两种状态码。
test: ["CMD-SHELL", "code=$$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8333/); [ \"$$code\" = 200 ] || [ \"$$code\" = 403 ]"]
interval: 5s
timeout: 5s
retries: 5
retries: 24
storage-init:
# SeaweedFS 与 MinIO 的一个关键差异:**PutObject 到不存在的 bucket 会直接失败**
# (实测:匿名下 AccessDenied、正确凭据下 404),不会自动建桶。
# 而本项目全仓库没有任何 createBucket 代码或初始化脚本 —— 桶必须显式预建,
# 否则换完存储照片上传依然是断的(这一点在 MinIO 时代就已是既成缺陷,
# 见 tests/e2e/storage_link_probe.py 的说明)。
#
# 建桶走 filer 的目录接口(POST /buckets/<name>/),无需 SigV4 签名,
# 因此可复用 seaweedfs 镜像自带的 curl,**不引入新镜像依赖**。
# 实测该方式建的目录会被 S3 网关识别为 bucket:list_buckets 可见、
# head_bucket 200、put_object 成功,且鉴权照常生效(匿名 GET 403)。
#
# 幂等语义(实测):首次 POST=201,已存在 POST=409,GET 到目录=200。
# 三者都视为「桶已就位」,故重跑 up 不会失败。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-storage-init
depends_on:
seaweedfs:
condition: service_healthy
entrypoint:
- sh
- -c
- |
for b in public private; do
code=$$(curl -s -o /dev/null -w '%{http_code}' "http://seaweedfs:8888/buckets/$$b/" || echo 000)
if [ "$$code" = "200" ]; then echo "bucket $$b: already exists"; continue; fi
i=0
while [ $$i -lt 30 ]; do
code=$$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://seaweedfs:8888/buckets/$$b/" || echo 000)
case "$$code" in 200|201|409) break ;; esac
i=$$((i+1)); sleep 2
done
echo "bucket $$b: create -> $$code"
done
restart: "no"
backend:
build:
@@ -81,8 +136,11 @@ services:
condition: service_healthy
redis:
condition: service_healthy
minio:
seaweedfs:
condition: service_healthy
storage-init:
# 一次性容器:桶没建好时后端任何 putObject / presigned 直传都会失败。
condition: service_completed_successfully
environment:
SPRING_PROFILES_ACTIVE: docker
DB_HOST: postgres
@@ -93,7 +151,11 @@ services:
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:?请在本地 .env 设置 REDIS_PASSWORD}
MINIO_ENDPOINT: http://minio:9000
# 变量名仍沿用 MINIO_* 前缀(服务端 Bean 与 application-*.yml 都在读它),
# 但它现在指向 SeaweedFS 的 S3 网关 —— 属历史命名,待下一轮统一改名:
# 那时会同时处理「presigned URL 的 host 必须是对外可达地址」这一缺陷,
# 届时 endpoint 语义会从单一值拆成「内网签发地址 + 外网访问地址」。
MINIO_ENDPOINT: http://seaweedfs:8333
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请在本地 .env 设置 MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请在本地 .env 设置 MINIO_SECRET_KEY}
JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET:?请在本地 .env 设置 JWT_ACCESS_SECRET}
@@ -147,4 +209,4 @@ services:
volumes:
postgres_data:
redis_data:
minio_data:
seaweedfs_data:
+1 -1
View File
@@ -176,7 +176,7 @@ http {
proxy_set_header X-Real-IP $remote_addr;
}
# MinIO public bucket (read-only)
# SeaweedFS public bucket (read-only)
location /uploads/ {
proxy_pass http://backend/;
proxy_set_header Host $host;
+2 -2
View File
@@ -44,8 +44,8 @@ check_service "PostgreSQL" \
check_service "Redis" \
"redis-cli -h redis${REDIS_PASSWORD:+ -a ${REDIS_PASSWORD}} ping 2>/dev/null | grep -q PONG && echo OK || echo FAIL"
check_service "MinIO" \
"curl -sf http://minio:9000/minio/health/live 2>/dev/null | grep -q '\"status\":\"online\"' && echo OK || echo FAIL"
check_service "SeaweedFS" \
"curl -s -o /dev/null -w '%{http_code}' http://seaweedfs:8333/ 2>/dev/null | grep -qE '^(200|403)$' && echo OK || echo FAIL"
check_service "Backend API" \
"curl -sf http://localhost:8080/api/v1/health/live 2>/dev/null | grep -q success && echo OK || echo FAIL"
+7 -4
View File
@@ -70,11 +70,14 @@ else
log_result "Redis 连接" "FAIL" "Redis 不可用"
fi
# MinIO
if curl -sf "http://localhost:9000/minio/health/live" 2>/dev/null | grep -q online; then
log_result "MinIO 连接" "PASS"
# SeaweedFS(S3 网关)。开启鉴权后匿名根请求返回 403,403 同样说明网关正在服务,
# 故 200/403 都算存活。宿主端口:本地 8333 / staging 8334,可用 UAT_STORAGE_S3_PORT 覆盖。
S3_PORT="${UAT_STORAGE_S3_PORT:-8333}"
code=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:${S3_PORT}/" 2>/dev/null || echo 000)
if [ "${code}" = "200" ] || [ "${code}" = "403" ]; then
log_result "SeaweedFS 连接" "PASS"
else
log_result "MinIO 连接" "FAIL" "对象存储不可用"
log_result "SeaweedFS 连接" "FAIL" "对象存储不可用"
fi
echo ""