276 lines
19 KiB
Markdown
276 lines
19 KiB
Markdown
---
|
||
name: aml-local-refresh
|
||
description: Refresh the AML local dev stack - git pull AML_Backend (and AML_Frontend), 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. Also covers running AML_Frontend against that local backend (npm run start:local, login admin, on the Mac or on FG-W11-ICON-DEV via a reverse SSH tunnel), hot-pushing frontend edits to that box, and watching frontend console/network (remote Chrome) plus backend file logs while the user tests by hand. Use when asked to 更新/还原/重建/重启本地后端环境, "拉最新代码并还原数据库", "restore the latest bak", 前端连本地后端/本地登录, 在 Windows 那台跑 AML 前端, 联调时看前后端日志/监控扫描仪测试, 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 步核对。
|
||
|
||
### 1b. 同步拉前端代码(AML_Frontend)
|
||
|
||
后端功能(批量操作、电话区号之类)通常前后端成对提交,只更后端会让前端对不上。前端不在 docker 栈里,这一步**只拉代码**:
|
||
|
||
```bash
|
||
cd /Users/dev/projects/AML/AML_Frontend && git status -sb | head -5
|
||
OLD=$(git rev-parse HEAD) && git pull --ff-only
|
||
git log -1 --format='%h %s'
|
||
git diff --name-only $OLD HEAD -- package.json # 有输出 = 依赖变了
|
||
```
|
||
|
||
- 分支是 **`dev`**,和后端一样。团队在 dev 上提交,再通过 PR 合进 `main`,所以 dev 上的代码永远不比 main 旧。本地如果还停在 main,先确认 `git rev-list --count origin/dev..dev` 是 0(本地 dev 没有未推的 commit),再 `git fetch . origin/dev:dev` 快进,然后**单独跑一条** `git switch dev`,确认切换成功后再 pull。工作区有改动同样先问用户。
|
||
- 往前端提交时也落在 dev 上,**不要直接推 main**。切分支、commit、push 要分开跑,每一步都确认分支对了再继续。之前就因为把它们串在一条命令里,switch 失败后 commit 被推到了 main。
|
||
- `package-lock.json` 被 gitignore 了,所以只能看 `package.json` 有没有变。
|
||
- 依赖变了就装一次。系统 Node 是 v24,Angular 11 不支持,所以前端一律通过 **fnm 用 Node 14(x64,跑在 Rosetta 上)**:`AML_Frontend/.node-version` 写的是 `14`(只在本地,已加进 `.git/info/exclude`)。非交互 shell 里不会自动切版本,要显式写:
|
||
|
||
```bash
|
||
fnm exec --using 14 -- npm install --legacy-peer-deps
|
||
fnm exec --using 14 -- npm run build:dev # 可选验证,约 2 分钟
|
||
```
|
||
|
||
`watchpack-chokidar2/fsevents` 的 `gyp ERR!` 是可选依赖编译失败,npm 会跳过,exit 0 就算成功。
|
||
|
||
### 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 节。
|
||
|
||
## 前端联调(AML_Frontend 连本地后端)
|
||
|
||
**登录账号**:宿主层管理员 `admin`,租户**留空(Host)**。
|
||
- 还原 `.bak` 的库(本 skill 的正常流程):密码 **`1qaz@WSX`**。
|
||
- 用 db-migrator 种子新建的库:密码 `1q2w3E*`(ABP 默认,readme 里也记了这个)。
|
||
|
||
库里还有几十个租户下也叫 `admin` 的账号,那些是真实客户的,别拿来试。
|
||
|
||
**有些功能宿主账号用不了,得用租户账号**:宿主账号没有租户 ID,前端不会请求 `GetTenantProperty`,所有租户开关(如 `enableKYC`)都按 false 处理。例:V2 新客户页(`#/customer-v2/...`)的 OCR 录入会打出 `[扫描仪] 租户未开通 KYC…`,不检测扫描仪。这时让用户换成开了 KYC 的测试租户登录:`million1`(用户 2026-11-03 用它测过扫描仪)、`crmTest1`、`cpa1`。查哪些租户开了 KYC:`AMLPortal_TenantPropertys.EnableKYC=1`,用 `TargetTenantID` 关联 `SaasTenants.Id`。租户密码我们不知道,由用户自己登录。
|
||
|
||
### 关键约束:issuer 必须是 `http://localhost:44331`
|
||
|
||
- 后端的 `AuthServer__Authority` / `App__SelfUrl` 固定是 `http://localhost:44331`。IdentityServer 的 issuer 跟着**请求的主机名**走:用 IP 访问时,发现文档里的 issuer 会变成 `http://192.168.x.x:44331`,签出来的 token 会被 API 拒掉(401)。所以**无论前端跑在哪台机器上,浏览器都必须通过 `localhost:44331` 访问后端**。
|
||
- 前端默认的 `environment.ts` 写的是 `https://localhost:44331`,而本地 host 只提供 http,所以用默认配置连本地后端是登录不了的。要用 **`npm run start:local`**:angular.json 里的 `local` 配置会把它换成 `src/environments/environment.local.ts`,全部指向 http。
|
||
- 后端这边已经就绪,不用改:CORS 放行了 `http://localhost:4200`,`FX_App` client 允许 `password` 授权、不需要 secret、回调地址里有 `http://localhost:4200`。前端的 `loginMode = 'password'` 用的就是密码模式。
|
||
- 排查顺序:先看 `http://localhost:44331/.well-known/openid-configuration` 里的 issuer,再用 `Origin: http://localhost:4200` 对 `/connect/token` 发一个 OPTIONS 预检(期望 204),最后看浏览器 Network 里 `/connect/token` 的响应。
|
||
|
||
### 在 Mac 上跑
|
||
|
||
```bash
|
||
cd /Users/dev/projects/AML/AML_Frontend
|
||
fnm exec --using 14 -- npm run start:local # 编译约 2 分钟,浏览器打开 http://localhost:4200
|
||
```
|
||
|
||
验证配置是否生效:`curl -s http://localhost:4200/main.js | grep -o "issuer: *'[^']*'"`,应该输出 `http://localhost:44331`。
|
||
|
||
### 在 FG-W11-ICON-DEV(Windows)上跑
|
||
|
||
用户有些测试要在那台机器上做。操作远程机器走 `remote-dev-box` skill(先 `boxes.sh locate -H FG-W11-ICON-DEV`,IP 会变)。现状:
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 代码 | `C:/Projects/AML_Frontend`,**不是 git 仓库**,是从 Mac 推过去的快照 |
|
||
| Node | 便携版 `C:/Tools/node-v14.21.3-win-x64`(系统里的 Node 22 不动) |
|
||
| 启动脚本 | `C:/Tools/start-aml-frontend.cmd`(设好了 PATH 和 4 GB 堆上限,用户双击就能起) |
|
||
| 日志 | `C:/Tools/aml-frontend-serve.log` |
|
||
|
||
**1. 后端:由 Mac 起反向隧道**,让 Windows 上的 `localhost:44331` 通到 Mac 的 docker host:
|
||
|
||
```bash
|
||
ssh -fNT -o ControlPath=none -o ExitOnForwardFailure=yes -R 44331:127.0.0.1:44331 FG-W11-ICON-DEV
|
||
```
|
||
|
||
- 这条 auto mode 不让 Claude 跑,**要请用户自己在提示符里用 `!` 运行**,并提醒他们**整行粘贴**:之前在 `-R` 后面断行,结果报了 `option requires an argument -- R`。
|
||
- `ControlPath=none` 不能省:`~/.ssh/config` 里给这台机器开了 `ControlMaster auto` + `ControlPersist 10m`,不关的话,转发会挂在 run.sh 的复用连接上,10 分钟就断。
|
||
- Mac 睡眠、断网或重启后隧道会断,重跑即可。关闭:`pkill -f "R 44331"`。
|
||
- 验证(在 Windows 上):`Invoke-WebRequest -UseBasicParsing http://localhost:44331/.well-known/openid-configuration`,issuer 应该是 `http://localhost:44331`。
|
||
|
||
**2. 更新代码**(Mac 上的前端有新提交,或 start:local 相关文件改了时):把 git 跟踪的文件和未跟踪的新文件一起打包推过去,不带 node_modules:
|
||
|
||
```bash
|
||
cd /Users/dev/projects/AML/AML_Frontend
|
||
git ls-files -co --exclude-standard -z | COPYFILE_DISABLE=1 tar --null -T - -czf "$SCRATCH/aml-frontend-src.tgz"
|
||
$S/cp.sh push -H FG-W11-ICON-DEV "$SCRATCH/aml-frontend-src.tgz" 'C:/Tools/aml-frontend-src.tgz'
|
||
# 远端:tar -xzf C:/Tools/aml-frontend-src.tgz -C C:/Projects/AML_Frontend ,然后删掉 tgz
|
||
```
|
||
|
||
覆盖解压不会删除文件。如果上游删了文件,要先清掉旧目录(node_modules 可以留着)。
|
||
|
||
**联调中途改了几个文件**,不用重新打包,逐个推过去,ng serve 会自动重新编译:
|
||
|
||
```bash
|
||
cd /Users/dev/projects/AML/AML_Frontend
|
||
for p in <改动的文件...>; do $S/cp.sh push -H FG-W11-ICON-DEV "$p" "C:/Projects/AML_Frontend/$p"; done
|
||
```
|
||
|
||
重新编译约 1–2 分钟。看日志尾部:最后一行是 `Generating browser application bundles...` 就继续等;等到出现 `Build at: <UTC 时间>` 并且晚于推送时间,再加上 `Compiled successfully`、没有 `error TS`,才算生效。然后请用户刷新页面。
|
||
|
||
**3. 装依赖**(首次安装,或 package.json 变了):
|
||
|
||
```powershell
|
||
$env:Path = 'C:/Tools/node-v14.21.3-win-x64;' + $env:Path
|
||
Set-Location C:/Projects/AML_Frontend
|
||
npm install --legacy-peer-deps --no-audit --no-fund --prefer-offline --maxsockets=6 --fetch-retries=5 *> C:/Tools/aml-npm-install.log
|
||
```
|
||
|
||
- 要用 `run_in_background` 跑,首次安装约 17 分钟。
|
||
- npm 6 在这台机器上**会假死**:进程还在、和 registry 的连接也还在,但 npm 缓存大小、CPU 时间、日志行数几分钟都不动。所以跑的过程中要隔几分钟查一次这三样,卡住就 `Stop-Process` 掉重跑。已经下载的包都在缓存里,重跑会接着用。看到 `node_modules/.staging` 里的目录数在涨,说明正在解压。
|
||
|
||
**4. 启动**:SSH 起的进程在 session 0,用户看不到,所以要用一次性计划任务在桌面会话里起:
|
||
|
||
```powershell
|
||
schtasks /Create /TN TmpAmlFrontend /TR '"C:\Tools\start-aml-frontend.cmd"' /SC ONCE /ST 23:59 /IT /F
|
||
schtasks /Run /TN TmpAmlFrontend
|
||
Start-Sleep 8; schtasks /Delete /TN TmpAmlFrontend /F
|
||
```
|
||
|
||
- **`NODE_OPTIONS=--max_old_space_size=4096` 必须设**(已经写在启动脚本里)。Node 14 默认堆上限约 2 GB,首次编译完会紧接着再构建一轮,这时会 `JavaScript heap out of memory`,ng serve 直接退出。
|
||
- 首次编译约 4–5 分钟。dev server **构建期间会挂住请求**,`Invoke-WebRequest` 看起来像超时,不是端口问题。日志最后一行如果还是 `Generating browser application bundles...`,就继续等。
|
||
- 验证:日志里有 `Compiled successfully`,且之后没有 `FATAL` / `npm ERR`;`http://127.0.0.1:4200/` 返回 200;`main.js` 里的 issuer 是 `http://localhost:44331`。起来一分钟后再查一次,前面那次 OOM 就是在第二轮构建时才崩的。
|
||
- 用户在那台机器的浏览器里打开 `http://localhost:4200` 登录。关掉桌面上那个「AML Frontend (start:local)」窗口就停了。
|
||
|
||
### 联调时监控前后端(用户手动操作页面,Claude 看日志、分析、修复)
|
||
|
||
用户不想来回粘贴日志,前后端日志都由 Claude 自己取。
|
||
|
||
**前端**:走 `remote-chrome` skill,接入用户在 FG-W11-ICON-DEV 上的 Chrome。**只读**:不跳转、不点击,页面由用户操作。
|
||
|
||
```bash
|
||
C=~/.claude/skills/remote-chrome/scripts/chrome.sh
|
||
$C call browser_console_messages '{"level":"info"}' | grep '\[扫描仪\]' | tail # 前端自己打的中文日志,[扫描仪] 前缀
|
||
$C call browser_network_requests '{"static":false,"filter":"ScanCertificateV2"}' # 列出请求,带序号
|
||
$C call browser_network_request '{"index":<序号>,"part":"request-body"}' # 也可以取 response-body
|
||
```
|
||
|
||
请求体和响应体里有客户证件信息。分析时只打印字段名、来源、命中数,**不要把值打出来**;需要留样本就放 scratchpad。
|
||
|
||
**后端**:Release 构建只写文件日志,按级别和日期分目录,日期是 UTC:`/app/Logs/<Error|Warning|Information>/<yyyy-MM-dd>/<yyyy-MM-dd>.txt`。用 Monitor 跟踪:在容器里 `tail -n 0 -F` 这三个文件,再用 awk 过滤,Information 只留 `Request finished` 里的目标接口(如 `/kyc/`)。噪音要滤掉,比如 `AuditLogCollector`、`XSRF-TOKEN`、`ABP-LIC-0013`、EF 的 `row limiting operator`。
|
||
- Monitor 最长 30 分钟,到期后要重新挂。
|
||
- **重建或重启 host 容器后,`docker exec` 会断,Monitor 随之结束**,要重新挂。
|
||
- 栈追踪在 Error 日志里,按时间 `grep -A 30` 取。
|
||
|
||
**改后端代码 → 生效**:`docker compose -f docker-compose.local.yml build httpapi-host`(增量构建约 3–5 分钟),然后 `up -d --no-build --no-deps httpapi-host`,等 swagger 返回 200。只改 `Data/AML/...` 下的 JSON 也得重建镜像,因为这些文件是打进镜像里的。
|
||
|
||
## 报告给用户
|
||
|
||
刷新完说清楚五件事:后端拉到的 commit、前端拉到的 commit(及 package.json 是否变了)、用的 .bak 文件名、库里的数量(表 / Plans / Editions / Tenants)、以及冒烟结果。
|