diff --git a/.claude/skills/aml-local-refresh/SKILL.md b/.claude/skills/aml-local-refresh/SKILL.md new file mode 100644 index 0000000..0a21e6e --- /dev/null +++ b/.claude/skills/aml-local-refresh/SKILL.md @@ -0,0 +1,138 @@ +--- +name: aml-local-refresh +description: Refresh the AML local dev stack - git pull AML_Backend, restore the newest AML-local-dev-*.bak into the mssql container as AbpAML, rebuild the httpapi-host image, and restart the API bypassing db-migrator. Use when asked to 更新/还原/重建/重启本地后端环境, "拉最新代码并还原数据库", "restore the latest bak", or when local portal endpoints 500 after the backend moved on. +--- + +# AML 本地环境刷新(拉代码 + 还原库 + 重启服务) + +一条龙刷新 `/Users/dev/projects/AML/docker-compose-local-dev/` 这套本地栈。栈本身的架构、配置来源(绕过 Nacos)、ABP 授权等背景见同目录 `readme.md`;本 skill 只讲**刷新流程**和**必须踩对的顺序**。 + +所有命令的工作目录都是: + +```bash +cd /Users/dev/projects/AML/docker-compose-local-dev +``` + +## 铁律(顺序错了必炸) + +1. **还原库前先停 host**,否则 `RESTORE` 拿不到独占锁。 +2. **永远不要对还原库跑 `db-migrator`**。`.bak` 里**没有** `__EFMigrationsHistory` 表,migrator 会从初始迁移重建表 → `SqlException 2714` → exit 133;而 compose 里 `httpapi-host depends_on db-migrator: service_completed_successfully`,migrator 一挂 host 就起不来。**起 host 必须带 `--no-deps`**。 +3. **代码更新了就必须 `build` 重建 host 镜像**。旧二进制配新库 = 列类型对不上(历史事故:`InvalidCastException: Decimal→Int32`,所有门户端点 500)。 +4. 还原后 **`seed-root-ou.sql` 必跑**(缺 root OU → 支付后建租户静默失败),`local-schema-catchup.sql` 也跑一遍(幂等)。 +5. `seed-agent-plans.sql` **默认不要跑**——先查活跃推薦人关联,有就跳过(见下)。 + +## 步骤 + +### 0. 前置检查 + +```bash +docker info >/dev/null 2>&1 || open -a Docker # 没起就拉起 Docker Desktop,等 docker info 通 +``` + +Docker 内存需 ≥16 GB(mssql 在 Apple Silicon 上是 amd64 模拟,不够会 exit 139)。 + +### 1. 拉最新后端代码 + +```bash +cd /Users/dev/projects/AML/AML_Backend && git status -sb | head -5 && git pull +``` + +- 分支通常是 `dev`。工作区有改动先问用户,别自作主张 stash。 +- `failed to get/store: -25308` 是 macOS keychain 的噪音,不影响 pull。 +- 拉完看一眼新迁移:`ls src/iCON.Abp.FX.EntityFrameworkCore.DbMigrations/Migrations/*.cs | grep -v Designer | tail -8`,留着第 4 步核对。 + +### 2. 起基础设施 + 停 host + +```bash +cd /Users/dev/projects/AML/docker-compose-local-dev +docker stop aml-httpapi-host +docker compose -f docker-compose.local.yml up -d mssql rabbitmq +# 等健康 +until [ "$(docker inspect -f '{{.State.Health.Status}}' aml-mssql 2>/dev/null)" = healthy ]; do /bin/sleep 10; done +``` + +### 3. 还原最新 .bak + +**别写死文件名**,用户会不定期换新的(文件名带日期戳,旧的会删): + +```bash +BAK=$(ls -t *.bak | head -1); echo "using: $BAK" +docker cp "$BAK" aml-mssql:/var/opt/mssql/restore.bak +# 坑:docker cp 进去是 root:root 0600,sqlservr 读不到 → "Operating system error 5(Access is denied)" +docker exec -u root aml-mssql bash -lc 'chown mssql:root /var/opt/mssql/restore.bak && chmod 644 /var/opt/mssql/restore.bak' + +docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -b -Q " +IF DB_ID('AbpAML') IS NOT NULL ALTER DATABASE [AbpAML] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; +RESTORE DATABASE [AbpAML] FROM DISK='/var/opt/mssql/restore.bak' + WITH REPLACE, RECOVERY, + MOVE 'AML-local-dev' TO '/var/opt/mssql/data/AbpAML.mdf', + MOVE 'AML-local-dev_log' TO '/var/opt/mssql/data/AbpAML_log.ldf'; +ALTER DATABASE [AbpAML] SET MULTI_USER;" +``` + +逻辑名恒为 `AML-local-dev` / `AML-local-dev_log`(不确定就先 `RESTORE FILELISTONLY`)。sqlcmd 在容器内路径是 `/opt/mssql-tools18/bin/sqlcmd`,**必须带 `-C`**。 + +### 4. 补数据 + 对齐 schema + +```bash +docker exec -i aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -b -i /dev/stdin < seed-root-ou.sql +docker exec -i aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -b -i /dev/stdin < local-schema-catchup.sql +``` + +`local-schema-catchup.sql` 输出可能是乱码(中文编码),看 exit code 即可。 + +然后拿第 1 步列出的最新几条迁移,逐个查库里缺不缺(缺了就往 `local-schema-catchup.sql` 追加幂等 DDL —— 注意 `ALTER TABLE ADD` 与引用该新列的语句不能同批次,回填要包在 `EXEC(N'...')` 里): + +```bash +docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -h -1 -W -Q " +SELECT 'SomeTable='+ISNULL(CAST(OBJECT_ID('SomeTable') AS varchar),'MISSING') + +' | SomeCol='+ISNULL(CAST(COL_LENGTH('SomeTable','SomeCol') AS varchar),'MISSING');" +``` + +> 查表存在性别用 `SELECT COUNT(*) FROM 该表`——表不存在会在编译期报 208。一律 `OBJECT_ID` / `COL_LENGTH` + `ISNULL(CAST(... AS varchar),'MISSING')`。 +> Edition 表叫 **`SaasEditions`**(不是 `AbpEditions`)。 + +推薦人脚本判断: + +```bash +docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -h -1 -W \ + -Q "SELECT COUNT(*) FROM AMLPortal_AgentUserPlans WHERE IsDeleted=0;" +``` + +> 0 才考虑 `seed-agent-plans.sql`;非 0(新库自带真实关联)**跳过**——该脚本会先软删 agent_A 的现有关联,且它预期的 agentTest2/3 已不存在(RAISERROR 中止)。 + +### 5. 重建 host 镜像 + +```bash +docker compose -f docker-compose.local.yml build httpapi-host +``` + +- 全量重建约 10–20 分钟(restore 大量 ABP 商业包 + 编译 ~40 个项目)。建议 `run_in_background` 跑,**别用 `| tail` 管道**——那会把失败的 exit code 吃掉,看起来像成功。 +- 常见失败 `error NU1301: Unable to load the service index for source https://www.myget.org/...`:sdk 镜像 CA 过期。`Dockerfile.local` 的 `build` 阶段已加 `ca-certificates` 刷新修好;若再复现,同样手法排查(`docker run --rm mcr.microsoft.com/dotnet/sdk:6.0 bash -lc 'curl -sS https://www.myget.org/F/blazorise/api/v3/index.json'`)。 +- 编译 OOM(exit 137):确认 Docker 内存 ≥16 GB。 + +### 6. 起 host(绕过 migrator) + +```bash +docker compose -f docker-compose.local.yml up -d --no-build --no-deps httpapi-host +``` + +### 7. 冒烟验证 + +```bash +docker compose -f docker-compose.local.yml ps +curl -s -o /dev/null -w '%{http_code}\n' http://localhost:44331/swagger/index.html +# 门户端点是 POST,GET 返 405 +curl -s -X POST http://localhost:44331/api/amlPortal/Order/portal/GetEditionList -H 'Content-Type: application/json' -d '{}' | head -c 300 +curl -s -X POST http://localhost:44331/api/amlPortal/plan/portal/GetPlanList -H 'Content-Type: application/json' -d '{}' | head -c 300 +``` + +期望 `{"code":0,...}`。 + +- **一律 `http://localhost:44331`**,`https` 会报 `tlsv1 alert protocol version` / `000`,看着像挂了其实只是协议不对。 +- host 起不来先看日志:Release 构建只写文件,`docker logs` 是空的 → `docker cp aml-httpapi-host:/app/Logs ./Logs`。 +- exit 214 / `ABP-LIC-0008` = ABP 授权令牌缺失,见 `readme.md` 第 3 节。 + +## 报告给用户 + +刷新完说清楚四件事:拉到的 commit、用的 .bak 文件名、库里的数量(表 / Plans / Editions / Tenants)、以及冒烟结果。 diff --git a/docker-compose-local-dev/Dockerfile.local b/docker-compose-local-dev/Dockerfile.local index 664de66..f95f34f 100644 --- a/docker-compose-local-dev/Dockerfile.local +++ b/docker-compose-local-dev/Dockerfile.local @@ -27,6 +27,15 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src +# The stock sdk:6.0 trust store is stale: www.myget.org (the BlazoriseMyGet feed +# in NuGet.Config) now serves a GlobalSign AlphaSSL 2025 cert the image cannot +# validate, so `dotnet restore` dies with NU1301 "Unable to load the service +# index". Refresh the CA bundle (same fix as `runtime-base` below). +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ + && update-ca-certificates \ + && rm -rf /var/lib/apt/lists/* + # NuGet.Config carries the ABP commercial feed (the account key is embedded in the URL). COPY NuGet.Config ./ COPY . .