feat(docker): add local docker-compose dev stack for AML_Backend

在 docker-compose-local-dev/ 下一键拉起 HttpApi.Host 的自包含本地开发栈
  (mssql + rabbitmq + db-migrator + httpapi-host),构建上下文为仓库根目录。

  - Dockerfile.local:.NET 6 多阶段构建,产出 host / migrator 两个目标
    (替代已过时、写着 .NET 5 且 COPY 不存在文件的 CI Dockerfile)
  - 绕过 Nacos:最新代码把配置切到 Nacos(192.168.1.120:8848,本机不可达);
    构建时用 appsettings.local.json(Nacos 改造前的完整配置快照,不含 NacosConfig)
    覆盖镜像内 appsettings.json,再由 compose 环境变量指向本地服务
  - RabbitMQ:内置 rabbitmq:3.13-management(vhost bthost)并开启 consumer,
    跑通数据采集/报告生成/AI检测/门户订单等异步链路;宿主端口错开为 5673/15673
  - iCS:服务端不在本仓库,iCSRootUrl 指向共享 stag(stag-abp-api.iconsz.com);
    runtime-base 阶段刷新 CA 库,修复 ZeroSSL 部分证书链导致的 HttpClient TLS 握手失败
  - Dockerfile.local.dockerignore:本地专用忽略表,覆盖会排除 appsettings.json 的根 .dockerignore
  - 关闭 SanctionJob(依赖本地未部署的 Elasticsearch)
main
fengruixiang 2026-07-03 11:11:09 +08:00
parent 1ed3fea21c
commit 13fdb70003
6 changed files with 2074 additions and 28 deletions

View File

@ -1,5 +1,7 @@
# CLAUDE.md # CLAUDE.md
人与助手之间的语言交互默认为简体中文。
python 要使用 uv run 运行。
不要随意创建说明文档(比如 .md),除非用户有要求。
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
需要遵守:@AGENTS.md 需要遵守:@AGENTS.md

View File

@ -6,8 +6,17 @@
# - Targets .NET 6 (the projects are net6.0; the CI Dockerfile still says 5.0). # - Targets .NET 6 (the projects are net6.0; the CI Dockerfile still says 5.0).
# - Does not COPY the non-existent src/access-token.bin. # - Does not COPY the non-existent src/access-token.bin.
# - Produces two runtime targets: `host` (the API) and `migrator` (data init). # - Produces two runtime targets: `host` (the API) and `migrator` (data init).
# - Bakes docker-compose-local-dev/appsettings.local.json over appsettings.json.
# The repo's appsettings.json now only carries a NacosConfig section and pulls
# the real settings from a Nacos server (192.168.1.120:8848) that is not
# reachable in local dev. Program.cs only loads Nacos when a NacosConfig
# section exists, so replacing appsettings.json with a Nacos-free, fully
# self-contained config makes the app start without Nacos.
# #
# Build context = repository root (AML_Backend). See Dockerfile.local.dockerignore. # Build context = repository root (AML_Backend); this Dockerfile lives one level
# down in docker-compose-local-dev/. BuildKit uses the sibling
# docker-compose-local-dev/Dockerfile.local.dockerignore (not the repo-root
# .dockerignore), which keeps appsettings.json and docker-compose-local-dev/.
############################ ############################
# Restore + build (whole solution available) # Restore + build (whole solution available)
@ -45,12 +54,28 @@ RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish "src/iCON.Abp.FX.DbMigrator/iCON.Abp.FX.DbMigrator.csproj" \ dotnet publish "src/iCON.Abp.FX.DbMigrator/iCON.Abp.FX.DbMigrator.csproj" \
-c "$BUILD_CONFIGURATION" -o /app/migrator /p:UseAppHost=false -m:1 -c "$BUILD_CONFIGURATION" -o /app/migrator /p:UseAppHost=false -m:1
############################
# Runtime base: refresh CA certificates so outbound HTTPS to services whose
# chain the stock image can't complete (e.g. the stag iCS at *.iconsz.com,
# ZeroSSL/Sectigo-issued -> "PartialChain") validates. The app's own cert
# bypass only covers legacy WebClient (ServicePointManager), not HttpClient,
# so we fix trust at the OS level to cover every code path.
############################
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime-base
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& update-ca-certificates \
&& rm -rf /var/lib/apt/lists/*
############################ ############################
# Runtime: API host # Runtime: API host
############################ ############################
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS host FROM runtime-base AS host
WORKDIR /app WORKDIR /app
COPY --from=publish-host /app/host ./ COPY --from=publish-host /app/host ./
# Local dev: replace the Nacos-only appsettings.json with a full self-contained
# config (no NacosConfig section -> the app does not try to reach Nacos).
COPY docker-compose-local-dev/appsettings.local.json ./appsettings.json
EXPOSE 44331 EXPOSE 44331
ENTRYPOINT ["dotnet", "iCON.Abp.FX.HttpApi.Host.dll"] ENTRYPOINT ["dotnet", "iCON.Abp.FX.HttpApi.Host.dll"]
@ -59,10 +84,12 @@ ENTRYPOINT ["dotnet", "iCON.Abp.FX.HttpApi.Host.dll"]
# Seed contributors read data files and the full AppConfig relative to the # Seed contributors read data files and the full AppConfig relative to the
# app base directory, so we reuse the host's appsettings.json / Data / wwwroot. # app base directory, so we reuse the host's appsettings.json / Data / wwwroot.
############################ ############################
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS migrator FROM runtime-base AS migrator
WORKDIR /app WORKDIR /app
COPY --from=publish-migrator /app/migrator ./ COPY --from=publish-migrator /app/migrator ./
COPY --from=publish-host /app/host/appsettings.json ./appsettings.json # Same Nacos-free local config as the host (the migrator's own appsettings.json
# is Nacos-only too); reuse the host's seed Data / wwwroot content.
COPY docker-compose-local-dev/appsettings.local.json ./appsettings.json
COPY --from=publish-host /app/host/Data ./Data COPY --from=publish-host /app/host/Data ./Data
COPY --from=publish-host /app/host/wwwroot ./wwwroot COPY --from=publish-host /app/host/wwwroot ./wwwroot
ENTRYPOINT ["dotnet", "iCON.Abp.FX.DbMigrator.dll"] ENTRYPOINT ["dotnet", "iCON.Abp.FX.DbMigrator.dll"]

File diff suppressed because it is too large Load Diff

View File

@ -2,28 +2,40 @@
用 docker-compose 在本地一键拉起 `iCON.Abp.FX.HttpApi.Host`,包含 SQL Server 数据库容器,并自动完成数据库迁移与种子数据初始化,用于本地开发调试。 用 docker-compose 在本地一键拉起 `iCON.Abp.FX.HttpApi.Host`,包含 SQL Server 数据库容器,并自动完成数据库迁移与种子数据初始化,用于本地开发调试。
> 适用平台macOS含 Apple Silicon / arm64。相关文件均位于 `AML_Backend/` 目录下。 > 适用平台macOS含 Apple Silicon / arm64。相关文件均位于 `AML_Backend/docker-compose-local-dev/` 目录下构建上下文build context是上一级的仓库根目录 `AML_Backend/`
--- ---
## 1. 架构概览 ## 1. 架构概览
`docker-compose.local.yml` 定义个服务: `docker-compose.local.yml` 定义个服务:
| 服务 | 作用 | 说明 | | 服务 | 作用 | 说明 |
|------|------|------| |------|------|------|
| `mssql` | SQL Server 2022 数据库 | arm64 上通过 `platform: linux/amd64`Rosetta 模拟)运行;数据持久化在命名卷 `mssql-data` | | `mssql` | SQL Server 2022 数据库 | arm64 上通过 `platform: linux/amd64`Rosetta 模拟)运行;数据持久化在命名卷 `mssql-data` |
| `rabbitmq` | 消息队列(异步任务中枢) | `rabbitmq:3.13-management`arm64 原生);`host`/`consumer` 靠它跑数据采集、报告生成、AI 检测、门户订单等异步链路。宿主端口错开为 `5673`/`15673`(见第 8 节) |
| `db-migrator` | 数据初始化 | 运行一次:执行 EF Core 迁移 + 种子数据完成后退出exit 0 | | `db-migrator` | 数据初始化 | 运行一次:执行 EF Core 迁移 + 种子数据完成后退出exit 0 |
| `httpapi-host` | 后端 API 主机 | 等 `db-migrator` 成功后才启动;对外暴露 `44331` | | `httpapi-host` | 后端 API 主机 | 等 `db-migrator` 成功后才启动;对外暴露 `44331`;随宿主启动 `RabbitMQConsumerService` 消费上述队列 |
启动顺序由健康检查与 `depends_on` 保证:`mssql` 健康 → `db-migrator` 跑完 → `httpapi-host` 启动。 启动顺序由健康检查与 `depends_on` 保证:`mssql` + `rabbitmq` 健康 → `db-migrator` 跑完 → `httpapi-host` 启动。
配套文件: 配套文件(均在 `docker-compose-local-dev/` 内)
- `Dockerfile.local`:本地多阶段构建镜像(**.NET 6**),产出 `host``migrator` 两个目标。 - `Dockerfile.local`:本地多阶段构建镜像(**.NET 6**),产出 `host``migrator` 两个目标。
- `Dockerfile.local.dockerignore`:本地构建专用忽略表(保留 `appsettings.json`,仅排除 `bin/obj/.git` 等)。 - `Dockerfile.local.dockerignore`:本地构建专用忽略表(仅排除 `bin/obj/.git`。BuildKit 会优先采用「与 Dockerfile 同目录、同名 + `.dockerignore`」的这个文件,从而**覆盖**仓库根目录的 `.dockerignore`(根目录那个会把 `appsettings.json`、`docker-compose*` 都排除掉,不适用于本地构建)。
- `appsettings.local.json`**本地专用、绕过 Nacos 的完整配置**(见第 1.1 节),构建时会被覆盖进镜像的 `appsettings.json`
> ⚠️ 仓库自带的 CI 用 `src/iCON.Abp.FX.HttpApi.Host/Dockerfile` 已过时(写的是 .NET 5、且 COPY 一个不存在的 `src/access-token.bin`**不要**用它做本地构建,请使用 `Dockerfile.local` > ⚠️ 仓库自带的 CI 用 `src/iCON.Abp.FX.HttpApi.Host/Dockerfile` 已过时(写的是 .NET 5、且 COPY 一个不存在的 `src/access-token.bin`**不要**用它做本地构建,请使用 `Dockerfile.local`
### 1.1 配置来源:为什么要绕过 Nacos
最新代码已把配置中心切到 **Nacos**`src/iCON.Abp.FX.HttpApi.Host/appsettings.json` 现在只剩一个 `NacosConfig`真正的配置连接串、AuthServer、AppConfig、RabbitMQ、任务开关等都从 Nacos 服务器 `http://192.168.1.120:8848`DataId `aml.dev.json`)拉取。
该 Nacos 地址是公司内网,本机通常不可达;且它指向的是共享 dev 环境(连接串指向远程 dev 库),与「本地自包含隔离栈」的目标冲突。
`Program.cs` 里 Nacos 是**条件加载**的——仅当配置里存在 `NacosConfig` 段才会 `AddNacosV2Configuration`。因此本地方案是:用 `appsettings.local.json`(一份**不含 `NacosConfig`、但包含完整配置**的文件)覆盖镜像里的 `appsettings.json`App 便不再尝试连接 Nacos直接用本地配置启动再由 compose 的环境变量把连接串等指向本地 `mssql` 容器。
> `appsettings.local.json` 取自 Nacos 改造前commit `297d3bb6` 的父提交)那版完整 `appsettings.json`,是目前唯一可自包含运行的完整配置快照。若今后 Nacos 里新增了启动必需的配置项,需要手动同步补进这个文件。
--- ---
## 2. 前置条件 ## 2. 前置条件
@ -77,10 +89,12 @@ volumes:
## 4. 一键启动 ## 4. 一键启动
```bash ```bash
cd AML_Backend cd AML_Backend/docker-compose-local-dev
docker compose -f docker-compose.local.yml up -d --build docker compose -f docker-compose.local.yml up -d --build
``` ```
> 也可在仓库根目录运行:`cd AML_Backend && docker compose -f docker-compose-local-dev/docker-compose.local.yml up -d --build`。
首次构建较慢(需还原大量 ABP 商业 NuGet 包并编译约 40 个项目,并拉取 SQL Server 镜像)。 首次构建较慢(需还原大量 ABP 商业 NuGet 包并编译约 40 个项目,并拉取 SQL Server 镜像)。
构建已做内存优化(`-m:1` 单项目编译 + NuGet 缓存挂载),避免 Roslyn 因内存不足被杀OOM / exit 137 构建已做内存优化(`-m:1` 单项目编译 + NuGet 缓存挂载),避免 Roslyn 因内存不足被杀OOM / exit 137
@ -100,6 +114,7 @@ docker compose -f docker-compose.local.yml ps
| Swagger | http://localhost:44331/swagger FX API + AMLPortal API | | Swagger | http://localhost:44331/swagger FX API + AMLPortal API |
| OpenID 配置 | http://localhost:44331/.well-known/openid-configuration | | OpenID 配置 | http://localhost:44331/.well-known/openid-configuration |
| 数据库 | `localhost,11433`,用户 `sa`,密码 `Aml@Local2026`,库名 `AbpAML` | | 数据库 | `localhost,11433`,用户 `sa`,密码 `Aml@Local2026`,库名 `AbpAML` |
| RabbitMQ 管理台 | http://localhost:15673 ,用户 `admin` / 密码 `123456`vhost `bthost` |
| 应用管理员 | `admin@abp.io` / `1q2w3E*` | | 应用管理员 | `admin@abp.io` / `1q2w3E*` |
种子数据包含管理员账号、249 个国家、155 种货币、转账类型、文本模板、检测阈值等(共约 124 张表)。 种子数据包含管理员账号、249 个国家、155 种货币、转账类型、文本模板、检测阈值等(共约 124 张表)。
@ -109,7 +124,7 @@ docker compose -f docker-compose.local.yml ps
## 6. 常用操作 ## 6. 常用操作
```bash ```bash
cd AML_Backend cd AML_Backend/docker-compose-local-dev
# 查看后端日志注意Release 构建日志写文件docker logs 可能为空,见下) # 查看后端日志注意Release 构建日志写文件docker logs 可能为空,见下)
docker compose -f docker-compose.local.yml logs -f httpapi-host docker compose -f docker-compose.local.yml logs -f httpapi-host
@ -143,9 +158,12 @@ docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd \
- `ASPNETCORE_URLS=http://+:44331``App__SelfUrl` / `AuthServer__Authority=http://localhost:44331`(保证 IdentityServer 颁发者与浏览器访问地址一致),`AuthServer__RequireHttpsMetadata=false`。 - `ASPNETCORE_URLS=http://+:44331``App__SelfUrl` / `AuthServer__Authority=http://localhost:44331`(保证 IdentityServer 颁发者与浏览器访问地址一致),`AuthServer__RequireHttpsMetadata=false`。
- `ConnectionStrings__Default` 指向 `mssql` 服务(覆盖 appsettings 里的 `localhost`)。 - `ConnectionStrings__Default` 指向 `mssql` 服务(覆盖 appsettings 里的 `localhost`)。
- 屏蔽不可达的外部集成:`AppConfig__RabbitMQConfig__HostName=127.0.0.1`(快速失败)、`...__EnableConsumer=false`;关闭 `SanctionJob`、`EngineListSyncJob`。 - **RabbitMQ**`AppConfig__RabbitMQConfig__HostName=rabbitmq`(指向 compose 内的 broker、`...__EnableConsumer=true`(开启后台消费)。账号/vhost`admin`/`123456`/`bthost`)沿用 appsettings.local.json`rabbitmq` 服务的 `RABBITMQ_DEFAULT_*` 一致。
- **iCS**`AppConfig__iCS__iCSRootUrl=https://stag-abp-api.iconsz.com/`iCS 服务端不在本仓库,指向共享 stag 实例,让文档/问卷/检测引擎等功能可用。iCS 用的是 ZeroSSL/Sectigo 证书、且服务器只下发部分证书链;`Dockerfile.local` 的 `runtime-base` 阶段刷新了容器 CA 库,否则 HttpClient 会因 `PartialChain` 握手失败(应用自带的证书旁路只作用于旧式 WebClient不管 HttpClient
> 依赖 stag 那边的 iCS 凭证有效;若 stag 不可达或换了证书,相关功能会失败但不影响 host 启动。
- 仍关闭 `SanctionJob`(依赖本地未部署的 Elasticsearch。`EngineListSyncJob` 因 iCS 已指向 stag 而恢复默认启用。
> 仍有部分检测功能依赖 Elasticsearch / iCS 等远程或局域网服务,本 compose 未包含。如需本地联调这些功能,需另行配置对应服务地址。 > 仍有部分检测功能依赖 Elasticsearch`192.168.1.120:9200` 等)等远程/局域网服务,本 compose 未包含。如需本地联调,需另行配置对应服务地址。
--- ---
@ -158,15 +176,22 @@ docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd \
| 构建时 `csc.dll exited with code 137` / `cannot allocate memory` | 编译 `DbMigrations` 大项目时 OOM | 已用 `-m:1` 缓解;确保 Docker 内存充足,必要时串行构建 | | 构建时 `csc.dll exited with code 137` / `cannot allocate memory` | 编译 `DbMigrations` 大项目时 OOM | 已用 `-m:1` 缓解;确保 Docker 内存充足,必要时串行构建 |
| `docker logs httpapi-host` 为空但服务异常 | Release 构建只写文件日志(`#if DEBUG` 才有控制台 sink日志在容器内 `/app/Logs/<级别>/<日期>/` | `docker cp aml-httpapi-host:/app/Logs ./Logs` 后查看,或临时用 `--build-arg BUILD_CONFIGURATION=Debug` 构建以获得控制台日志 | | `docker logs httpapi-host` 为空但服务异常 | Release 构建只写文件日志(`#if DEBUG` 才有控制台 sink日志在容器内 `/app/Logs/<级别>/<日期>/` | `docker cp aml-httpapi-host:/app/Logs ./Logs` 后查看,或临时用 `--build-arg BUILD_CONFIGURATION=Debug` 构建以获得控制台日志 |
| 访问 Swagger 401/跳转异常 | `AuthServer:Authority` 与实际访问地址不一致 | 确认仍是 `http://localhost:44331`,端口映射 `44331:44331` 未改 | | 访问 Swagger 401/跳转异常 | `AuthServer:Authority` 与实际访问地址不一致 | 确认仍是 `http://localhost:44331`,端口映射 `44331:44331` 未改 |
| 启动报 `Bind for 0.0.0.0:5672 failed: port is already allocated` | 宿主已有其它 RabbitMQ 占用 5672/15672 | 本 compose 已把宿主端口错开为 `5673`/`15673`;若仍冲突,改 `rabbitmq.ports` 即可(容器间通信走内部 5672不受影响 |
| 日志含 `SSL connection could not be established ... PartialChain` | 容器 CA 库无法补全 iCS(stag) 的 ZeroSSL 证书链,且 HttpClient 不认应用的证书旁路 | 由 `Dockerfile.local``runtime-base` 刷新 CA 库解决;若复现,确认镜像是最新构建(`--build`|
| iCS 功能报 401/token 失败(非 TLS | stag 那边的 iCS 凭证/租户失效 | 与后端确认 `AppConfig:iCS:Credential` 在 stag 是否有效;非致命,不影响 host |
--- ---
## 9. 相关文件清单 ## 9. 相关文件清单
``` ```
AML_Backend/ AML_Backend/ # 构建上下文build context根目录
├─ NuGet.Config # ABP 商业源Dockerfile 会 COPY
├─ src/iCON.Abp.FX.HttpApi.Host/ # API 主机工程
└─ docker-compose-local-dev/ # ← 本地开发相关文件都在这里
├─ docker-compose.local.yml # 本地三服务编排(本说明对应文件) ├─ docker-compose.local.yml # 本地三服务编排(本说明对应文件)
├─ Dockerfile.local # 本地 .NET 6 多阶段构建host / migrator 两个目标) ├─ Dockerfile.local # 本地 .NET 6 多阶段构建host / migrator 两个目标)
├─ Dockerfile.local.dockerignore # 本地构建忽略表(保留 appsettings.json ├─ Dockerfile.local.dockerignore # 本地构建忽略表(覆盖根目录 .dockerignore
└─ src/iCON.Abp.FX.HttpApi.Host/ # API 主机工程 ├─ appsettings.local.json # 绕过 Nacos 的完整本地配置(覆盖进镜像)
└─ docker-compose-local-dev.md # 本说明文档
``` ```

View File

@ -33,10 +33,33 @@ services:
retries: 30 retries: 30
start_period: 60s start_period: 60s
rabbitmq:
image: rabbitmq:3.13-management # has native arm64, no platform override needed
container_name: aml-rabbitmq
environment:
# Match RabbitMQConfig in appsettings.local.json (user/pass/vhost).
RABBITMQ_DEFAULT_USER: "admin"
RABBITMQ_DEFAULT_PASS: "123456"
RABBITMQ_DEFAULT_VHOST: "bthost"
ports:
# Host ports are shifted to avoid clashing with any other local RabbitMQ
# (e.g. a general dev-infra broker already on 5672/15672). The API talks to
# this broker over the compose network via internal port 5672 regardless.
- "5673:5672" # AMQP (host 5673 -> container 5672)
- "15673:15672" # management UI (http://localhost:15673 admin/123456)
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 20
start_period: 30s
db-migrator: db-migrator:
build: build:
context: . # Context is the AML_Backend repo root (one level up); this compose file and
dockerfile: Dockerfile.local # the Dockerfile live in docker-compose-local-dev/.
context: ..
dockerfile: docker-compose-local-dev/Dockerfile.local
target: migrator target: migrator
image: aml-dbmigrator:local image: aml-dbmigrator:local
container_name: aml-dbmigrator container_name: aml-dbmigrator
@ -50,14 +73,16 @@ services:
httpapi-host: httpapi-host:
build: build:
context: . context: ..
dockerfile: Dockerfile.local dockerfile: docker-compose-local-dev/Dockerfile.local
target: host target: host
image: aml-httpapi-host:local image: aml-httpapi-host:local
container_name: aml-httpapi-host container_name: aml-httpapi-host
depends_on: depends_on:
mssql: mssql:
condition: service_healthy condition: service_healthy
rabbitmq:
condition: service_healthy
db-migrator: db-migrator:
condition: service_completed_successfully condition: service_completed_successfully
ports: ports:
@ -75,11 +100,15 @@ services:
App__SelfUrl: "http://localhost:44331" App__SelfUrl: "http://localhost:44331"
AuthServer__Authority: "http://localhost:44331" AuthServer__Authority: "http://localhost:44331"
AuthServer__RequireHttpsMetadata: "false" AuthServer__RequireHttpsMetadata: "false"
# Silence integrations that point at unreachable infra in local dev. # RabbitMQ: use the bundled broker service and run the background consumer.
AppConfig__RabbitMQConfig__EnableConsumer: "false" AppConfig__RabbitMQConfig__HostName: "rabbitmq"
AppConfig__RabbitMQConfig__HostName: "127.0.0.1" AppConfig__RabbitMQConfig__EnableConsumer: "true"
# iCS is a separate iCON service (not in this repo). Point at the shared stag
# instance so document/survey/engine features work without running iCS locally.
# (Requires the iCS credentials in appsettings.local.json to be valid on stag.)
AppConfig__iCS__iCSRootUrl: "https://stag-abp-api.iconsz.com/"
# Still unavailable locally: Elasticsearch-backed sanction sync.
AppConfig__JobConfig__SanctionJob__Enabled: "false" AppConfig__JobConfig__SanctionJob__Enabled: "false"
AppConfig__JobConfig__EngineListSyncJob__Enabled: "false"
restart: unless-stopped restart: unless-stopped
volumes: volumes: