mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
Initial commit: reorganized source tree
- backend/: 13 Maven modules (cw-elevator-application, cloudwalk-cloud, intelligent-cwoscomponent, ninca-crk, etc.) - frontend/: 4 Vue projects (elevator-front, cwos-portal, alarm-front, front_acs) + decompiled + scripts - scripts/: build, test-env, tools (Docker Compose, service templates, API parity) - docs/: AGENTS.md, superpowers specs, architecture docs - .gitignore: standard Java/Maven exclusions Moved from legacy maven-*/ root layout to backend/ organized structure.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,396 @@
|
||||
# 数据库表结构参考手册 — 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 走查全部 5 个数据库(2 库直连 + 3 库代码推导),输出带 Mermaid ER 图 + 脱敏样本数据的 Markdown 参考手册。
|
||||
|
||||
**Architecture:** 分三层执行 — 数据库查询层(直连 INFORMATION_SCHEMA + SELECT 样本)、代码扫描层(并行读取全部 MyBatis Mapper XML 提取表/列/JOIN)、文档生成层(交叉验证 + 组装 Markdown)。直连库与代码扫描完全并行。
|
||||
|
||||
**Tech Stack:** MySQL CLI (`mysql`), Bash, Python 3(JSON 缓存), Mermaid erDiagram
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-05-01-database-schema-reference-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 前置检查
|
||||
|
||||
- [ ] **Step 0: 确认数据库可达**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 -e "SELECT VERSION(); SHOW DATABASES LIKE '%component%'; SHOW DATABASES LIKE '%elevator%';"
|
||||
```
|
||||
|
||||
期望输出: MySQL 版本号 + `component-organization` 和 `cw-elevator-application` 两个库存在。
|
||||
|
||||
> 若失败:全量降级为代码推导模式(见 Task 4 备选路径)。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 采集组件组织库 schema + 样本
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/superpowers/data/component-organization/schema_raw.json`
|
||||
- Create: `docs/superpowers/data/component-organization/tables.json`
|
||||
- Create: `docs/superpowers/data/component-organization/samples/`
|
||||
|
||||
- [ ] **Step 1: 导出表清单**
|
||||
|
||||
```bash
|
||||
mkdir -p docs/superpowers/data/component-organization/samples
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 component-organization -N -e \
|
||||
"SELECT TABLE_NAME, IFNULL(TABLE_ROWS,0), ENGINE, IFNULL(TABLE_COMMENT,'') \
|
||||
FROM INFORMATION_SCHEMA.TABLES \
|
||||
WHERE TABLE_SCHEMA='component-organization' AND TABLE_TYPE='BASE TABLE' \
|
||||
ORDER BY TABLE_NAME;" > docs/superpowers/data/component-organization/tables.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 导出全部列定义**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 component-organization -N -e \
|
||||
"SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), COLUMN_KEY, EXTRA, IFNULL(COLUMN_COMMENT,'') \
|
||||
FROM INFORMATION_SCHEMA.COLUMNS \
|
||||
WHERE TABLE_SCHEMA='component-organization' AND TABLE_NAME NOT LIKE 'QRTZ_%' AND TABLE_NAME NOT LIKE 'quartz_%' \
|
||||
ORDER BY TABLE_NAME, ORDINAL_POSITION;" > docs/superpowers/data/component-organization/columns.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 导出索引**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 component-organization -N -e \
|
||||
"SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, NON_UNIQUE, SEQ_IN_INDEX \
|
||||
FROM INFORMATION_SCHEMA.STATISTICS \
|
||||
WHERE TABLE_SCHEMA='component-organization' \
|
||||
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;" > docs/superpowers/data/component-organization/indexes.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 逐表采集样本(每表 3 行)**
|
||||
|
||||
```bash
|
||||
# 读取表清单,排除系统表,对每张表执行 SELECT * LIMIT 3
|
||||
while IFS=$'\t' read -r table_name rest; do
|
||||
if [[ "$table_name" != QRTZ_* ]] && [[ "$table_name" != quartz_* ]]; then
|
||||
echo "--- Extracting: $table_name ---"
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 component-organization -t -e \
|
||||
"SELECT * FROM \`$table_name\` ORDER BY 1 DESC LIMIT 3;" \
|
||||
> "docs/superpowers/data/component-organization/samples/${table_name}.txt" 2>&1
|
||||
fi
|
||||
done < <(cut -f1 docs/superpowers/data/component-organization/tables.tsv)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交中间产物**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/data/component-organization/
|
||||
git commit -m "data: add component-organization schema raw dump and samples"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 采集电梯应用库 schema + 样本
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/superpowers/data/cw-elevator-application/schema_raw.json`
|
||||
- Create: `docs/superpowers/data/cw-elevator-application/tables.json`
|
||||
- Create: `docs/superpowers/data/cw-elevator-application/samples/`
|
||||
|
||||
- [ ] **Step 1: 导出表清单**
|
||||
|
||||
```bash
|
||||
mkdir -p docs/superpowers/data/cw-elevator-application/samples
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 cw-elevator-application -N -e \
|
||||
"SELECT TABLE_NAME, IFNULL(TABLE_ROWS,0), ENGINE, IFNULL(TABLE_COMMENT,'') \
|
||||
FROM INFORMATION_SCHEMA.TABLES \
|
||||
WHERE TABLE_SCHEMA='cw-elevator-application' AND TABLE_TYPE='BASE TABLE' \
|
||||
ORDER BY TABLE_NAME;" > docs/superpowers/data/cw-elevator-application/tables.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 导出全部列定义**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 cw-elevator-application -N -e \
|
||||
"SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), COLUMN_KEY, EXTRA, IFNULL(COLUMN_COMMENT,'') \
|
||||
FROM INFORMATION_SCHEMA.COLUMNS \
|
||||
WHERE TABLE_SCHEMA='cw-elevator-application' AND TABLE_NAME NOT LIKE 'QRTZ_%' AND TABLE_NAME NOT LIKE 'quartz_%' \
|
||||
ORDER BY TABLE_NAME, ORDINAL_POSITION;" > docs/superpowers/data/cw-elevator-application/columns.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 导出索引**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 cw-elevator-application -N -e \
|
||||
"SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, NON_UNIQUE, SEQ_IN_INDEX \
|
||||
FROM INFORMATION_SCHEMA.STATISTICS \
|
||||
WHERE TABLE_SCHEMA='cw-elevator-application' \
|
||||
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;" > docs/superpowers/data/cw-elevator-application/indexes.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 逐表采集样本(每表 3 行)**
|
||||
|
||||
```bash
|
||||
while IFS=$'\t' read -r table_name rest; do
|
||||
if [[ "$table_name" != QRTZ_* ]] && [[ "$table_name" != quartz_* ]]; then
|
||||
echo "--- Extracting: $table_name ---"
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 cw-elevator-application -t -e \
|
||||
"SELECT * FROM \`$table_name\` ORDER BY 1 DESC LIMIT 3;" \
|
||||
> "docs/superpowers/data/cw-elevator-application/samples/${table_name}.txt" 2>&1
|
||||
fi
|
||||
done < <(cut -f1 docs/superpowers/data/cw-elevator-application/tables.tsv)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交中间产物**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/data/cw-elevator-application/
|
||||
git commit -m "data: add cw-elevator-application schema raw dump and samples"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 扫描所有 MyBatis Mapper XML 提取表/列/JOIN
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/superpowers/data/mapper_tables.tsv`
|
||||
|
||||
- [ ] **Step 1: 枚举所有 Mapper XML 文件**
|
||||
|
||||
```bash
|
||||
find ./maven-cw-elevator-application ./maven-cwos-resource ./maven-ninca-crk ./maven-ninca-qk-alarm \
|
||||
-name "*Mapper.xml" -path "*/src/main/*" 2>/dev/null | sort \
|
||||
> docs/superpowers/data/mapper_files.txt
|
||||
wc -l docs/superpowers/data/mapper_files.txt
|
||||
```
|
||||
|
||||
期望输出: ~80+ 个 Mapper XML 文件。
|
||||
|
||||
- [ ] **Step 2: 从 Mapper XML 提取 INSERT/UPDATE/FROM 表名**
|
||||
|
||||
```bash
|
||||
# 提取 INSERT INTO / UPDATE / FROM / JOIN 后的表名
|
||||
> docs/superpowers/data/mapper_tables.tsv
|
||||
while IFS= read -r xmlfile; do
|
||||
module=$(echo "$xmlfile" | cut -d'/' -f2)
|
||||
tablenames=$(grep -oP '(INSERT\s+INTO\s+|UPDATE\s+|FROM\s+|JOIN\s+)\s*\`?\K[a-z_][a-z0-9_]*' "$xmlfile" 2>/dev/null | sort -u | tr '\n' ',')
|
||||
echo -e "$module\t$xmlfile\t$tablenames"
|
||||
done < docs/superpowers/data/mapper_files.txt > docs/superpowers/data/mapper_tables.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 提取 resultMap 列映射**
|
||||
|
||||
对每个 Mapper XML,读取 `<resultMap>` 中的 `<result column="..." property="...">` 提取列名。用下面脚本:
|
||||
|
||||
```bash
|
||||
> docs/superpowers/data/mapper_columns.tsv
|
||||
while IFS= read -r xmlfile; do
|
||||
cols=$(grep -oP '<result\s+column="\K[^"]+' "$xmlfile" 2>/dev/null | sort -u | tr '\n' ',')
|
||||
ids=$(grep -oP '<id\s+column="\K[^"]+' "$xmlfile" 2>/dev/null | sort -u | tr '\n' ',')
|
||||
echo -e "${xmlfile}\t${ids}|${cols}"
|
||||
done < docs/superpowers/data/mapper_files.txt > docs/superpowers/data/mapper_columns.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 提取 JOIN 关系**
|
||||
|
||||
```bash
|
||||
> docs/superpowers/data/mapper_joins.tsv
|
||||
while IFS= read -r xmlfile; do
|
||||
joins=$(grep -oP '(LEFT\s+|RIGHT\s+|INNER\s+)?JOIN\s+\`?\K[a-z_][a-z0-9_]*\s+ON\s+\K[^;]+' "$xmlfile" 2>/dev/null | tr '\n' '|')
|
||||
if [ -n "$joins" ]; then
|
||||
echo -e "${xmlfile}\t${joins}"
|
||||
fi
|
||||
done < docs/superpowers/data/mapper_files.txt > docs/superpowers/data/mapper_joins.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 扫描 ShardingSphere 分表配置**
|
||||
|
||||
```bash
|
||||
grep -r "actual-data-nodes\|sharding-column\|table-strategy" \
|
||||
--include="*.properties" --include="*.yml" --include="*.yaml" \
|
||||
./maven-cw-elevator-application/ ./maven-ninca-crk/ 2>/dev/null \
|
||||
> docs/superpowers/data/sharding_config.txt
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/data/mapper_*.tsv docs/superpowers/data/mapper_files.txt docs/superpowers/data/sharding_config.txt
|
||||
git commit -m "data: add MyBatis mapper table/column/join extraction"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 代码推导不可达库的表结构
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/superpowers/data/ninca-crk-std/mapper_tables.tsv`
|
||||
- Create: `docs/superpowers/data/alarm-deploy/mapper_tables.tsv`
|
||||
- Create: `docs/superpowers/data/cwos-resource/mapper_tables.tsv`
|
||||
|
||||
**ninca_crk_std** 和 **alarm_deploy** 的 MyBatis mapper 路径在 `application.properties` 中声明:
|
||||
- ninca-crk: `classpath*:cn/cloudwalk/data/**/mysql/*.xml,classpath*:cn/cloudwalk/task/data/**/mysql/*.xml`
|
||||
- alarm: `classpath:cn/cloudwalk/data/**/mysql/*.xml`
|
||||
- cwos-resource: Mapper XML 已在 Task 3 中扫描
|
||||
|
||||
- [ ] **Step 1: 从 ninca-crk 的 Mapper XML 提取表信息**
|
||||
|
||||
```bash
|
||||
# ninca-crk 的 Mapper XML 位于 src/main/java 下(非 resources/mapper)
|
||||
find ./maven-ninca-crk -name "*.xml" -path "*/mysql/*" 2>/dev/null | sort > docs/superpowers/data/ninca-crk-std/mapper_files.txt
|
||||
|
||||
while IFS= read -r xmlfile; do
|
||||
tablenames=$(grep -oP '(INSERT\s+INTO\s+|UPDATE\s+|FROM\s+|JOIN\s+)\s*\`?\K[a-z_][a-z0-9_]*' "$xmlfile" 2>/dev/null | sort -u | tr '\n' ',')
|
||||
echo -e "ninca-crk-std\t$xmlfile\t$tablenames"
|
||||
done < docs/superpowers/data/ninca-crk-std/mapper_files.txt > docs/superpowers/data/ninca-crk-std/mapper_tables.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 从 alarm 的 Mapper XML 提取表信息**
|
||||
|
||||
```bash
|
||||
find ./maven-ninca-qk-alarm -name "*.xml" -path "*/mysql/*" 2>/dev/null | sort > docs/superpowers/data/alarm-deploy/mapper_files.txt
|
||||
|
||||
while IFS= read -r xmlfile; do
|
||||
tablenames=$(grep -oP '(INSERT\s+INTO\s+|UPDATE\s+|FROM\s+|JOIN\s+)\s*\`?\K[a-z_][a-z0-9_]*' "$xmlfile" 2>/dev/null | sort -u | tr '\n' ',')
|
||||
echo -e "alarm-deploy\t$xmlfile\t$tablenames"
|
||||
done < docs/superpowers/data/alarm-deploy/mapper_files.txt > docs/superpowers/data/alarm-deploy/mapper_tables.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 从 cwos-resource 的 Mapper XML 提取表信息**
|
||||
|
||||
```bash
|
||||
# cwos-resource 有 db2/mysql/oracle 三个方言目录,以 mysql/ 为准
|
||||
find ./maven-cwos-resource -name "*.xml" -path "*/mysql/*" 2>/dev/null | sort > docs/superpowers/data/cwos-resource/mapper_files.txt
|
||||
|
||||
while IFS= read -r xmlfile; do
|
||||
tablenames=$(grep -oP '(INSERT\s+INTO\s+|UPDATE\s+|FROM\s+|JOIN\s+)\s*\`?\K[a-z_][a-z0-9_]*' "$xmlfile" 2>/dev/null | sort -u | tr '\n' ',')
|
||||
echo -e "cwos-resource\t$xmlfile\t$tablenames"
|
||||
done < docs/superpowers/data/cwos-resource/mapper_files.txt > docs/superpowers/data/cwos-resource/mapper_tables.tsv
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/data/ninca-crk-std/ docs/superpowers/data/alarm-deploy/ docs/superpowers/data/cwos-resource/
|
||||
git commit -m "data: add code-derived table info for unreachable databases"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 交叉验证与生成最终 Markdown 文档
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/superpowers/specs/2026-05-01-database-schema-reference.md`(最终产物)
|
||||
|
||||
此任务通过 subagent 执行——将 Task 1-4 采集的原始数据 + 现有架构文档 + Mapper 扫描结果汇总为最终手册。
|
||||
|
||||
- [ ] **Step 1: 交付 subagent 生成文档**
|
||||
|
||||
委托 `deep` agent,传入以下 prompt:
|
||||
|
||||
```
|
||||
TASK: 根据以下原始数据生成数据库表结构参考手册 Markdown 文件。
|
||||
|
||||
EXPECTED OUTCOME:
|
||||
- 文件写入 docs/superpowers/specs/2026-05-01-database-schema-reference.md
|
||||
- 包含 9 个章节(见设计说明 §2)
|
||||
- 每库有 Mermaid erDiagram ER 图
|
||||
- 每表有列清单(列名、类型、可空、键、注释)
|
||||
- 可连库的表有脱敏样本数据(Markdown 表格,≤3 行)
|
||||
- 跨库关系总图
|
||||
- 代码-表映射索引
|
||||
|
||||
REQUIRED TOOLS: Read, Write, Bash
|
||||
|
||||
MUST DO:
|
||||
1. 读取设计说明: docs/superpowers/specs/2026-05-01-database-schema-reference-design.md
|
||||
2. 读取现有架构文档: docs/architecture/租户组织人员访客-数据模型与用例.md
|
||||
3. 读取 Task 1-2 的 schema 原始数据: docs/superpowers/data/component-organization/ 和 cw-elevator-application/ 下的 tables.tsv, columns.tsv, indexes.tsv
|
||||
4. 读取 Task 1-2 的样本数据: samples/ 目录下各 .txt 文件
|
||||
5. 读取 Task 3-4 的 Mapper 扫描结果: docs/superpowers/data/mapper_tables.tsv, mapper_columns.tsv, mapper_joins.tsv
|
||||
6. 对每张表应用脱敏规则(设计说明 §5.3):姓名截断、手机号截断、IP替换、密码列跳过、时间戳转换
|
||||
7. 按设计说明 §4 规范绘制 Mermaid ER 图:业务关键列、三种关系线(约束/JOIN/跨库)、subgraph 分包
|
||||
8. 交叉验证:列出「库中有但代码无 Mapper」的表(标注为运维/外部表),对比 DDL 与实际列
|
||||
9. 对不可达库(ninca-crk-std, alarm-deploy, cwos-resource)显式标注「⚠️ 未连接生产库,从代码推导」
|
||||
10. Mermaid 语法必须可渲染(erDiagram 关键字、正确的 {} 语法、||--o{ 关系线)
|
||||
11. 文档中不出现 "TBD"、"TODO"、"待补充"
|
||||
|
||||
MUST NOT DO:
|
||||
- 不要编造未在原始数据中出现的表名或列名
|
||||
- 不要对不可达库声称有样本数据
|
||||
- 不要包含系统表(QRTZ_*, quartz_*)
|
||||
- 不要包含未脱敏的姓名/手机号/IP
|
||||
|
||||
CONTEXT:
|
||||
- 仓库路径: /media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/源码
|
||||
- 数据库环境: 192.168.3.12:3307 (MySQL), user=root, password=123456
|
||||
- 数据库: component-organization, cw-elevator-application (直连); ninca_crk_std, alarm_deploy, cwos_resource (代码推导)
|
||||
- 现有架构文档已有 organization 库 5 表 + elevator 库 2 表的 ER 模型,以此为起点扩展
|
||||
- MyBatis Mapper XML 路径已在 mapper_files.txt 中列出
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证文档完整性**
|
||||
|
||||
```bash
|
||||
# 检查文档存在且非空
|
||||
wc -l docs/superpowers/specs/2026-05-01-database-schema-reference.md
|
||||
|
||||
# 检查 Mermaid 块数量(应有 ≥5 个 erDiagram 块)
|
||||
grep -c 'erDiagram' docs/superpowers/specs/2026-05-01-database-schema-reference.md
|
||||
|
||||
# 检查无占位符
|
||||
! grep -n 'TBD\|TODO\|待补充' docs/superpowers/specs/2026-05-01-database-schema-reference.md
|
||||
echo "Exit: $?"
|
||||
```
|
||||
|
||||
期望: 文件 ≥ 500 行,≥ 5 个 erDiagram 块,无占位符匹配(exit 0)。
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/specs/2026-05-01-database-schema-reference.md
|
||||
git commit -m "docs: add full database schema reference manual with ER diagrams and sample data"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 收尾清理
|
||||
|
||||
- [ ] **Step 1: 取消中间数据跟踪(可选)**
|
||||
|
||||
若中间数据不应入库,更新 `.gitignore`:
|
||||
|
||||
```bash
|
||||
# 如需排除原始数据
|
||||
echo "docs/superpowers/data/" >> .gitignore
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 最终提交**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git status
|
||||
git commit -m "chore: finalize database schema reference delivery"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 备选路径
|
||||
|
||||
### 若 192.168.3.12 不可达
|
||||
|
||||
跳过 Task 1 和 Task 2。所有表结构从代码 Mapper XML 推导,标注「⚠️ 数据库不可达,全部信息从代码推导」。在 Task 5 的 subagent prompt 中移除「读取样本数据」步骤。
|
||||
|
||||
### 若某个库的 Mapper XML 为空
|
||||
|
||||
对应章节仅输出「该模块未发现 MyBatis Mapper XML,无法从代码推导表结构」,不生成 ER 图。
|
||||
|
||||
---
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
- [ ] 产物文件 `docs/superpowers/specs/2026-05-01-database-schema-reference.md` 存在且 ≥ 500 行
|
||||
- [ ] 5 个数据库各有独立章节 + ER 图
|
||||
- [ ] 跨库关系总图存在
|
||||
- [ ] 代码-表映射索引覆盖全部 Mapper XML
|
||||
- [ ] 可连库的每张表有 1-3 行脱敏样本
|
||||
- [ ] 无 `TBD` / `TODO` / `待补充`
|
||||
- [ ] 所有 Mermaid 块语法正确可渲染
|
||||
- [ ] 脱敏规则已应用(姓名截断、手机号截断等)
|
||||
@@ -0,0 +1,647 @@
|
||||
# 租户访客楼层策略 org_id 粒度修复 — 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将 `tenant_visitor_floor_policy` 的策略键从 `business_id` 改为 `org_id`,实现二选一语义(有策略用 allow,无策略用 floorList),修复 F1/F2/W2 问题。
|
||||
|
||||
**Architecture:** DDL 先上线(加列+改约束,不影响行为)→ 代码切换(Mapper/DAO/Service 三层的 business_id → org_id + 二选一逻辑)→ 数据迁移(运维 SQL 填 org_id)。整体改动控制在 7 个文件内,最小风险。
|
||||
|
||||
**Tech Stack:** Java 8, Spring Boot, MyBatis, MySQL 5.7
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-05-01-org-id-policy-fix-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- [ ] **Step 0: 确认分支与编译环境**
|
||||
|
||||
```bash
|
||||
git checkout -b fix/org-id-policy-granularity
|
||||
cd maven-cw-elevator-application && mvn formatter:validate -Dformatter-maven-plugin.version=2.16.0
|
||||
```
|
||||
|
||||
期望: formatter 校验通过。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: DDL — 策略表结构变更
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/sql/tenant_visitor_floor_policy_v2.sql`
|
||||
|
||||
- [ ] **Step 1: 编写 DDL 脚本**
|
||||
|
||||
```sql
|
||||
-- 租户访客楼层策略:org_id 粒度修复
|
||||
-- 执行顺序:先 DDL → 数据迁移(Task 5)→ 发应用包
|
||||
-- 回滚:DROP INDEX uk_org_building, DROP COLUMN org_id, ADD UNIQUE KEY uk_biz_building (business_id, building_id)
|
||||
|
||||
USE `cw-elevator-application`;
|
||||
|
||||
-- 1. 新增 org_id 列
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
ADD COLUMN org_id VARCHAR(32) NULL COMMENT '组织节点ID(cw_is_organization.ID)'
|
||||
AFTER business_id;
|
||||
|
||||
-- 2. 替换唯一约束(business_id → org_id)
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
DROP INDEX uk_biz_building,
|
||||
ADD UNIQUE KEY uk_org_building (org_id, building_id);
|
||||
|
||||
-- 3. 标记 business_id 为废弃
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
MODIFY COLUMN business_id VARCHAR(64) NULL COMMENT 'DEPRECATED: 已废弃,以 org_id 为准';
|
||||
|
||||
-- 验证
|
||||
SELECT COLUMN_NAME, COLUMN_KEY, COLUMN_COMMENT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'cw-elevator-application'
|
||||
AND TABLE_NAME = 'tenant_visitor_floor_policy'
|
||||
ORDER BY ORDINAL_POSITION;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在开发库执行 DDL**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 cw-elevator-application < docs/sql/tenant_visitor_floor_policy_v2.sql
|
||||
```
|
||||
|
||||
期望: 无错误,`org_id` 列存在,`uk_org_building` 索引存在,`uk_biz_building` 已删除。
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/sql/tenant_visitor_floor_policy_v2.sql
|
||||
git commit -m "feat: add org_id column and uk_org_building constraint to tenant_visitor_floor_policy"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: DTO — 新增 orgId 字段
|
||||
|
||||
**Files:**
|
||||
- Modify: `maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dto/TenantVisitorFloorPolicyDto.java`
|
||||
|
||||
- [ ] **Step 1: 添加 orgId 字段 + getter/setter**
|
||||
|
||||
在 `businessId` 的 setter 之后插入:
|
||||
|
||||
```java
|
||||
// 新增字段
|
||||
private String orgId;
|
||||
|
||||
public String getOrgId() {
|
||||
return orgId;
|
||||
}
|
||||
|
||||
public void setOrgId(String orgId) {
|
||||
this.orgId = orgId;
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:`businessId` 字段保留不删,兼容旧序列化。
|
||||
|
||||
- [ ] **Step 2: 验证编译**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application && mvn compile -pl cw-elevator-application-data -am -DskipTests
|
||||
```
|
||||
|
||||
期望: BUILD SUCCESS。
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dto/TenantVisitorFloorPolicyDto.java
|
||||
git commit -m "feat: add orgId field to TenantVisitorFloorPolicyDto"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Mapper — SQL 切换 business_id → org_id
|
||||
|
||||
**Files:**
|
||||
- Modify: `maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.xml`
|
||||
- Modify: `maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.java`
|
||||
|
||||
- [ ] **Step 1: 修改 Mapper XML — WHERE 条件 + 映射**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper">
|
||||
|
||||
<select id="selectEnabledByOrgId" resultType="cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto">
|
||||
SELECT id,
|
||||
org_id AS orgId,
|
||||
policy_type AS policyType,
|
||||
allow_zone_ids AS allowZoneIds,
|
||||
building_id AS buildingId,
|
||||
enabled AS enabled,
|
||||
policy_version AS policyVersion
|
||||
FROM tenant_visitor_floor_policy
|
||||
WHERE org_id = #{orgId,jdbcType=VARCHAR}
|
||||
AND enabled = 1
|
||||
AND policy_type = 'INTERSECT_ALLOWLIST'
|
||||
AND (building_id IS NULL OR building_id = '')
|
||||
ORDER BY updated_at DESC, policy_version DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 旧方法保留作历史参考(可选删除)
|
||||
<select id="selectEnabledTenantDefault" resultType="...">
|
||||
... business_id ...
|
||||
</select>
|
||||
-->
|
||||
</mapper>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 修改 Mapper 接口**
|
||||
|
||||
```java
|
||||
package cn.cloudwalk.elevator.person.mapper;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface TenantVisitorFloorPolicyMapper {
|
||||
|
||||
/**
|
||||
* 按组织节点 ID 查询启用中的 INTERSECT_ALLOWLIST 策略(building_id 为空)。
|
||||
*/
|
||||
TenantVisitorFloorPolicyDto selectEnabledByOrgId(@Param("orgId") String orgId);
|
||||
|
||||
// 旧方法(废弃,保留以兼容编译)
|
||||
// TenantVisitorFloorPolicyDto selectEnabledTenantDefault(@Param("businessId") String businessId);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证编译**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application && mvn compile -pl cw-elevator-application-data -am -DskipTests
|
||||
```
|
||||
|
||||
期望: BUILD SUCCESS。
|
||||
|
||||
- [ ] **Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.xml
|
||||
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.java
|
||||
git commit -m "feat: change policy query from business_id to org_id in TenantVisitorFloorPolicyMapper"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: DAO — 接口与实现切换
|
||||
|
||||
**Files:**
|
||||
- Modify: `maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dao/TenantVisitorFloorPolicyDao.java`
|
||||
- Modify: `maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/impl/TenantVisitorFloorPolicyDaoImpl.java`
|
||||
|
||||
- [ ] **Step 1: 修改 DAO 接口**
|
||||
|
||||
```java
|
||||
package cn.cloudwalk.elevator.person.dao;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
|
||||
public interface TenantVisitorFloorPolicyDao {
|
||||
|
||||
/**
|
||||
* 按组织节点 ID 查询启用中的 INTERSECT_ALLOWLIST 策略(building_id 为空)。
|
||||
*
|
||||
* @param orgId 组织节点 ID(cw_is_organization.ID)
|
||||
* @return 无配置时 null
|
||||
*/
|
||||
TenantVisitorFloorPolicyDto selectEnabledByOrgId(String orgId);
|
||||
|
||||
// 旧方法(废弃)
|
||||
// TenantVisitorFloorPolicyDto selectEnabledTenantDefault(String businessId);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 修改 DAO 实现**
|
||||
|
||||
```java
|
||||
package cn.cloudwalk.elevator.person.impl;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dao.TenantVisitorFloorPolicyDao;
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
import cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class TenantVisitorFloorPolicyDaoImpl implements TenantVisitorFloorPolicyDao {
|
||||
|
||||
@Resource
|
||||
private TenantVisitorFloorPolicyMapper tenantVisitorFloorPolicyMapper;
|
||||
|
||||
@Override
|
||||
public TenantVisitorFloorPolicyDto selectEnabledByOrgId(String orgId) {
|
||||
return this.tenantVisitorFloorPolicyMapper.selectEnabledByOrgId(orgId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证编译**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application && mvn compile -pl cw-elevator-application-data -am -DskipTests
|
||||
```
|
||||
|
||||
期望: BUILD SUCCESS。
|
||||
|
||||
- [ ] **Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dao/TenantVisitorFloorPolicyDao.java
|
||||
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/impl/TenantVisitorFloorPolicyDaoImpl.java
|
||||
git commit -m "feat: update DAO interface and impl to use org_id query"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Service — addVisitor 核心逻辑重写
|
||||
|
||||
**Files:**
|
||||
- Modify: `maven-cw-elevator-application/cw-elevator-application-service/src/main/java/cn/cloudwalk/elevator/person/impl/PersonRuleServiceImpl.java`
|
||||
|
||||
这是改动最大的文件。分 3 个子步骤。
|
||||
|
||||
- [ ] **Step 1: 重写 addVisitor 方法(第 174-275 行)**
|
||||
|
||||
完整替换:
|
||||
|
||||
```java
|
||||
@CloudwalkParamsValidate
|
||||
public CloudwalkResult<Boolean> addVisitor(AcsPersonAddVisitorParam param, CloudwalkCallContext context)
|
||||
throws ServiceException {
|
||||
this.logger.info("根据被访人添加访客派梯权限开始,AcsPersonAddVisitorParam=[{}], CloudwalkCallContext=[{}]",
|
||||
JSONObject.toJSONString(param), JSONObject.toJSONString(context));
|
||||
try {
|
||||
// ===== Step 1: 获取被访人信息(UC-01/02 都需要) =====
|
||||
PersonDetailParam detailParam = new PersonDetailParam();
|
||||
detailParam.setId(param.getPersonId());
|
||||
detailParam.setBusinessId(context.getCompany().getCompanyId());
|
||||
CloudwalkResult<PersonResult> detail = this.personService.detail(detailParam, context);
|
||||
if (detail == null || !detail.isSuccess()) {
|
||||
String code = detail != null ? detail.getCode() : "76260531";
|
||||
String msg = detail != null ? detail.getMessage() : getMessage("76260531");
|
||||
return CloudwalkResult.fail(code, msg);
|
||||
}
|
||||
PersonResult personResult = (PersonResult) detail.getData();
|
||||
if (personResult == null) {
|
||||
return CloudwalkResult.fail("76260531", getMessage("76260531"));
|
||||
}
|
||||
List<String> hostFloors = personResult.getFloorList();
|
||||
if (CollectionUtils.isEmpty(hostFloors)) {
|
||||
return CloudwalkResult.fail("76260531", getMessage("76260531"));
|
||||
}
|
||||
|
||||
// ===== Step 2: 按 org_id 查找策略 =====
|
||||
TenantVisitorFloorPolicyDto policy = findPolicyByOrgIds(personResult.getOrganizationIds());
|
||||
|
||||
// ===== Step 3: 确定生效楼层(二选一,不求交) =====
|
||||
List<String> effectiveFloors;
|
||||
boolean callerProvidedFloors = !CollectionUtils.isEmpty(param.getFloorIds());
|
||||
|
||||
if (policy != null) {
|
||||
// 有策略:直接用 allow,忽略调用方 floorIds
|
||||
effectiveFloors = resolveEffectiveFloors(
|
||||
callerProvidedFloors ? param.getFloorIds() : hostFloors,
|
||||
hostFloors, policy, param.getPersonId());
|
||||
} else {
|
||||
// 无策略:用调用方 floorIds 或 hostFloors
|
||||
effectiveFloors = callerProvidedFloors ? param.getFloorIds() : hostFloors;
|
||||
if (callerProvidedFloors) {
|
||||
// UC-02 软校验:记录不在 hostFloors 中的楼层
|
||||
Set<String> hostSet = new HashSet<>(hostFloors);
|
||||
List<String> outliers = param.getFloorIds().stream()
|
||||
.filter(f -> !hostSet.contains(f))
|
||||
.collect(Collectors.toList());
|
||||
if (!outliers.isEmpty()) {
|
||||
this.logger.warn("UC-02 传入非被访人授权楼层 businessId={} personId={} outliers={}",
|
||||
context.getCompany().getCompanyId(), param.getPersonId(), outliers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(effectiveFloors)) {
|
||||
return CloudwalkResult.fail("76260531", getMessage("76260531"));
|
||||
}
|
||||
param.setFloorIds(effectiveFloors);
|
||||
|
||||
// ===== Step 4: 落库(不变) =====
|
||||
ZoneQueryParam zoneQueryParam = new ZoneQueryParam();
|
||||
zoneQueryParam.setId(param.getFloorIds().get(0));
|
||||
zoneQueryParam.setRowsOfPage(10);
|
||||
zoneQueryParam.setCurrentPage(1);
|
||||
CloudwalkResult<CloudwalkPageAble<ZoneResult>> zonePage = this.zoneService.page(zoneQueryParam, context);
|
||||
List<ZoneResult> zoneResults = (List<ZoneResult>) ((CloudwalkPageAble) zonePage.getData()).getDatas();
|
||||
String imageStoreId =
|
||||
this.deviceImageStoreDao.getByBuildingId(((ZoneResult) zoneResults.get(0)).getParentId());
|
||||
List<ImageRuleRefAddDto> insertList = new ArrayList<>();
|
||||
for (String floorId : param.getFloorIds()) {
|
||||
ImageRuleRefResultDto defaultRule = this.imageRuleRefDao.getDefaultByZoneId(floorId);
|
||||
ImageRuleRefAddDto addDto = new ImageRuleRefAddDto();
|
||||
addDto.setId(genUUID());
|
||||
addDto.setBusinessId(context.getCompany().getCompanyId());
|
||||
addDto.setPersonId(param.getVisitorId());
|
||||
addDto.setParentRule(defaultRule.getId());
|
||||
addDto.setName(defaultRule.getName());
|
||||
addDto.setZoneId(defaultRule.getZoneId());
|
||||
addDto.setZoneName(defaultRule.getZoneName());
|
||||
addDto.setCreateTime(Long.valueOf(System.currentTimeMillis()));
|
||||
addDto.setLastUpdateTime(Long.valueOf(System.currentTimeMillis()));
|
||||
addDto.setPersonDelete(Integer.valueOf(0));
|
||||
insertList.add(addDto);
|
||||
}
|
||||
this.logger.info("访客添加派梯权限开始,数据为=[{}]", JSONObject.toJSONString(insertList));
|
||||
if (!CollectionUtils.isEmpty(insertList)) {
|
||||
this.imageRuleRefDao.insertList(insertList);
|
||||
}
|
||||
ImageStorePersonBindParam imageStorePersonBindParam = new ImageStorePersonBindParam();
|
||||
imageStorePersonBindParam.setImageStoreId(imageStoreId);
|
||||
imageStorePersonBindParam.setPersonIds(Collections.singletonList(param.getVisitorId()));
|
||||
imageStorePersonBindParam.setNullDateIsLongTerm(Boolean.valueOf(true));
|
||||
imageStorePersonBindParam.setExpiryBeginDate(param.getBegVisitorTime());
|
||||
imageStorePersonBindParam.setExpiryEndDate(param.getEndVisitorTime());
|
||||
this.logger.info("远程调用绑定人员图库开始,imageStorePersonBindParam=[{}], CloudwalkCallContext=[{}]",
|
||||
JSONObject.toJSONString(imageStorePersonBindParam), JSONObject.toJSONString(context));
|
||||
CloudwalkResult<ImgStoreBatchBindPersonResult> bindResult =
|
||||
this.imageStorePersonService.batchBind(imageStorePersonBindParam, context);
|
||||
if (!bindResult.isSuccess()) {
|
||||
this.logger.error("远程调用绑定人员图库异常,原因:[{}],失败人员id:[{}]", bindResult.getMessage(), param.getVisitorId());
|
||||
return CloudwalkResult.fail(bindResult.getCode(), bindResult.getMessage());
|
||||
}
|
||||
UpdateGroupPersonRefParam refParam = new UpdateGroupPersonRefParam();
|
||||
refParam.setBusinessId(context.getCompany().getCompanyId());
|
||||
refParam.setPersonIds(Collections.singletonList(param.getVisitorId()));
|
||||
refParam.setImageStoreId(imageStoreId);
|
||||
this.imageStorePersonService.updateGroupPersonRef(refParam, context);
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
this.logger.error("根据被访人添加访客派梯权限失败,原因:[{}]", e);
|
||||
throw new ServiceException("76260530", getMessage("76260530"));
|
||||
}
|
||||
return CloudwalkResult.success(Boolean.valueOf(true));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 添加两个新辅助方法 + 修改 W2(JSON 日志升级)**
|
||||
|
||||
在 `addVisitor` 方法之后插入:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 按 org_id 查找策略,遍历 organizationIds 取第一个命中。
|
||||
*/
|
||||
private TenantVisitorFloorPolicyDto findPolicyByOrgIds(List<String> orgIds) {
|
||||
if (CollectionUtils.isEmpty(orgIds)) return null;
|
||||
for (String orgId : orgIds) {
|
||||
TenantVisitorFloorPolicyDto p = this.tenantVisitorFloorPolicyDao.selectEnabledByOrgId(orgId);
|
||||
if (p != null && p.getEnabled() != null && p.getEnabled().intValue() == 1) {
|
||||
List<String> allow = parseAllowZoneIds(p.getAllowZoneIds());
|
||||
if (!CollectionUtils.isEmpty(allow)) return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二选一:用 allow 替换 fallbackFloors。
|
||||
* 约束:allow 必须是 hostFloors 的子集,否则拒绝(76260533)。
|
||||
*/
|
||||
private List<String> resolveEffectiveFloors(
|
||||
List<String> fallbackFloorsUnused, List<String> hostFloors,
|
||||
TenantVisitorFloorPolicyDto policy, String personId) {
|
||||
List<String> allow = parseAllowZoneIds(policy.getAllowZoneIds());
|
||||
if (CollectionUtils.isEmpty(allow)) return fallbackFloorsUnused;
|
||||
|
||||
// 安全校验:allow 中每个值必须在 hostFloors 中存在
|
||||
Set<String> hostSet = new HashSet<>(hostFloors);
|
||||
List<String> unknownAllow = allow.stream()
|
||||
.filter(a -> !hostSet.contains(a))
|
||||
.collect(Collectors.toList());
|
||||
if (!unknownAllow.isEmpty()) {
|
||||
this.logger.error("策略配置错误:allow 包含不在被访人 floorList 中的 zoneId!"
|
||||
+ "orgId={} policyId={} personId={} unknownAllow={} hostFloors={}",
|
||||
policy.getOrgId(), policy.getId(), personId, unknownAllow, hostFloors);
|
||||
throw new ServiceException("76260533",
|
||||
"策略配置了被访人无权访问的楼层,请联系管理员");
|
||||
}
|
||||
|
||||
this.logger.info("策略生效 orgId={} policyId={} v={} allowSize={} hostSize={}",
|
||||
policy.getOrgId(), policy.getId(), policy.getPolicyVersion(),
|
||||
allow.size(), hostFloors.size());
|
||||
return allow;
|
||||
}
|
||||
```
|
||||
|
||||
同时修改 `parseAllowZoneIds` 的 catch 块(W2 修复):
|
||||
|
||||
```java
|
||||
// 旧代码:
|
||||
// this.logger.warn("allow_zone_ids JSON 无效,按无策略处理: {}", e.getMessage());
|
||||
|
||||
// 新代码:
|
||||
this.logger.error("allow_zone_ids JSON 无效,策略失效!policyId={} raw={}",
|
||||
"policy.id", json, e); // 注意:此处无法获取 policy.id,改用实际可用字段
|
||||
```
|
||||
|
||||
> 实际实现时,`parseAllowZoneIds` 不持有 `policyId`,可以在 `resolveEffectiveFloors` 中调用 `parseAllowZoneIds` 之前先做 null 检查,将 ERROR 日志放在调用处:
|
||||
|
||||
```java
|
||||
private List<String> resolveEffectiveFloors(...) {
|
||||
String rawJson = policy.getAllowZoneIds();
|
||||
List<String> allow = parseAllowZoneIds(rawJson);
|
||||
if (CollectionUtils.isEmpty(allow)) {
|
||||
if (!StringUtils.isBlank(rawJson)) {
|
||||
this.logger.error("allow_zone_ids JSON 无效或为空,策略失效!orgId={} policyId={} raw={}",
|
||||
policy.getOrgId(), policy.getId(), rawJson);
|
||||
}
|
||||
return fallbackFloorsUnused;
|
||||
}
|
||||
// ... 后续校验
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 删除旧辅助方法 `intersectPreserveHostOrder`(不再需要)**
|
||||
|
||||
该方法已被 `resolveEffectiveFloors` 替代,可删除或保留(无调用方即可)。
|
||||
|
||||
- [ ] **Step 4: 验证编译**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application && mvn compile -DskipTests
|
||||
```
|
||||
|
||||
期望: BUILD SUCCESS。
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add maven-cw-elevator-application/cw-elevator-application-service/src/main/java/cn/cloudwalk/elevator/person/impl/PersonRuleServiceImpl.java
|
||||
git commit -m "feat: rewrite addVisitor with org_id policy lookup and either-or semantics
|
||||
|
||||
- Replace business_id policy key with org_id from PersonResult.getOrganizationIds()
|
||||
- Change from intersection (floorList ∩ allow) to either-or (policy? allow : floorList)
|
||||
- Add resolveEffectiveFloors with allow ⊆ floorList safety check (76260533)
|
||||
- UC-02 now also checks policy (policy takes precedence over caller floorIds)
|
||||
- Upgrade JSON parse failure log from WARN to ERROR
|
||||
- Remove unused intersectPreserveHostOrder method"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 错误码注册(76260533)
|
||||
|
||||
**Files:**
|
||||
- Check: `maven-cw-elevator-application/cw-elevator-application-starter/src/main/resources/access-control.properties`(或对应的 messages 资源文件)
|
||||
|
||||
- [ ] **Step 1: 查找错误码资源文件**
|
||||
|
||||
```bash
|
||||
grep -rn "76260531\|76260532" --include="*.properties" --include="*.xml" maven-cw-elevator-application/
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在对应的 messages 文件中新增**
|
||||
|
||||
```properties
|
||||
76260533=策略配置了被访人无权访问的楼层,请联系管理员
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add <错误码资源文件路径>
|
||||
git commit -m "feat: add error code 76260533 for policy-host floor mismatch"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 数据迁移 SQL
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/sql/tenant_visitor_floor_policy_migrate_org_id.sql`
|
||||
|
||||
- [ ] **Step 1: 编写迁移脚本**
|
||||
|
||||
```sql
|
||||
-- 租户访客楼层策略:business_id → org_id 数据迁移
|
||||
-- 前提:DDL(Task 1)已执行
|
||||
-- 执行方式:人工确认 org_id 对应关系后逐行执行
|
||||
|
||||
USE cw-elevator-application;
|
||||
|
||||
-- 1. 列出所有公司级组织节点(供确认)
|
||||
-- 在 component-organization 库执行:
|
||||
-- SELECT o.ID, o.NAME, o.PARENT_ID
|
||||
-- FROM `component-organization`.cw_is_organization o
|
||||
-- WHERE o.BUSINESS_ID = '2524639890ba4f2cba9ba1a4eeaa4015'
|
||||
-- AND o.IS_DEL = 0
|
||||
-- ORDER BY o.NAME;
|
||||
|
||||
-- 2. 为现有策略行填入 org_id(示例:广发基金)
|
||||
-- 请先确认 NAME 匹配正确
|
||||
UPDATE tenant_visitor_floor_policy
|
||||
SET org_id = '<广发基金的 org_id>',
|
||||
business_id = NULL -- 可选:标记 business_id 已废弃
|
||||
WHERE id = 'gf_vstr_policy_guangfa_fund_001x';
|
||||
|
||||
-- 3. 为其他公司新增策略行(模板)
|
||||
-- INSERT INTO tenant_visitor_floor_policy
|
||||
-- (id, org_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, remark, created_at, updated_at)
|
||||
-- VALUES
|
||||
-- (REPLACE(UUID(),'-',''), '<公司 org_id>', 'INTERSECT_ALLOWLIST',
|
||||
-- '["<zone_id>"]', NULL, 1, 1, '', UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
|
||||
|
||||
-- 4. 验证迁移结果
|
||||
SELECT id, org_id, business_id, policy_type, allow_zone_ids, enabled
|
||||
FROM tenant_visitor_floor_policy
|
||||
ORDER BY org_id;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/sql/tenant_visitor_floor_policy_migrate_org_id.sql
|
||||
git commit -m "docs: add org_id data migration SQL for tenant_visitor_floor_policy"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 构建验证 + 发布准备
|
||||
|
||||
- [ ] **Step 1: 全量构建**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application && mvn clean install -DskipTests
|
||||
```
|
||||
|
||||
期望: BUILD SUCCESS,无编译错误。
|
||||
|
||||
- [ ] **Step 2: formatter 校验**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application && mvn formatter:validate -Dformatter-maven-plugin.version=2.16.0
|
||||
```
|
||||
|
||||
期望: 无格式化违规。
|
||||
|
||||
- [ ] **Step 3: 生成发布包**
|
||||
|
||||
```bash
|
||||
bash scripts/release-cw-elevator-application.sh 2.0.10
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 提交发布包**
|
||||
|
||||
```bash
|
||||
git add releases/
|
||||
git commit -m "release: cw-elevator-application v2.0.10 with org_id policy fix"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 回滚方案
|
||||
|
||||
| 步骤 | 操作 |
|
||||
|------|------|
|
||||
| 1. 回滚应用包 | 部署旧版本 JAR(用 `business_id` 查询的代码) |
|
||||
| 2. 回滚 DDL(可选) | `DROP INDEX uk_org_building; ALTER TABLE ... DROP COLUMN org_id; ADD UNIQUE KEY uk_biz_building (business_id, building_id);` |
|
||||
| 3. 恢复数据(可选) | `UPDATE tenant_visitor_floor_policy SET business_id = '252463...' WHERE org_id IS NOT NULL;` |
|
||||
|
||||
> DDL 回滚不影响旧代码行为(旧代码不查 `org_id` 列)。
|
||||
|
||||
---
|
||||
|
||||
## 发布顺序(生产环境)
|
||||
|
||||
```
|
||||
1. DDL 上线(Task 1) → 表结构变更,不影响线上行为
|
||||
2. 数据迁移(Task 7) → 运维手工填 org_id
|
||||
3. 发应用包(Task 8) → 代码切换到 org_id 查询
|
||||
4. 验证(Task 8 后) → 抽样确认策略生效
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
- [ ] DDL 在开发库执行成功
|
||||
- [ ] `TenantVisitorFloorPolicyDto` 有 `orgId` 字段
|
||||
- [ ] Mapper XML/Java 使用 `org_id` 查询
|
||||
- [ ] DAO 接口/实现已切换
|
||||
- [ ] `addVisitor` 使用 `findPolicyByOrgIds` + `resolveEffectiveFloors`
|
||||
- [ ] W2 修复:JSON 解析失败打 ERROR 而非 WARN
|
||||
- [ ] 错误码 76260533 已在资源文件注册
|
||||
- [ ] 数据迁移 SQL 已编写
|
||||
- [ ] `mvn clean install` 通过
|
||||
- [ ] `mvn formatter:validate` 通过
|
||||
@@ -0,0 +1,544 @@
|
||||
# org_id 策略修复验证脚本 — 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 编写 Python 无鉴权验证脚本 `verify_org_policy_fix.py`,自动准备测试数据、执行 7 个用例、清理数据、输出 JSON 报告。
|
||||
|
||||
**Architecture:** 单脚本 4 个 Phase:Phase0 健康检查 → Phase1 MySQL 准备数据 → Phase2 HTTP 逐用例调用 add/visitor + passRule/image → Phase3 MySQL 清理 → Phase4 输出 JSON。复用现有 `quick_verify_visitor_floor_policy.py` 的 noauth 调用模式。
|
||||
|
||||
**Tech Stack:** Python 3.8+, `requests`, `pymysql`, JSON
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-05-01-org-policy-verify-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 脚本骨架与配置
|
||||
|
||||
**Files:**
|
||||
- Create: `maven-cw-elevator-application/tools/visitor_floor_verification/scripts/verify_org_policy_fix.py`
|
||||
|
||||
- [ ] **Step 1: 写入脚本骨架**
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""org_id 策略修复 — 无鉴权验证脚本"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pymysql
|
||||
import requests
|
||||
|
||||
# ===== 配置常量 =====
|
||||
DB_CONFIG = {
|
||||
"host": "192.168.3.12",
|
||||
"port": 3307,
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"db_org": "component-organization",
|
||||
"db_elevator": "cw-elevator-application",
|
||||
}
|
||||
|
||||
BUSINESS_ID = "2524639890ba4f2cba9ba1a4eeaa4015"
|
||||
|
||||
# 测试用组织节点
|
||||
ORG_1403 = "72fb65ec5de94201b909a98b8bae1892"
|
||||
ORG_1405 = "2095de3d541f44eba686c78fda68336f"
|
||||
ORG_GUANGFA = "488b8ad049bb43408a6fbcc50bcb89ac"
|
||||
|
||||
# 被访人
|
||||
HOST_CHEN = "1060601019894960128" # 陈国辉 (1403+星中心)
|
||||
HOST_WANG = "1090779433129840640" # 王姣 (1405)
|
||||
HOST_QIN = "1072908835884208128" # 秦夏 (广发基金)
|
||||
|
||||
# 访客(测试专用号段)
|
||||
VISITOR_IDS = [
|
||||
"9199000100000000001", "9199000100000000002", "9199000100000000003",
|
||||
"9199000100000000004", "9199000100000000005", "9199000100000000006",
|
||||
"9199000100000000007",
|
||||
]
|
||||
|
||||
ZONE_28F = "605560545117995008"
|
||||
ZONE_99F = "605560540000000000" # 不存在,用于 T3
|
||||
|
||||
OK_CODES = {"0", "200"}
|
||||
|
||||
TEST_CASES = [
|
||||
{
|
||||
"id": "T1", "name": "有策略→allow替换floorList",
|
||||
"host_id": HOST_CHEN, "visitor_id": VISITOR_IDS[0],
|
||||
"policy_id": "policy_t1_1403", "expected_pass": True,
|
||||
"expected_floors": [ZONE_28F],
|
||||
},
|
||||
{
|
||||
"id": "T2", "name": "无策略→floorList",
|
||||
"host_id": HOST_WANG, "visitor_id": VISITOR_IDS[1],
|
||||
"policy_id": None, "expected_pass": True,
|
||||
"expected_floors": None, # 不做楼层精确比对,只验证成功
|
||||
},
|
||||
{
|
||||
"id": "T3", "name": "allow含无效zone→拒绝",
|
||||
"host_id": HOST_CHEN, "visitor_id": VISITOR_IDS[2],
|
||||
"policy_id": "policy_t3_invalid", "expected_pass": False,
|
||||
"expected_code": "76260533",
|
||||
},
|
||||
{
|
||||
"id": "T4", "name": "多组织命中第一个策略",
|
||||
"host_id": HOST_CHEN, "visitor_id": VISITOR_IDS[3],
|
||||
"policy_id": "policy_t1_1403", "expected_pass": True,
|
||||
"expected_floors": [ZONE_28F],
|
||||
},
|
||||
{
|
||||
"id": "T5", "name": "enabled=0等同无策略",
|
||||
"host_id": HOST_CHEN, "visitor_id": VISITOR_IDS[4],
|
||||
"policy_id": "policy_t5_disabled", "expected_pass": True,
|
||||
"expected_floors": None,
|
||||
},
|
||||
{
|
||||
"id": "T6", "name": "UC-02策略优先",
|
||||
"host_id": HOST_CHEN, "visitor_id": VISITOR_IDS[5],
|
||||
"policy_id": "policy_t1_1403", "expected_pass": True,
|
||||
"expected_floors": [ZONE_28F],
|
||||
"floor_ids_override": ["605560541473144832"], # 传6F,策略应覆盖为28F
|
||||
},
|
||||
{
|
||||
"id": "T7", "name": "广发基金迁移验证",
|
||||
"host_id": HOST_QIN, "visitor_id": VISITOR_IDS[6],
|
||||
"policy_id": "gf_vstr_policy_guangfa_fund_001x", "expected_pass": True,
|
||||
"expected_floors": [ZONE_28F],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def parse_args():
|
||||
p = argparse.ArgumentParser(description="org_id 策略修复验证")
|
||||
p.add_argument("--elevator-base-url", default="http://127.0.0.1:18081")
|
||||
p.add_argument("--skip-db", action="store_true", help="跳过数据库准备/清理")
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
print(f"elevator: {args.elevator_base_url}")
|
||||
print(f"skip-db: {args.skip_db}")
|
||||
print(f"cases: {len(TEST_CASES)}")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证导入可用**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application/tools/visitor_floor_verification
|
||||
python3 -c "import requests; import pymysql; print('OK')"
|
||||
```
|
||||
|
||||
期望: `OK`
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add maven-cw-elevator-application/tools/visitor_floor_verification/scripts/verify_org_policy_fix.py
|
||||
git commit -m "test: scaffold verify_org_policy_fix.py with config and test cases"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Phase 0 — 健康检查 + Phase 1 — 数据准备/清理
|
||||
|
||||
**Files:**
|
||||
- Modify: `verify_org_policy_fix.py` (追加函数)
|
||||
|
||||
- [ ] **Step 1: 添加健康检查函数**
|
||||
|
||||
```python
|
||||
def health_check(base_url: str) -> bool:
|
||||
"""GET /actuator/health"""
|
||||
try:
|
||||
r = requests.get(f"{base_url}/actuator/health", timeout=10)
|
||||
ok = r.status_code == 200
|
||||
print(f"[HEALTH] {base_url} -> {r.status_code} {'OK' if ok else 'FAIL'}")
|
||||
return ok
|
||||
except Exception as e:
|
||||
print(f"[HEALTH] {base_url} -> ERROR: {e}")
|
||||
return False
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 添加数据库连接函数**
|
||||
|
||||
```python
|
||||
def get_db_conn():
|
||||
return pymysql.connect(
|
||||
host=DB_CONFIG["host"], port=DB_CONFIG["port"],
|
||||
user=DB_CONFIG["user"], password=DB_CONFIG["password"],
|
||||
database=DB_CONFIG["db_elevator"],
|
||||
charset="utf8mb4", autocommit=True,
|
||||
)
|
||||
|
||||
|
||||
def execute_sql(sql: str, params=None):
|
||||
conn = get_db_conn()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, params)
|
||||
finally:
|
||||
conn.close()
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 添加数据准备函数**
|
||||
|
||||
```python
|
||||
def prepare_test_data():
|
||||
"""INSERT 测试策略 + UPDATE 广发基金 org_id"""
|
||||
policies = [
|
||||
("policy_t1_1403", ORG_1403, f'["{ZONE_28F}"]', 1),
|
||||
("policy_t3_invalid", ORG_1403, f'["{ZONE_28F}","{ZONE_99F}"]', 1),
|
||||
("policy_t5_disabled", ORG_1403, f'["{ZONE_28F}"]', 0),
|
||||
]
|
||||
for pid, oid, zones_json, enabled in policies:
|
||||
execute_sql("DELETE FROM tenant_visitor_floor_policy WHERE id=%s", (pid,))
|
||||
execute_sql(
|
||||
"INSERT INTO tenant_visitor_floor_policy "
|
||||
"(id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at) "
|
||||
"VALUES (%s, %s, NULL, 'INTERSECT_ALLOWLIST', %s, NULL, %s, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000)",
|
||||
(pid, oid, zones_json, enabled),
|
||||
)
|
||||
print(f" INSERT policy {pid} org={oid} enabled={enabled}")
|
||||
|
||||
# 广发基金迁移
|
||||
execute_sql(
|
||||
"UPDATE tenant_visitor_floor_policy SET org_id=%s WHERE id='gf_vstr_policy_guangfa_fund_001x'",
|
||||
(ORG_GUANGFA,),
|
||||
)
|
||||
print(f" UPDATE 广发基金 org_id={ORG_GUANGFA}")
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 添加数据清理函数**
|
||||
|
||||
```python
|
||||
def cleanup_test_data():
|
||||
"""DELETE 测试策略 + 回滚广发基金 org_id"""
|
||||
for pid in ["policy_t1_1403", "policy_t3_invalid", "policy_t5_disabled"]:
|
||||
execute_sql("DELETE FROM tenant_visitor_floor_policy WHERE id=%s", (pid,))
|
||||
print(f" DELETE {pid}")
|
||||
|
||||
execute_sql(
|
||||
"UPDATE tenant_visitor_floor_policy SET org_id=NULL WHERE id='gf_vstr_policy_guangfa_fund_001x'"
|
||||
)
|
||||
print(" UPDATE 广发基金 org_id=NULL (回滚)")
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 测试 DB 函数**
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import verify_org_policy_fix as v
|
||||
v.prepare_test_data()
|
||||
print('prepared')
|
||||
v.cleanup_test_data()
|
||||
print('cleaned')
|
||||
"
|
||||
```
|
||||
|
||||
期望: 看到 INSERT/DELETE 输出,无异常。
|
||||
|
||||
- [ ] **Step 6: 提交**
|
||||
|
||||
```bash
|
||||
git add verify_org_policy_fix.py
|
||||
git commit -m "test: add Phase 0-1: health check + DB prepare/cleanup"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Phase 2 — noauth HTTP 调用 + 回读验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `verify_org_policy_fix.py` (追加函数)
|
||||
|
||||
- [ ] **Step 1: 添加 noauth 请求头构建**
|
||||
|
||||
```python
|
||||
def build_noauth_headers() -> Dict[str, str]:
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"businessid": BUSINESS_ID,
|
||||
}
|
||||
|
||||
|
||||
def now_ms() -> int:
|
||||
return int(time.time() * 1000)
|
||||
|
||||
|
||||
def tomorrow_ms() -> int:
|
||||
return int((time.time() + 86400) * 1000)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 添加 add_visitor 调用函数**
|
||||
|
||||
```python
|
||||
def call_add_visitor(base_url: str, person_id: str, visitor_id: str,
|
||||
floor_ids: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""POST /elevator/person/add/visitor"""
|
||||
body = {
|
||||
"personId": person_id,
|
||||
"visitorId": visitor_id,
|
||||
"floorIds": floor_ids if floor_ids is not None else [],
|
||||
"begVisitorTime": now_ms(),
|
||||
"endVisitorTime": tomorrow_ms(),
|
||||
}
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{base_url}/elevator/person/add/visitor",
|
||||
json=body, headers=build_noauth_headers(), timeout=30,
|
||||
)
|
||||
return {
|
||||
"http_status": r.status_code,
|
||||
"body": r.json() if r.headers.get("content-type", "").startswith("application/json") else r.text,
|
||||
}
|
||||
except Exception as e:
|
||||
return {"http_status": 0, "error": str(e)}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 添加 passRule/image 回读函数**
|
||||
|
||||
```python
|
||||
def call_passrule_image(base_url: str, visitor_id: str) -> Dict[str, Any]:
|
||||
"""POST /elevator/passRule/image"""
|
||||
body = {"personId": visitor_id}
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{base_url}/elevator/passRule/image",
|
||||
json=body, headers=build_noauth_headers(), timeout=30,
|
||||
)
|
||||
return {
|
||||
"http_status": r.status_code,
|
||||
"body": r.json() if r.headers.get("content-type", "").startswith("application/json") else r.text,
|
||||
}
|
||||
except Exception as e:
|
||||
return {"http_status": 0, "error": str(e)}
|
||||
|
||||
|
||||
def extract_zone_ids(passrule_response: Dict) -> List[str]:
|
||||
"""从 passRule/image 响应中提取 zoneId 列表"""
|
||||
try:
|
||||
datas = passrule_response["body"]["data"]["datas"]
|
||||
return [d["zoneId"] for d in datas if "zoneId" in d]
|
||||
except (KeyError, TypeError):
|
||||
return []
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 测试 HTTP 调用(需 V2 运行中)**
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import verify_org_policy_fix as v
|
||||
r = v.call_add_visitor('http://127.0.0.1:18081', '1060601019894960128', '9199000100000000001')
|
||||
print(json.dumps(r, indent=2, ensure_ascii=False))
|
||||
"
|
||||
```
|
||||
|
||||
期望: HTTP 200,响应中包含 `success` 字段。
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add verify_org_policy_fix.py
|
||||
git commit -m "test: add Phase 2: noauth HTTP calls + passRule/image extraction"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Phase 2 — 用例执行器 + Phase 3-4 — 报告
|
||||
|
||||
**Files:**
|
||||
- Modify: `verify_org_policy_fix.py` (追加函数 + main)
|
||||
|
||||
- [ ] **Step 1: 添加用例执行函数**
|
||||
|
||||
```python
|
||||
def run_case(base_url: str, case: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""执行单个用例,返回结果 dict"""
|
||||
cid = case["id"]
|
||||
print(f"\n[{cid}] {case['name']}")
|
||||
|
||||
floor_ids = case.get("floor_ids_override")
|
||||
|
||||
# Step A: 确保正确的策略行生效
|
||||
pid = case.get("policy_id")
|
||||
if pid:
|
||||
# T3: 需要切换到 policy_t3_invalid(先停用 policy_t1_1403)
|
||||
if cid == "T3":
|
||||
execute_sql("DELETE FROM tenant_visitor_floor_policy WHERE id='policy_t1_1403'")
|
||||
print(f" [DB] 临时删除 policy_t1_1403 以启用 T3 策略")
|
||||
|
||||
result = {"id": cid, "name": case["name"]}
|
||||
|
||||
# Step B: add/visitor
|
||||
r = call_add_visitor(base_url, case["host_id"], case["visitor_id"], floor_ids)
|
||||
result["add_visitor"] = {
|
||||
"http_status": r.get("http_status"),
|
||||
"success": r.get("body", {}).get("success") if isinstance(r.get("body"), dict) else None,
|
||||
"code": r.get("body", {}).get("code") if isinstance(r.get("body"), dict) else None,
|
||||
"message": r.get("body", {}).get("message") if isinstance(r.get("body"), dict) else None,
|
||||
"error": r.get("error"),
|
||||
}
|
||||
av = result["add_visitor"]
|
||||
business_ok = av["http_status"] == 200 and str(av.get("code", "")) in OK_CODES
|
||||
|
||||
# Step C: 判定
|
||||
if case["expected_pass"]:
|
||||
if business_ok:
|
||||
# 回读楼层
|
||||
pr = call_passrule_image(base_url, case["visitor_id"])
|
||||
actual_zones = extract_zone_ids(pr)
|
||||
result["passrule_image"] = {"zones": actual_zones}
|
||||
expected = case.get("expected_floors")
|
||||
if expected is not None:
|
||||
match = set(actual_zones) == set(expected)
|
||||
result["floor_match"] = match
|
||||
result["passed"] = match
|
||||
print(f" add/visitor OK, floors: actual={actual_zones} expected={expected} match={match}")
|
||||
else:
|
||||
result["passed"] = True
|
||||
print(f" add/visitor OK, floors={actual_zones} (no strict check)")
|
||||
else:
|
||||
result["passed"] = False
|
||||
print(f" expected success but got code={av.get('code')} msg={av.get('message')}")
|
||||
else:
|
||||
# 期望失败
|
||||
expected_code = case.get("expected_code")
|
||||
actual_code = str(av.get("code", ""))
|
||||
result["passed"] = (not business_ok) and (actual_code == expected_code)
|
||||
print(f" expected fail code={expected_code} actual={actual_code} passed={result['passed']}")
|
||||
|
||||
# Step D: 恢复策略(T3 执行后)
|
||||
if cid == "T3":
|
||||
execute_sql(
|
||||
"INSERT INTO tenant_visitor_floor_policy "
|
||||
"(id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at) "
|
||||
"VALUES ('policy_t1_1403', %s, NULL, 'INTERSECT_ALLOWLIST', %s, NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000)",
|
||||
(ORG_1403, f'["{ZONE_28F}"]'),
|
||||
)
|
||||
print(f" [DB] 恢复 policy_t1_1403")
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 添加报告生成函数**
|
||||
|
||||
```python
|
||||
def generate_report(results: List[Dict], base_url: str) -> Dict:
|
||||
passed = sum(1 for r in results if r.get("passed"))
|
||||
failed = len(results) - passed
|
||||
return {
|
||||
"test": "org_id policy fix verification",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"elevator_url": base_url,
|
||||
"mode": "noauth-probe",
|
||||
"business_id": BUSINESS_ID,
|
||||
"summary": {"total": len(results), "passed": passed, "failed": failed},
|
||||
"results": results,
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 完善 main 函数**
|
||||
|
||||
```python
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
base = args.elevator_base_url.rstrip("/")
|
||||
|
||||
# Phase 0
|
||||
if not health_check(base):
|
||||
print("FATAL: elevator service not reachable")
|
||||
sys.exit(1)
|
||||
|
||||
# Phase 1
|
||||
if not args.skip_db:
|
||||
print("\n=== Phase 1: prepare test data ===")
|
||||
prepare_test_data()
|
||||
|
||||
# Phase 2
|
||||
print(f"\n=== Phase 2: run {len(TEST_CASES)} cases ===")
|
||||
results = []
|
||||
for case in TEST_CASES:
|
||||
r = run_case(base, case)
|
||||
results.append(r)
|
||||
|
||||
# Phase 3
|
||||
if not args.skip_db:
|
||||
print("\n=== Phase 3: cleanup ===")
|
||||
cleanup_test_data()
|
||||
|
||||
# Phase 4
|
||||
report = generate_report(results, base)
|
||||
report_path = f"report/org-policy-fix-verify-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json"
|
||||
import os
|
||||
os.makedirs("report", exist_ok=True)
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n=== Report: {report_path} ===")
|
||||
print(f"Passed: {report['summary']['passed']}/{report['summary']['total']}")
|
||||
for r in results:
|
||||
status = "✅" if r.get("passed") else "❌"
|
||||
print(f" {status} [{r['id']}] {r['name']}")
|
||||
|
||||
sys.exit(0 if report["summary"]["failed"] == 0 else 1)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add verify_org_policy_fix.py
|
||||
git commit -m "test: add Phase 2-4: case runner + report generation + main entry"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 端到端运行验证
|
||||
|
||||
- [ ] **Step 1: 确保 V2 运行中**
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:18081/actuator/health
|
||||
```
|
||||
|
||||
期望: `{"status":"UP"}`
|
||||
|
||||
- [ ] **Step 2: 执行全量验证**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application/tools/visitor_floor_verification
|
||||
python3 scripts/verify_org_policy_fix.py --elevator-base-url http://127.0.0.1:18081
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 检查报告**
|
||||
|
||||
```bash
|
||||
ls -la report/org-policy-fix-verify-*.json | tail -1
|
||||
python3 -c "import json; r=json.load(open('$(ls -t report/org-policy-fix-verify-*.json | head -1)')); print(f\"{r['summary']['passed']}/{r['summary']['total']} passed\")"
|
||||
```
|
||||
|
||||
期望: `7/7 passed`
|
||||
|
||||
- [ ] **Step 4: 提交报告(可选,不提交 JSON 到 git)**
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
- [ ] `verify_org_policy_fix.py` 存在且可导入
|
||||
- [ ] Phase 0: `health_check()` 返回 True
|
||||
- [ ] Phase 1: `prepare_test_data()` 无异常
|
||||
- [ ] Phase 2: 7 个用例全部执行
|
||||
- [ ] Phase 3: `cleanup_test_data()` 无异常
|
||||
- [ ] Phase 4: JSON 报告生成,7/7 passed
|
||||
- [ ] 无脱敏泄露(报告中不出现真实姓名/手机号)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,327 @@
|
||||
# V2 完整环境部署 + V1/V2 对比测试 — 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans. Steps use checkbox (`- [ ]`) syntax.
|
||||
|
||||
**Goal:** 搭建 V2 电梯功能测试所需的最小服务集(infra + 3 Java 服务),运行 V1/V2 API 对拍及策略差异验证。
|
||||
|
||||
**Architecture:** Docker 提供 Consul/Redis/Nginx,复用 MySQL 192.168.3.12:3307,按 ninca-common → component-org → elevator V2 → elevator V1 顺序启动,最终执行 pytest 对拍 + curl 策略测试。
|
||||
|
||||
**Tech Stack:** Bash, Docker Compose v2, JDK 8, Maven 3.9, Python 3.10 + pytest, MySQL 5.7
|
||||
|
||||
**关联 Spec:** `docs/superpowers/specs/2026-05-01-v2-test-env-setup-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 前置状态
|
||||
|
||||
```bash
|
||||
MySQL: 192.168.3.12:3307 root/123456 ✅
|
||||
Docker: Compose v2.40.3 可用 ✅
|
||||
JDK 8: /usr/lib/jvm/java-8-openjdk-amd64 ✅
|
||||
部署包: 13 个 tar.gz, 7.2 GB ✅
|
||||
DB 数据: 11 个库已恢复, 策略表已创建 ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 启动基础设施 (Docker Compose)
|
||||
|
||||
**Files:**
|
||||
- Use: `scripts/test-env/docker-compose.infra.yml`
|
||||
- Use: `scripts/test-env/config/env.sh`
|
||||
|
||||
- [ ] **Step 1: 清理旧容器并启动**
|
||||
|
||||
```bash
|
||||
source scripts/test-env/config/env.sh
|
||||
cd scripts/test-env
|
||||
|
||||
docker rm -f v2test-consul v2test-redis v2test-nginx 2>/dev/null || true
|
||||
docker compose -f docker-compose.infra.yml down --remove-orphans 2>/dev/null || true
|
||||
docker compose -f docker-compose.infra.yml up -d
|
||||
|
||||
sleep 5
|
||||
curl -sf http://127.0.0.1:9517/v1/status/leader && echo "Consul UP" || echo "FAIL"
|
||||
redis-cli -p 6380 -a '1qaz!QAZ' PING && echo "Redis UP" || echo "FAIL"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证 MySQL**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 -e "SELECT 1" && echo "MySQL UP"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 启动 ninca-common (port 33010)
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/test-env/config/ninca-common-override.properties`
|
||||
- Use: `services/ninca-common/ninca_common_01-ninca_common_backend/ninca-common-backend-V2.9.2_20210730.jar`
|
||||
|
||||
**关键**: ninca-common 用 ShardingSphere,需覆盖 `spring.shardingsphere.datasource.ds0.jdbc-url`
|
||||
|
||||
- [ ] **Step 1: 创建 ShardingSphere 覆盖配置**
|
||||
|
||||
```bash
|
||||
source scripts/test-env/config/env.sh
|
||||
cat > $TEST_ENV_DIR/config/ninca-common-override.properties << EOF
|
||||
spring.shardingsphere.datasource.names=ds0
|
||||
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
|
||||
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
|
||||
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://$MYSQL_HOST:$MYSQL_PORT/ninca_common?useSSL=false&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
|
||||
spring.shardingsphere.datasource.ds0.username=$MYSQL_USER
|
||||
spring.shardingsphere.datasource.ds0.password=$MYSQL_PASS
|
||||
spring.shardingsphere.sharding.default-data-source-name=ds0
|
||||
EOF
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 启动**
|
||||
|
||||
```bash
|
||||
NC_JAR="$SERVICE_DIR/ninca-common/ninca_common_01-ninca_common_backend/ninca-common-backend-V2.9.2_20210730.jar"
|
||||
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -jar -Xmx1024m "$NC_JAR" \
|
||||
--server.port=33010 \
|
||||
--spring.config.additional-location="$TEST_ENV_DIR/config/ninca-common-override.properties" \
|
||||
--spring.cloud.consul.host=$CONSUL_HOST --spring.cloud.consul.port=$CONSUL_PORT \
|
||||
--spring.redis.host=$REDIS_HOST --spring.redis.port=$REDIS_PORT --spring.redis.password=$REDIS_PASS \
|
||||
&> /tmp/ninca-common-plan.log &
|
||||
echo "PID=$!"
|
||||
sleep 30
|
||||
curl -sf http://127.0.0.1:33010/health && echo " ✅" || echo " ❌"
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 如启动失败 → Python stub**
|
||||
|
||||
```bash
|
||||
# 若 ninca-common 无法启动,创建 stub 模拟 person 属性查询
|
||||
# 组件 org 的 person/detail 内部调 ninca-common,可用空响应 stub 绕过
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 启动 component-organization (port 33011)
|
||||
|
||||
**Files:**
|
||||
- Use: `services/component-org/.../ninca-common-component-organization-V2.9.2_20210730.jar`
|
||||
|
||||
- [ ] **Step 1: 确保 Quartz 表存在**
|
||||
|
||||
```bash
|
||||
mysql -h 192.168.3.12 -P 3307 -u root -p123456 -e "
|
||||
CREATE TABLE IF NOT EXISTS \`component-organization\`.QRTZ_LOCKS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL,
|
||||
PRIMARY KEY (SCHED_NAME, LOCK_NAME)
|
||||
) ENGINE=InnoDB;" 2>/dev/null
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 启动 (带 ninca-common Ribbon 路由)**
|
||||
|
||||
```bash
|
||||
COMP_JAR="$SERVICE_DIR/component-org/ninca_common_component_organization_01-ninca_common_component_organization/ninca-common-component-organization-V2.9.2_20210730.jar"
|
||||
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dlogging.config=/tmp/logback-comp-org.xml -jar -Xmx1024m "$COMP_JAR" \
|
||||
--server.port=33011 \
|
||||
--spring.datasource.url="jdbc:mysql://$MYSQL_HOST:$MYSQL_PORT/component-organization?useSSL=false&characterEncoding=utf-8" \
|
||||
--spring.datasource.username=$MYSQL_USER --spring.datasource.password=$MYSQL_PASS \
|
||||
--spring.cloud.consul.host=$CONSUL_HOST --spring.cloud.consul.port=$CONSUL_PORT \
|
||||
--spring.redis.host=$REDIS_HOST --spring.redis.port=$REDIS_PORT --spring.redis.password=$REDIS_PASS \
|
||||
--ninca-common.ribbon.listOfServers=127.0.0.1:33010 \
|
||||
&> /tmp/comp-org-plan.log &
|
||||
echo "PID=$!"
|
||||
sleep 30
|
||||
curl -sf http://127.0.0.1:17116/actuator/health | python3 -c "import sys,json;print(json.load(sys.stdin)['status'])" && echo " ✅"
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证 person/detail**
|
||||
|
||||
```bash
|
||||
curl -sf -X POST http://127.0.0.1:33011/component/person/detail \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":"1072908835884208128"}' | python3 -c "import sys,json;d=json.load(sys.stdin);print(f'code={d[\"code\"]}')"
|
||||
# 预期: code=00000000 (非 53014011)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 如果 still 53014011**
|
||||
|
||||
放弃启动 ninca-common,改为 Python stub 方案:
|
||||
- 创建 `scripts/test-env/stub-person-service.py`
|
||||
- 监听 33010,响应 `/health` 和任意 person 查询
|
||||
- 重启 comp-org(它只需 ninca-common 可达,不关心响应内容)
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 启动 elevator V2 (port 18081)
|
||||
|
||||
**Files:**
|
||||
- Use: `maven-cw-elevator-application/deploy/v2-maven/cw-elevator-application-2.0.9.jar`
|
||||
|
||||
- [ ] **Step 1: 重启 V2 带全量 Ribbon 路由**
|
||||
|
||||
```bash
|
||||
pkill -f "elevator.*18081" 2>/dev/null; sleep 2
|
||||
source scripts/test-env/config/env.sh
|
||||
DEPLOY_DIR="$REPO_ROOT/maven-cw-elevator-application/deploy/v2-maven"
|
||||
V2_JAR=$(ls -t "$DEPLOY_DIR"/cw-elevator-application-*.jar | head -1)
|
||||
|
||||
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -jar -Xmx2048m "$V2_JAR" \
|
||||
--server.port=18081 --spring.redis.port=6380 --spring.redis.password='1qaz!QAZ' \
|
||||
--spring.cloud.consul.host=127.0.0.1 --spring.cloud.consul.port=9517 \
|
||||
--ninca-common-component-organization.ribbon.listOfServers=127.0.0.1:33011 \
|
||||
--spring.config.location="$DEPLOY_DIR/" \
|
||||
&> /tmp/v2-plan.log &
|
||||
echo "PID=$!"
|
||||
sleep 35
|
||||
curl -sf http://127.0.0.1:18081/health && echo " ✅"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 启动 elevator V1 (port 18080)
|
||||
|
||||
**Files:**
|
||||
- Use: `maven-cw-elevator-application/deploy/v1-legacy/cw-elevator-application-V1.0.0.20211103.jar`
|
||||
|
||||
- [ ] **Step 1: 启动 V1**
|
||||
|
||||
```bash
|
||||
pkill -f "elevator.*18080" 2>/dev/null; sleep 2
|
||||
source scripts/test-env/config/env.sh
|
||||
V1_DIR="$REPO_ROOT/maven-cw-elevator-application/deploy/v1-legacy"
|
||||
V1_JAR=$(ls -t "$V1_DIR"/cw-elevator-application-*.jar | head -1)
|
||||
|
||||
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -jar -Xmx2048m "$V1_JAR" \
|
||||
--server.port=18080 --spring.redis.port=6380 --spring.redis.password='1qaz!QAZ' \
|
||||
--spring.cloud.consul.host=127.0.0.1 --spring.cloud.consul.port=9517 \
|
||||
--ninca-common-component-organization.ribbon.listOfServers=127.0.0.1:33011 \
|
||||
--spring.config.location="$V1_DIR/" \
|
||||
&> /tmp/v1-plan.log &
|
||||
echo "PID=$!"
|
||||
sleep 35
|
||||
curl -sf http://127.0.0.1:18080/health && echo " ✅"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 对拍测试
|
||||
|
||||
**Files:**
|
||||
- Use: `maven-cw-elevator-application/tools/elevator_api_parity/`
|
||||
|
||||
- [ ] **Step 1: 运行全量对拍**
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application/tools/elevator_api_parity
|
||||
ELEVATOR_BASE_OLD=http://127.0.0.1:18080 ELEVATOR_BASE_NEW=http://127.0.0.1:18081 \
|
||||
python3 -m pytest tests/ -v --tb=short -p no:allure_pytest
|
||||
# 预期: 8/8 passed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 策略差异验证
|
||||
|
||||
**Files:**
|
||||
- DB: `tenant_visitor_floor_policy` (广发基金 org_id=488b8ad049bb43408a6fbcc50bcb89ac)
|
||||
- 人员: `1072908835884208128` (秦夏)
|
||||
|
||||
- [ ] **Step 1: V1 add/visitor (无策略 → floorList 全集)**
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:18080/elevator/person/add/visitor \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"personId":"1072908835884208128","businessId":"2524639890ba4f2cba9ba1a4eeaa4015","visitorName":"test","phone":"13800000000"}'
|
||||
# 预期: code != 76260532, 使用 floorList 全集
|
||||
```
|
||||
|
||||
- [ ] **Step 2: V2 add/visitor (策略 → allow_zone_ids 替换)**
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:18081/elevator/person/add/visitor \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"personId":"1072908835884208128","businessId":"2524639890ba4f2cba9ba1a4eeaa4015","visitorName":"test","phone":"13800000000"}'
|
||||
# 预期: 策略生效 → 返回 allow_zone_ids 交集 或 floorList 全集 (V1 无策略)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 差异判定**
|
||||
|
||||
```bash
|
||||
# V1_CODE != V2_CODE → STRATEGY DIVERGENCE CONFIRMED
|
||||
# V1_CODE == V2_CODE → policy not triggered (check comp-org person/detail)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 失败回退 — Python stub 方案
|
||||
|
||||
**如果 Task 2-3 的 Java 服务无法启动:**
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/test-env/stub-person-service.py`
|
||||
|
||||
- [ ] **Step 1: 创建 stub (模拟 ninca-common + comp-org)**
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Stub: 模拟 component-organization person/detail,返回广发基金秦夏数据"""
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import json
|
||||
|
||||
class PersonStub(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
body_len = int(self.headers.get('Content-Length', 0))
|
||||
body = json.loads(self.rfile.read(body_len)) if body_len > 0 else {}
|
||||
person_id = body.get('id', body.get('personId', 'unknown'))
|
||||
|
||||
if self.path in ('/health', '/actuator/health'):
|
||||
self._json(200, {"status": "UP"})
|
||||
elif '/person/detail' in self.path or '/component/person/detail' in self.path:
|
||||
self._json(200, {
|
||||
"code": "00000000", "success": True, "message": "success",
|
||||
"data": {
|
||||
"id": person_id, "name": "秦夏", "businessId": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"phone": "13666667067",
|
||||
"organizationIds": ["488b8ad049bb43408a6fbcc50bcb89ac"],
|
||||
"floorList": ["605560541473144832", "605560541657694208"],
|
||||
"labelIds": [], "labelNames": []
|
||||
}
|
||||
})
|
||||
elif '/component/person/page' in self.path:
|
||||
self._json(200, {"code": "00000000", "success": True, "data": {"datas": [], "total": 0}})
|
||||
else:
|
||||
self._json(200, {"code": "00000000", "success": True, "data": None})
|
||||
|
||||
def do_GET(self):
|
||||
self._json(200, {"status": "UP"})
|
||||
|
||||
def _json(self, status, data):
|
||||
self.send_response(status)
|
||||
self.send_header('Content-Type', 'application/json;charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
|
||||
|
||||
HTTPServer(('127.0.0.1', 33010), PersonStub).serve_forever()
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 启动 stub 并重新配置电梯**
|
||||
|
||||
```bash
|
||||
python3 scripts/test-env/stub-person-service.py &
|
||||
echo "Stub PID=$!"
|
||||
# 重新启动 elevator V2,Feign 路由到 stub:33010
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实施依赖
|
||||
|
||||
```
|
||||
Task 1 (infra) ──→ Task 2 (ninca-common)
|
||||
↓ ↘ (fail → Task 8 stub)
|
||||
Task 3 (comp-org)
|
||||
↓
|
||||
Task 4 (elevator V2) + Task 5 (elevator V1)
|
||||
↓
|
||||
Task 6 (对拍) + Task 7 (策略差异)
|
||||
```
|
||||
|
||||
Task 4/5 可并行;Task 6/7 可在 4/5 完成后立即执行。
|
||||
@@ -0,0 +1,920 @@
|
||||
# 目录结构重组 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Restructure the monorepo directory tree — rename to kebab-case English, separate source/runtime/packages/archive, consolidate scripts, remove naming inconsistencies — while preserving all files and git history.
|
||||
|
||||
**Architecture:** The project spans two git repos: `源码/` (submodule, ~29K files) and the parent repo (星中心/, 部署包/, data_backup/, etc.). Each task specifies which repo it operates in. Cross-repo moves use `mv` + separate `git add`/`git rm` in each repo. No files are deleted — outdated content is relocated to `archive/`.
|
||||
|
||||
**Tech Stack:** git, bash, Python (path update scripts)
|
||||
|
||||
---
|
||||
|
||||
### Task 0: Safety Preflight
|
||||
|
||||
**Context:** Verify workspace state before any changes.
|
||||
|
||||
- [ ] **Step 0.1: Check git status in parent repo**
|
||||
|
||||
Run: `git status`
|
||||
Expected: clean working tree, or note ongoing work
|
||||
|
||||
- [ ] **Step 0.2: Check git status in submodule (源码/)**
|
||||
|
||||
Run: `git status`
|
||||
Expected: clean working tree (last commit was the spec doc)
|
||||
|
||||
- [ ] **Step 0.3: Record the current tree state for rollback**
|
||||
|
||||
Run: `git rev-parse HEAD` (parent repo) and `git rev-parse HEAD` (submodule)
|
||||
Expected: record these SHAs for rollback reference
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Parent Repo — Rename Top-Level Directories
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Rename 星中心/ → runtime/
|
||||
|
||||
**Files:**
|
||||
- Rename: `星中心/` → `runtime/`
|
||||
|
||||
- [ ] **Step 1.1: git mv 星中心 to runtime**
|
||||
|
||||
Run (from repo root):
|
||||
```bash
|
||||
git mv 星中心 runtime
|
||||
```
|
||||
Expected: directory renamed, no content changes
|
||||
|
||||
- [ ] **Step 1.2: Rename internal directories (remove `_01-` prefix)**
|
||||
|
||||
```bash
|
||||
git mv runtime/cwos_manager_01-cwos_manager runtime/cwos-manager
|
||||
git mv runtime/cwos_system_api_01-cwos_system_api runtime/cwos-system-api
|
||||
git mv runtime/cw-elevator-application-V1.0.0.20211103 runtime/elevator-v1
|
||||
git mv runtime/ninca_crk_std_01-ninca_crk_std_backend runtime/ninca-crk-std
|
||||
git mv runtime/ninca_qk_alarm_app_01-ninca_qk_alarm_app runtime/ninca-qk-alarm
|
||||
git mv runtime/ninca-crk-std-backend-V2.9.1_20210630 runtime/ninca-crk-std-lib
|
||||
```
|
||||
Expected: all 6 directories renamed
|
||||
|
||||
- [ ] **Step 1.3: Rename internal frontend subdirs (runtime/frontend/)**
|
||||
|
||||
```bash
|
||||
git mv runtime/frontend/cwos_manager_frontend runtime/frontend/cwos-manager-frontend
|
||||
```
|
||||
Expected: dir renamed
|
||||
|
||||
- [ ] **Step 1.4: Verify**
|
||||
|
||||
Run: `ls runtime/`
|
||||
Expected: `cwos-manager/ cwos-system-api/ elevator-v1/ frontend/ ninca-crk-std/ ninca-crk-std-lib/ ninca-qk-alarm/`
|
||||
|
||||
- [ ] **Step 1.5: Commit**
|
||||
|
||||
```bash
|
||||
git add runtime/
|
||||
git commit -m "refactor: rename 星中心/ => runtime/, normalize service dir names"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Rename 部署包/ → packages/
|
||||
|
||||
**Files:**
|
||||
- Rename: `部署包/` → `packages/`
|
||||
|
||||
- [ ] **Step 2.1: git mv**
|
||||
|
||||
Run (from repo root):
|
||||
```bash
|
||||
git mv 部署包 packages
|
||||
```
|
||||
Expected: directory renamed
|
||||
|
||||
- [ ] **Step 2.2: Rename tar.gz files (remove `01-` prefix)**
|
||||
|
||||
List all files first:
|
||||
```bash
|
||||
ls packages/*.tar.gz
|
||||
```
|
||||
|
||||
For each file, rename. Example pattern:
|
||||
```bash
|
||||
git mv packages/cwos_manager_01-cwos_manager.tar.gz packages/cwos-manager.tar.gz
|
||||
git mv packages/cwos_system_api_01-cwos_system_api.tar.gz packages/cwos-system-api.tar.gz
|
||||
git mv packages/ninca_common_backend_01-ninca_common_backend.tar.gz packages/ninca-common-backend.tar.gz
|
||||
git mv packages/ninca_common_component_organization_01-ninca_common_component_organization.tar.gz packages/ninca-common-component-organization.tar.gz
|
||||
git mv packages/ninca_common_monitor_app_01-ninca_common_monitor_app.tar.gz packages/ninca-common-monitor-app.tar.gz
|
||||
git mv packages/ninca_common_snap_app_01-ninca_common_snap_app.tar.gz packages/ninca-common-snap-app.tar.gz
|
||||
git mv packages/ninca_common_vehicle_app_01-ninca_common_vehicle_app.tar.gz packages/ninca-common-vehicle-app.tar.gz
|
||||
git mv packages/ninca_crk_std_01-ninca_crk_std_backend.tar.gz packages/ninca-crk-std-backend.tar.gz
|
||||
git mv packages/ninca_md_heat_analysis_01-ninca_md_heat_analysis_backend.tar.gz packages/ninca-md-heat-analysis-backend.tar.gz
|
||||
git mv packages/ninca_qk_alarm_app_01-ninca_qk_alarm_app.tar.gz packages/ninca-qk-alarm-app.tar.gz
|
||||
git mv packages/ninca-person-file-app-V2.9.2_20210216.tar.gz packages/ninca-person-file-app-v2.9.2.tar.gz
|
||||
git mv packages/frontend.tar.gz packages/frontend.tar.gz
|
||||
git mv packages/cw-elevator-application-V1.0.0.20211103.tar.gz packages/cw-elevator-application-v1.0.0.tar.gz
|
||||
```
|
||||
|
||||
- [ ] **Step 2.3: Rename expanded component directory**
|
||||
|
||||
```bash
|
||||
git mv packages/ninca_common_component_organization_01-ninca_common_component_organization packages/ninca-common-component-organization
|
||||
```
|
||||
Then rename sub-subdirs:
|
||||
```bash
|
||||
git mv packages/ninca-common-component-organization/ninca_common_component_organization_01-ninca_common_component_organization207 packages/ninca-common-component-organization/v207
|
||||
git mv packages/ninca-common-component-organization/ninca_common_component_organization_01-ninca_common_component_organization208 packages/ninca-common-component-organization/v208
|
||||
git mv packages/ninca-common-component-organization/ninca_common_component_organization_01-ninca_common_component_organization209 packages/ninca-common-component-organization/v209
|
||||
```
|
||||
|
||||
- [ ] **Step 2.4: Verify**
|
||||
|
||||
Run: `ls packages/` and `ls packages/ninca-common-component-organization/`
|
||||
|
||||
- [ ] **Step 2.5: Commit**
|
||||
|
||||
```bash
|
||||
git add packages/
|
||||
git commit -m "refactor: rename 部署包/ => packages/, normalize tar.gz names"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Rename data_backup/ → data-backups/
|
||||
|
||||
**Files:**
|
||||
- Rename: `data_backup/` → `data-backups/`
|
||||
|
||||
- [ ] **Step 3.1: git mv**
|
||||
|
||||
```bash
|
||||
git mv data_backup data-backups
|
||||
```
|
||||
|
||||
- [ ] **Step 3.2: Commit**
|
||||
|
||||
```bash
|
||||
git add data-backups/
|
||||
git commit -m "refactor: rename data_backup/ => data-backups/ (kebab-case)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Move cn/ and media/ to archive/
|
||||
|
||||
**Files:**
|
||||
- Move: `cn/` → `archive/miscellaneous/cn/`
|
||||
- Move: `media/` → `archive/miscellaneous/media/`
|
||||
- Create: `archive/miscellaneous/`
|
||||
|
||||
- [ ] **Step 4.1: Create archive directories**
|
||||
|
||||
```bash
|
||||
mkdir -p archive/miscellaneous
|
||||
```
|
||||
|
||||
- [ ] **Step 4.2: Move cn/ into archive**
|
||||
|
||||
```bash
|
||||
git mv cn archive/miscellaneous/cn
|
||||
```
|
||||
|
||||
- [ ] **Step 4.3: Move media/ into archive**
|
||||
|
||||
```bash
|
||||
git mv media archive/miscellaneous/media
|
||||
```
|
||||
|
||||
- [ ] **Step 4.4: Commit**
|
||||
|
||||
```bash
|
||||
git add archive/
|
||||
git commit -m "refactor: move cn/, media/ to archive/miscellaneous/"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Submodule (源码/) — Maven & Scripts Restructure
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Rename Maven projects (remove `maven-` prefix)
|
||||
|
||||
**Files (within 源码/):**
|
||||
- Rename: `maven-cloudwalk-cloud/` → `source/backend/cloudwalk-cloud/`
|
||||
- Rename: `maven-cloudwalk-device-manager/` → `source/backend/cloudwalk-device-manager/`
|
||||
- Rename: `maven-cloudwalk-device-sdk/` → `source/backend/cloudwalk-device-sdk/`
|
||||
- Rename: `maven-cloudwalk-intelligent-davinci-manager/` → `source/backend/intelligent-davinci-manager/`
|
||||
- Rename: `maven-cloudwalk-legacy-public/` → `source/backend/cloudwalk-legacy-public/`
|
||||
- Rename: `maven-cw-elevator-application/` → `source/backend/cw-elevator-application/`
|
||||
- Rename: `maven-cwos-common-aks/` → `source/backend/cwos-common-aks/`
|
||||
- Rename: `maven-cwos-device-authentication/` → `source/backend/cwos-device-authentication/`
|
||||
- Rename: `maven-cwos-resource/` → `source/backend/cwos-resource/`
|
||||
- Rename: `maven-intelligent-cwoscomponent/` → `source/backend/intelligent-cwoscomponent/`
|
||||
- Rename: `maven-ninca-common-component-organization/` → `source/backend/ninca-common-component-organization/`
|
||||
- Rename: `maven-ninca-crk-from-lib/` → `source/backend/ninca-crk-from-lib/`
|
||||
- Rename: `maven-ninca-qk-alarm/` → `source/backend/ninca-qk-alarm/`
|
||||
|
||||
Note: `源码/` (submodule) already means "source code" in Chinese. Creating `源码/source/` inside it would be redundant. Instead, Maven projects go under `源码/backend/`.
|
||||
|
||||
- [ ] **Step 5.1: Create backend/ directory**
|
||||
|
||||
Run (from 源码/):
|
||||
```bash
|
||||
mkdir -p backend
|
||||
```
|
||||
|
||||
- [ ] **Step 5.2: Move and rename all Maven projects**
|
||||
|
||||
```bash
|
||||
git mv maven-cloudwalk-cloud backend/cloudwalk-cloud
|
||||
git mv maven-cloudwalk-device-manager backend/cloudwalk-device-manager
|
||||
git mv maven-cloudwalk-device-sdk backend/cloudwalk-device-sdk
|
||||
git mv maven-cloudwalk-intelligent-davinci-manager backend/intelligent-davinci-manager
|
||||
git mv maven-cloudwalk-legacy-public backend/cloudwalk-legacy-public
|
||||
git mv maven-cw-elevator-application backend/cw-elevator-application
|
||||
git mv maven-cwos-common-aks backend/cwos-common-aks
|
||||
git mv maven-cwos-device-authentication backend/cwos-device-authentication
|
||||
git mv maven-cwos-resource backend/cwos-resource
|
||||
git mv maven-intelligent-cwoscomponent backend/intelligent-cwoscomponent
|
||||
git mv maven-ninca-common-component-organization backend/ninca-common-component-organization
|
||||
git mv maven-ninca-crk-from-lib backend/ninca-crk-from-lib
|
||||
git mv maven-ninca-qk-alarm backend/ninca-qk-alarm
|
||||
```
|
||||
|
||||
Expected: all 13 projects moved, check `ls backend/`
|
||||
|
||||
- [ ] **Step 5.3: Verify no broken symlinks or missing dirs**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ls backend/ | wc -l
|
||||
```
|
||||
Expected: 13
|
||||
|
||||
- [ ] **Step 5.4: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/ && git rm -r maven-cloudwalk-cloud maven-cloudwalk-device-manager maven-cloudwalk-device-sdk maven-cloudwalk-intelligent-davinci-manager maven-cloudwalk-legacy-public maven-cw-elevator-application maven-cwos-common-aks maven-cwos-device-authentication maven-cwos-resource maven-intelligent-cwoscomponent maven-ninca-common-component-organization maven-ninca-crk-from-lib maven-ninca-qk-alarm
|
||||
git commit -m "refactor: move Maven projects to backend/, remove maven- prefix"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Create source/frontend/ from frontend-source/
|
||||
|
||||
**Files:**
|
||||
- Move: `frontend-source/` → `source/frontend/`
|
||||
|
||||
Note: After Task 11 and Task 13, the old `frontend/` (runtime) is emptied. The actual frontend source (`frontend-source/`) takes its place.
|
||||
|
||||
- [ ] **Step 6.1: Remove empty frontend/ dir (runtime moved out)**
|
||||
|
||||
```bash
|
||||
rmdir frontend 2>/dev/null || echo "not empty, check manually"
|
||||
```
|
||||
|
||||
- [ ] **Step 6.2: Rename frontend-source to frontend**
|
||||
|
||||
```bash
|
||||
git mv frontend-source frontend
|
||||
```
|
||||
|
||||
- [ ] **Step 6.3: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/
|
||||
git commit -m "refactor: rename frontend-source/ => frontend/ (actual source)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Consolidate scripts/ into submodule scripts/
|
||||
|
||||
**Files:**
|
||||
- Move: `scripts/` (from 源码/ root) → `source/backend/cw-elevator-application/scripts/` (keep with elevator project)
|
||||
- Create: `scripts/` (consolidated, at source/ root)
|
||||
|
||||
Actually, the design says to keep scripts at the parent source/ level, organized by function. Let me adjust.
|
||||
|
||||
- [ ] **Step 7.1: Create consolidated scripts/ structure**
|
||||
|
||||
```bash
|
||||
mkdir -p scripts/build scripts/deploy scripts/tools
|
||||
```
|
||||
|
||||
- [ ] **Step 7.2: Classify and move scripts**
|
||||
|
||||
Build/release scripts:
|
||||
```bash
|
||||
git mv scripts/release-cw-elevator-application.sh scripts/build/
|
||||
git mv scripts/format_maven_formatter_all.sh scripts/build/
|
||||
git mv scripts/check_maven_formatter_validate.sh scripts/build/
|
||||
```
|
||||
|
||||
Tools/analysis scripts:
|
||||
```bash
|
||||
git mv scripts/check_elevator_fatjar_lib_parity.sh scripts/tools/
|
||||
git mv scripts/compare_jar_to_sources.py scripts/tools/
|
||||
git mv scripts/decompile_ninca_crk_lib_modules.py scripts/tools/
|
||||
git mv scripts/generate_v1_v2_elevator_dependency_diff.py scripts/tools/
|
||||
git mv scripts/run_full_elevator_api_suite.sh scripts/tools/
|
||||
git mv scripts/run_v1v2_parity_automated.sh scripts/tools/
|
||||
git mv scripts/scan_java_tail_block_comments.py scripts/tools/
|
||||
git mv scripts/strip_jdcore_java_noise.py scripts/tools/
|
||||
git mv scripts/verify_frontend_runtime.py scripts/tools/
|
||||
git mv scripts/verify_v1_v2_config_load_order.sh scripts/tools/
|
||||
git mv scripts/collect_elevator_runtime_evidence.sh scripts/tools/
|
||||
git mv scripts/alibaba_heuristic_audit.py scripts/tools/
|
||||
```
|
||||
|
||||
The `test-env/` directory stays as is under scripts/:
|
||||
```bash
|
||||
# test-env is already at scripts/test-env/, keep it
|
||||
```
|
||||
|
||||
- [ ] **Step 7.3: Commit**
|
||||
|
||||
```bash
|
||||
git add scripts/
|
||||
git commit -m "refactor: consolidate scripts/ by function (build/deploy/tools)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Move self-referencing 源码/源码/ to archive
|
||||
|
||||
**Files (in submodule):**
|
||||
- Move: `源码/源码/` → parent repo `archive/miscellaneous/source-self-ref/`
|
||||
|
||||
This is a cross-repo move. The content is inside the submodule but should end up in the parent repo's archive.
|
||||
|
||||
- [ ] **Step 8.1: Create target directory in parent repo**
|
||||
|
||||
Run (from parent repo root):
|
||||
```bash
|
||||
mkdir -p archive/miscellaneous/source-self-ref
|
||||
```
|
||||
|
||||
- [ ] **Step 8.2: Move content using regular mv**
|
||||
|
||||
Run (from parent repo root):
|
||||
```bash
|
||||
mv 源码/源码 archive/miscellaneous/source-self-ref/
|
||||
```
|
||||
|
||||
- [ ] **Step 8.3: Register in parent repo**
|
||||
|
||||
```bash
|
||||
git add archive/miscellaneous/source-self-ref/
|
||||
git commit -m "archive: relocate 源码/源码/ (self-referencing dir)"
|
||||
```
|
||||
|
||||
- [ ] **Step 8.4: Remove from submodule**
|
||||
|
||||
Run (from 源码/ submodule):
|
||||
```bash
|
||||
git rm -r 源码
|
||||
git commit -m "refactor: remove self-referencing 源码/源码/ dir (moved to parent archive)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Move decompiled sources (反1/) to parent archive
|
||||
|
||||
**Files:**
|
||||
- Move: `反1/` (in submodule) → `archive/decompiled-sources/` (parent repo)
|
||||
|
||||
- [ ] **Step 9.1: Create target in parent repo**
|
||||
|
||||
```bash
|
||||
mkdir -p archive/decompiled-sources
|
||||
```
|
||||
|
||||
- [ ] **Step 9.2: Move via regular mv**
|
||||
|
||||
```bash
|
||||
mv 源码/反1/* archive/decompiled-sources/
|
||||
```
|
||||
|
||||
Verify: `ls archive/decompiled-sources/ | wc -l` should show 22 files
|
||||
|
||||
- [ ] **Step 9.3: Register in parent repo**
|
||||
|
||||
```bash
|
||||
git add archive/decompiled-sources/
|
||||
git commit -m "archive: add decompiled jar sources (from 反1/)"
|
||||
```
|
||||
|
||||
- [ ] **Step 9.4: Remove from submodule**
|
||||
|
||||
```bash
|
||||
git rm -r 反1
|
||||
git commit -m "refactor: move 反1/ (decompiled sources) to parent archive"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Move root-level files to archive
|
||||
|
||||
**Files:**
|
||||
- Move: `源码/visitor-noauth-verify-v20260430.zip` → `archive/miscellaneous/`
|
||||
- Move: `源码/elevator-app.log` → `archive/miscellaneous/`
|
||||
|
||||
- [ ] **Step 10.1: Cross-repo move zip and log**
|
||||
|
||||
```bash
|
||||
mv 源码/visitor-noauth-verify-v20260430.zip archive/miscellaneous/
|
||||
mv 源码/elevator-app.log archive/miscellaneous/
|
||||
```
|
||||
|
||||
- [ ] **Step 10.2: Register in parent repo**
|
||||
|
||||
```bash
|
||||
git add archive/miscellaneous/
|
||||
git commit -m "archive: add root-level zip and log files from 源码/"
|
||||
```
|
||||
|
||||
- [ ] **Step 10.3: Remove from submodule**
|
||||
|
||||
```bash
|
||||
git rm visitor-noauth-verify-v20260430.zip elevator-app.log
|
||||
git commit -m "refactor: move root-level artifacts to parent archive"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 11: Move .bak frontend dirs to archive
|
||||
|
||||
**Files (in submodule 源码/frontend/):**
|
||||
- Move all `*.bak*` directories from `源码/frontend/` to parent `archive/frontend-backups/`
|
||||
|
||||
- [ ] **Step 11.1: Identify all .bak dirs**
|
||||
|
||||
```bash
|
||||
ls -d 源码/frontend/*.bak* 2>/dev/null
|
||||
```
|
||||
|
||||
Expected list (from earlier discovery):
|
||||
```
|
||||
front_acs.bak20231012 front_acs.bak20231018 front_acs.bak20231020
|
||||
front_acs.bak20231027 front_acs.bak20231222
|
||||
canoe-account.bak20230928 canoe-car.bak20231020
|
||||
canoe-device.bak20231018 canoe-device.bak20231020
|
||||
canoe-person.bak20231018 canoe-person.bak20231024
|
||||
elevator-front.bak20231019
|
||||
```
|
||||
|
||||
Also include tar.gz archives in 源码/frontend/:
|
||||
```
|
||||
alarm-front.tar.gz canoe-car.tar.gz canoe-device.tar.gz.20240515
|
||||
heat-analysis-portal.tar.gz.zhongshanyi heat-analysis-portal.tar(4).gz
|
||||
```
|
||||
|
||||
- [ ] **Step 11.2: Create target and move**
|
||||
|
||||
```bash
|
||||
mkdir -p archive/frontend-backups
|
||||
```
|
||||
|
||||
Move each .bak dir:
|
||||
```bash
|
||||
for d in 源码/frontend/*.bak*; do
|
||||
name=$(basename "$d")
|
||||
mv "$d" "archive/frontend-backups/$name"
|
||||
done
|
||||
```
|
||||
|
||||
Move tar.gz archives:
|
||||
```bash
|
||||
for f in 源码/frontend/*.tar.gz*; do
|
||||
name=$(basename "$f")
|
||||
mv "$f" "archive/frontend-backups/$name"
|
||||
done
|
||||
```
|
||||
|
||||
- [ ] **Step 11.3: Register in parent repo**
|
||||
|
||||
```bash
|
||||
git add archive/frontend-backups/
|
||||
git commit -m "archive: add frontend .bak backups from 源码/frontend/"
|
||||
```
|
||||
|
||||
- [ ] **Step 11.4: Remove from submodule**
|
||||
|
||||
Run from 源码/:
|
||||
```bash
|
||||
for d in frontend/*.bak*; do git rm -r "$d"; done
|
||||
for f in frontend/*.tar.gz*; do git rm "$f"; done
|
||||
git commit -m "refactor: move frontend backups to parent archive"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 12: Move .bak frontend dirs from runtime/ to archive
|
||||
|
||||
**Files (in parent repo runtime/frontend/):**
|
||||
- Move all `*.bak*` directories from `runtime/frontend/` to `archive/frontend-backups/`
|
||||
|
||||
- [ ] **Step 12.1: Identify .bak dirs in runtime/frontend/**
|
||||
|
||||
```bash
|
||||
ls -d runtime/frontend/*.bak*
|
||||
```
|
||||
|
||||
- [ ] **Step 12.2: Move to archive**
|
||||
|
||||
```bash
|
||||
for d in runtime/frontend/*.bak*; do
|
||||
name=$(basename "$d")
|
||||
git mv "$d" "archive/frontend-backups/runtime-$name"
|
||||
done
|
||||
```
|
||||
|
||||
- [ ] **Step 12.3: Verify and commit**
|
||||
|
||||
```bash
|
||||
ls archive/frontend-backups/ | head -20
|
||||
git add archive/
|
||||
git commit -m "archive: add frontend backups from runtime/frontend/"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 13: Move frontend runtime to runtime/frontend/
|
||||
|
||||
**Files:**
|
||||
- Move: `源码/frontend/` (active non-.bak dirs) → `runtime/frontend/` (parent repo)
|
||||
|
||||
This merges the active frontend dirs from the submodule into the runtime.
|
||||
|
||||
- [ ] **Step 13.1: Identify non-.bak frontend dirs**
|
||||
|
||||
```bash
|
||||
ls -d 源码/frontend/*/ 2>/dev/null | grep -v '\.bak' | grep -v '\.tar\.gz'
|
||||
```
|
||||
|
||||
- [ ] **Step 13.2: Cross-repo move**
|
||||
|
||||
For each active frontend dir:
|
||||
```bash
|
||||
for d in 源码/frontend/*/; do
|
||||
name=$(basename "$d")
|
||||
# Skip .bak dirs
|
||||
if [[ "$name" != *".bak"* ]]; then
|
||||
rsync -a "$d" "runtime/frontend/$name"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
Use rsync/cp to copy, then verify, then remove from source.
|
||||
|
||||
- [ ] **Step 13.3: Check for duplicates with existing runtime/frontend/**
|
||||
|
||||
```bash
|
||||
diff -rq 源码/frontend/alarm-front runtime/frontend/alarm-front 2>/dev/null | head -20
|
||||
```
|
||||
|
||||
If identical, remove from 源码/:
|
||||
```bash
|
||||
git rm -r frontend/alarm-front
|
||||
```
|
||||
|
||||
- [ ] **Step 13.4: Register new files in parent**
|
||||
|
||||
```bash
|
||||
git add runtime/frontend/
|
||||
git commit -m "refactor: merge active frontend dirs from submodule to runtime/frontend/"
|
||||
```
|
||||
|
||||
- [ ] **Step 13.5: Remove from submodule**
|
||||
|
||||
```bash
|
||||
git rm -r frontend/alarm-front frontend/area-front frontend/attendancepc ...
|
||||
git commit -m "refactor: move frontend runtime to parent runtime/ (dedup)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 14: Move nginx/ to top level
|
||||
|
||||
**Files:**
|
||||
- Move: `源码/nginx-r1.0425/` → `nginx/` (parent repo root)
|
||||
|
||||
- [ ] **Step 14.1: Cross-repo move**
|
||||
|
||||
```bash
|
||||
cp -a 源码/nginx-r1.0425 nginx
|
||||
```
|
||||
|
||||
Verify: `ls nginx/`
|
||||
|
||||
- [ ] **Step 14.2: Register in parent repo**
|
||||
|
||||
```bash
|
||||
git add nginx/
|
||||
git commit -m "refactor: move nginx config from submodule to top-level"
|
||||
```
|
||||
|
||||
- [ ] **Step 14.3: Remove from submodule**
|
||||
|
||||
```bash
|
||||
git rm -r nginx-r1.0425
|
||||
git commit -m "refactor: move nginx config to parent repo top-level"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 15: Move artifacts/ to top level
|
||||
|
||||
**Files:**
|
||||
- Move: `源码/artifacts/` → `artifacts/` (parent repo root)
|
||||
|
||||
- [ ] **Step 15.1: Cross-repo move using cp**
|
||||
|
||||
```bash
|
||||
cp -a 源码/artifacts artifacts
|
||||
```
|
||||
|
||||
- [ ] **Step 15.2: Register in parent repo**
|
||||
|
||||
```bash
|
||||
git add artifacts/
|
||||
git commit -m "refactor: move artifacts/ from submodule to top-level"
|
||||
```
|
||||
|
||||
- [ ] **Step 15.3: Remove from submodule**
|
||||
|
||||
```bash
|
||||
git rm -r artifacts
|
||||
git commit -m "refactor: move artifacts/ to parent repo top-level"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 16: Move dev-support/ to docs/
|
||||
|
||||
**Files (in submodule):**
|
||||
- Move: `dev-support/` → `docs/dev-support/`
|
||||
|
||||
- [ ] **Step 16.1: git mv**
|
||||
|
||||
```bash
|
||||
git mv dev-support docs/dev-support
|
||||
```
|
||||
|
||||
- [ ] **Step 16.2: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/
|
||||
git commit -m "docs: merge dev-support/ into docs/ (per existing convention)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 17: Update AGENTS.md with new paths
|
||||
|
||||
**Files:**
|
||||
- Modify: `AGENTS.md` (submodule root)
|
||||
- Modify: `docs/AGENTS.md` (docs AGENTS.md)
|
||||
|
||||
- [ ] **Step 17.1: Update submodule AGENTS.md — OVERVIEW section**
|
||||
|
||||
Replace the OVERVIEW section to reflect new paths:
|
||||
|
||||
```markdown
|
||||
## OVERVIEW
|
||||
|
||||
CloudWalk (云从科技) 电梯/门禁/报警/人脸识别系统。Maven 多模块单体仓库 + 独立部署运行包。
|
||||
- **source/**: 可编辑的 Maven 源码树 (主要迭代区) + 前端源码
|
||||
- **runtime/**: 运行时部署包 + 配置文件 (生产/预发镜像)
|
||||
- **packages/**: 13 个组件的完整 tar.gz 压缩包
|
||||
- **data-backups/**: 各服务数据库备份 (SQL gz)
|
||||
- **archive/**: 历史/参考/遗留内容
|
||||
- **docs/**: 文档中心
|
||||
- **scripts/**: 统一脚本 (build/deploy/test-env/tools)
|
||||
- **nginx/**: Nginx 配置
|
||||
```
|
||||
|
||||
- [ ] **Step 17.2: Update AGENTS.md — 顶层目录结构 section**
|
||||
|
||||
Replace the tree:
|
||||
```markdown
|
||||
```
|
||||
星河湾星中星/
|
||||
├── source/ # 可编辑的源码
|
||||
│ ├── backend/ # Maven 后端项目 (13 个)
|
||||
│ │ ├── cw-elevator-application/ # 电梯应用 (主力迭代)
|
||||
│ │ ├── intelligent-cwoscomponent/
|
||||
│ │ └── ...
|
||||
│ ├── frontend/ # 前端源码项目
|
||||
│ └── scripts/ # 构建/工具脚本
|
||||
├── runtime/ # 运行时部署包 (运行目录)
|
||||
│ ├── elevator-v1/ # V1 电梯应用部署
|
||||
│ ├── ninca-crk-std/ # CRK 人脸识别 GPU 后端
|
||||
│ └── cwos-manager/ # CWOS Manager (Docker)
|
||||
├── packages/ # 部署压缩包
|
||||
├── data-backups/ # 数据库备份
|
||||
├── archive/ # 历史/参考/遗留内容
|
||||
├── docs/ # 文档中心
|
||||
├── scripts/ # 统一脚本
|
||||
├── artifacts/ # 构建/还原产物
|
||||
├── nginx/ # Nginx 配置
|
||||
└── logs/ # 运行日志
|
||||
```
|
||||
```
|
||||
|
||||
- [ ] **Step 17.3: Update all path references in AGENTS.md**
|
||||
|
||||
Replace remaining old paths:
|
||||
- `maven-cw-elevator-application/` → `backend/cw-elevator-application/`
|
||||
- `maven-intelligent-cwoscomponent/` → `backend/intelligent-cwoscomponent/`
|
||||
- `deploy/v2-maven/` → `backend/cw-elevator-application/deploy/v2-maven/`
|
||||
- `scripts/test-env/` → `scripts/test-env/` (already correct)
|
||||
|
||||
- [ ] **Step 17.4: Commit**
|
||||
|
||||
```bash
|
||||
git add AGENTS.md
|
||||
git commit -m "docs: update AGENTS.md paths for directory restructure"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 18: Update test-env script paths
|
||||
|
||||
**Files:**
|
||||
- Modify: `scripts/test-env/setup.sh`
|
||||
- Modify: `scripts/test-env/config/env.sh`
|
||||
- Modify: `scripts/test-env/start-all.sh`
|
||||
- Modify: `scripts/test-env/stop-all.sh`
|
||||
- Modify: `scripts/test-env/prepare-services.sh`
|
||||
- Modify: `scripts/test-env/prepare-db.sh`
|
||||
- Modify: `scripts/test-env/health-check.sh`
|
||||
- Modify: `scripts/test-env/verify-functional.sh`
|
||||
|
||||
- [ ] **Step 18.1: Find all old path references in test-env/**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rn "maven-cw-elevator-application\|cw-elevator-application-V1.0.0\|星中心\|部署包\|data_backup" scripts/test-env/ --include="*.sh" --include="*.yml" --include="*.py" --include="*.env"
|
||||
```
|
||||
|
||||
Record all matches.
|
||||
|
||||
- [ ] **Step 18.2: Update each file**
|
||||
|
||||
For each reference found in step 18.1, replace:
|
||||
- `maven-cw-elevator-application` → `backend/cw-elevator-application`
|
||||
- `../../maven-cw-elevator-application` → `../backend/cw-elevator-application`
|
||||
- `cw-elevator-application-V1.0.0.20211103` → `elevator-v1`
|
||||
- `星中心` → `runtime`
|
||||
- `部署包` → `packages`
|
||||
- `data_backup` → `data-backups`
|
||||
- `cwos_manager_01-cwos_manager` → `cwos-manager`
|
||||
- `cwos_system_api_01-cwos_system_api` → `cwos-system-api`
|
||||
- `ninca_crk_std_01-ninca_crk_std_backend` → `ninca-crk-std`
|
||||
- `ninca_qk_alarm_app_01-ninca_qk_alarm_app` → `ninca-qk-alarm`
|
||||
|
||||
- [ ] **Step 18.3: Update docker-compose.infra.yml**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -n "maven-cw-elevator\|星中心\|部署包\|data_backup" scripts/test-env/docker-compose.infra.yml
|
||||
```
|
||||
Apply the same replacements.
|
||||
|
||||
- [ ] **Step 18.4: Commit**
|
||||
|
||||
```bash
|
||||
git add scripts/test-env/
|
||||
git commit -m "fix: update test-env script paths after directory restructure"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 19: Update elevator API parity tool paths
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/cw-elevator-application/tools/elevator_api_parity/` (Python test files, configs)
|
||||
|
||||
- [ ] **Step 19.1: Find old path references**
|
||||
|
||||
```bash
|
||||
grep -rn "maven-cw-elevator-application\|deploy/v2-maven\|cw-elevator-application-V1.0.0" backend/cw-elevator-application/tools/ --include="*.py" --include="*.sh" --include="*.json" --include="*.yml" --include="*.cfg"
|
||||
```
|
||||
|
||||
- [ ] **Step 19.2: Update references**
|
||||
|
||||
Replace paths as needed. Key replacements:
|
||||
- `../../deploy/` → `../../deploy/` (relative path likely still works)
|
||||
- Any absolute or hard-coded paths need updating
|
||||
|
||||
- [ ] **Step 19.3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/cw-elevator-application/tools/
|
||||
git commit -m "fix: update parity tool paths after directory restructure"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 20: Clean up remaining submodule root
|
||||
|
||||
**Files (in submodule root 源码/):**
|
||||
- Remove now-empty directories that had all content moved out
|
||||
|
||||
- [ ] **Step 20.1: Check what remains in submodule root**
|
||||
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
Expected remaining:
|
||||
```
|
||||
.editorconfig
|
||||
.git/
|
||||
.gitignore
|
||||
.sisyphus/
|
||||
AGENTS.md
|
||||
docs/
|
||||
source/
|
||||
scripts/
|
||||
logs/
|
||||
frontend-source/ (moved to source/frontend/ - check if still exists)
|
||||
```
|
||||
|
||||
If `frontend-source/` still exists as empty: `git rm -r frontend-source`
|
||||
|
||||
- [ ] **Step 20.2: Clean up empty dirs**
|
||||
|
||||
```bash
|
||||
# Remove empty frontend-source if moved
|
||||
git rm -r frontend-source 2>/dev/null || echo "already gone"
|
||||
|
||||
# Remove empty dev-support if moved
|
||||
git rm -r dev-support 2>/dev/null || echo "already gone"
|
||||
```
|
||||
|
||||
- [ ] **Step 20.3: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "refactor: clean up empty directories after restructure"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 21: Final verification — parent repo
|
||||
|
||||
- [ ] **Step 21.1: Check parent repo git status**
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
Expected: clean tree, all changes committed
|
||||
|
||||
- [ ] **Step 21.2: Verify key directories exist**
|
||||
|
||||
```bash
|
||||
ls -d runtime packages data-backups archive scripts nginx artifacts docs logs 2>/dev/null
|
||||
```
|
||||
|
||||
- [ ] **Step 21.3: Verify no critical paths are broken**
|
||||
|
||||
```bash
|
||||
ls runtime/elevator-v1 runtime/cwos-manager runtime/ninca-crk-std runtime/ninca-qk-alarm 2>/dev/null | head -5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 22: Final verification — submodule (源码/)
|
||||
|
||||
- [ ] **Step 22.1: Check submodule git status**
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
Expected: clean tree
|
||||
|
||||
- [ ] **Step 22.2: Verify key directories**
|
||||
|
||||
```bash
|
||||
ls -d source/backend source/frontend docs scripts 2>/dev/null
|
||||
```
|
||||
|
||||
- [ ] **Step 22.3: Verify submodule is committed and parent tracks new ref**
|
||||
|
||||
```bash
|
||||
git log --oneline -3
|
||||
```
|
||||
Expected: shows 15+ commits for all restructuring work
|
||||
|
||||
- [ ] **Step 22.4: Update parent's submodule pointer**
|
||||
|
||||
```bash
|
||||
git add 源码
|
||||
git commit -m "chore: update submodule ref after directory restructure"
|
||||
```
|
||||
Reference in New Issue
Block a user