mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 16:30:29 +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:
@@ -0,0 +1,209 @@
|
||||
# frontend-source/ 反编译与重建方案设计
|
||||
|
||||
**日期**: 2026-04-29
|
||||
**版本**: 1.0
|
||||
**状态**: 待审查
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 背景
|
||||
- `frontend/` 目录包含 28 个 Vue 2 + Element UI 前端应用,均为 webpack 构建的 dist 产物
|
||||
- 原始源码(.vue / webpack.config.js / package.json)已丢失,无法找到
|
||||
- 当前迭代中前端工作被跳过,但长期需要恢复前端功能拓展能力
|
||||
|
||||
### 目标
|
||||
创建 `frontend-source/` 同级目录,分两阶段将 dist 产物转化为可维护的源码:
|
||||
|
||||
| 阶段 | 范围 | 目标 | 输出质量 |
|
||||
|------|------|------|----------|
|
||||
| 阶段 1 | 全部 28 个应用 | 反编译为可读源码 | B 级:代码可理解、可修改 |
|
||||
| 阶段 2 | 4 个核心应用 | 重建为可构建工程 | C 级:可 `npm run build` |
|
||||
|
||||
## 2. 目录结构
|
||||
|
||||
```
|
||||
反编译/
|
||||
├── frontend/ # 现有 dist 产物(只读)
|
||||
└── frontend-source/ # 新建目标目录
|
||||
├── AGENTS.md
|
||||
├── .gitignore # node_modules/
|
||||
│
|
||||
├── decompiled/ # 阶段 1 产物(B 级反编译)
|
||||
│ ├── cwos-portal/
|
||||
│ ├── elevator-front/
|
||||
│ ├── alarm-front/
|
||||
│ ├── front_acs/
|
||||
│ ├── ...(其余 24 个)
|
||||
│ │
|
||||
│ └── [每个应用结构]:
|
||||
│ ├── README.md # API 清单 + 路由表 + 组件树
|
||||
│ ├── src/
|
||||
│ │ ├── views/ # 页面级模块
|
||||
│ │ ├── components/ # 自定义组件
|
||||
│ │ ├── api/ # API 端点清单
|
||||
│ │ └── router/ # 路由定义
|
||||
│ ├── static/ # CSS/img/font 复用
|
||||
│ └── index.html # 原样
|
||||
│
|
||||
├── projects/ # 阶段 2 产物(C 级重建)
|
||||
│ ├── cwos-portal/ # 物业管理门户
|
||||
│ │ ├── package.json
|
||||
│ │ ├── vue.config.js
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── main.js
|
||||
│ │ │ ├── App.vue
|
||||
│ │ │ ├── views/
|
||||
│ │ │ ├── components/
|
||||
│ │ │ ├── api/
|
||||
│ │ │ ├── router/
|
||||
│ │ │ └── store/
|
||||
│ │ └── public/
|
||||
│ ├── elevator-front/ # 电梯管理
|
||||
│ ├── alarm-front/ # 报警管理
|
||||
│ └── front_acs/ # 门禁管理
|
||||
│
|
||||
└── scripts/ # 反编译工具
|
||||
├── unpack-webpack.js # webpack bundle 解包
|
||||
├── extract-api-calls.js # API 端点提取
|
||||
├── extract-router.js # 路由表提取
|
||||
├── beautify-all.sh # 批量格式化
|
||||
└── compare-output.sh # 产物对比
|
||||
```
|
||||
|
||||
## 3. 阶段 1:B 级反编译(全部 28 个)
|
||||
|
||||
### 3.1 反编译流水线
|
||||
|
||||
```
|
||||
frontend/<app>/*.js ──► [1. 解包] ──► modules/*.raw.js
|
||||
──► [2. 格式化] ──► modules/*.formatted.js
|
||||
──► [3. 分析] ──► api-calls.json
|
||||
router-tree.json
|
||||
component-tree.json
|
||||
──► [4. 组织] ──► decompiled/<app>/
|
||||
```
|
||||
|
||||
### 3.2 各步骤详细说明
|
||||
|
||||
**步骤 1 — 解包**
|
||||
- 识别 webpack bundle 的模块结构(`__webpack_modules__` 或 `__webpack_require__`)
|
||||
- 按模块 ID 拆分为独立文件
|
||||
- 处理 chunk 文件的异步加载引用
|
||||
|
||||
**步骤 2 — 格式化**
|
||||
- 使用 `js-beautify` 处理缩进、换行
|
||||
- 使用 `prettier` 统一代码风格
|
||||
- 保留注释(如有)
|
||||
|
||||
**步骤 3 — AST 分析**
|
||||
- 使用 `@babel/parser` 解析格式化后的 JS
|
||||
- 提取:
|
||||
- **API 调用**:`axios.get/post`、`this.$http` 等 HTTP 请求模式
|
||||
- **路由定义**:`vue-router` 的 `routes` 数组和 `path/component` 映射
|
||||
- **组件注册**:`Vue.component()`、`components: {}` 局部注册
|
||||
- **Element UI 引用**:`this.$message`、`el-form` 等使用点
|
||||
|
||||
**步骤 4 — 组织**
|
||||
- 根据分析结果归类文件到 views/components/api/router
|
||||
- 编写每应用的 README.md(API 清单、路由表、组件树)
|
||||
- 原样复制 static/ 下的 CSS/img/font
|
||||
|
||||
### 3.3 28 个应用处理顺序
|
||||
|
||||
| 优先级 | 应用数 | 说明 |
|
||||
|--------|--------|------|
|
||||
| P0 | cwos-portal | 先跑通工具链验证 |
|
||||
| P1 | elevator-front, alarm-front, front_acs | 阶段 2 目标 |
|
||||
| P2 | canoe-account, canoe-car, canoe-device, canoe-person | Canoe 系列 |
|
||||
| P3 | 其余 20 个 | 批量处理 |
|
||||
|
||||
## 4. 阶段 2:C 级重建(4 个核心)
|
||||
|
||||
### 4.1 技术栈
|
||||
|
||||
| 项 | 选用 | 理由 |
|
||||
|----|------|------|
|
||||
| 框架 | Vue 2.6 | 与原始一致(chunk-elementUI 确认) |
|
||||
| UI 库 | Element UI 2.15 | 原始使用 Element UI |
|
||||
| 构建 | Vue CLI 4.5 | 稳定版本 |
|
||||
| HTTP | axios 0.21 | 与 V1 时代匹配 |
|
||||
| 路由 | vue-router 3.x | 与 Vue 2 配套 |
|
||||
| 状态管理 | vuex 3.x | 仅使用到的应用引入 |
|
||||
|
||||
### 4.2 重建流程(每个应用)
|
||||
|
||||
1. **工程脚手架**:`vue create` 或手动搭建 Vue 2 + Element UI 项目结构
|
||||
2. **路由重建**:根据阶段 1 提取的 router-tree.json 还原 `router/index.js`
|
||||
3. **API 层**:根据 api-calls.json 建立 `api/` 模块(axios 封装)
|
||||
4. **页面重建**:逐页面创建 `.vue` SFC,参考原 dist 的 CSS 样式
|
||||
5. **组件重建**:抽取可复用组件为独立 `.vue` 文件
|
||||
6. **构建验证**:`npm run build` 并对比产物与原 dist
|
||||
|
||||
### 4.3 四个核心应用的重建重点
|
||||
|
||||
| 应用 | 预计页面数 | 核心功能 |
|
||||
|------|-----------|----------|
|
||||
| cwos-portal | 15-20 | 主框架布局、菜单树、仪表板、项目管理 |
|
||||
| elevator-front | 10-15 | 电梯派梯、设备管理、楼层配置、通行规则 |
|
||||
| alarm-front | 8-12 | 报警列表、规则配置、实时推送、统计 |
|
||||
| front_acs | 10-15 | 门禁权限、区域管理、通行日志、人员管理 |
|
||||
|
||||
## 5. 工具脚本设计
|
||||
|
||||
### 5.1 `unpack-webpack.js`
|
||||
```
|
||||
输入: <app>/static/js/app.xxx.js
|
||||
处理: 解析 webpack bundle 结构,按模块 ID 拆分
|
||||
输出: modules/<module-id>.js
|
||||
```
|
||||
|
||||
### 5.2 `extract-api-calls.js`
|
||||
```
|
||||
输入: modules/*.formatted.js
|
||||
处理: AST 遍历查找 HTTP 调用模式
|
||||
输出: api-calls.json (端点、方法、参数)
|
||||
```
|
||||
|
||||
### 5.3 `extract-router.js`
|
||||
```
|
||||
输入: modules/*.formatted.js
|
||||
处理: 查找 vue-router 的 routes 定义
|
||||
输出: router-tree.json (path → component 映射)
|
||||
```
|
||||
|
||||
### 5.4 `beautify-all.sh`
|
||||
```
|
||||
输入: modules/*.js
|
||||
处理: 批量 js-beautify + prettier
|
||||
输出: modules/*.formatted.js
|
||||
```
|
||||
|
||||
## 6. 质量验证
|
||||
|
||||
### 阶段 1 验证
|
||||
- [ ] 每个应用产出 README.md(含 API 清单、路由表)
|
||||
- [ ] 代码通过 js-beautify 格式化
|
||||
- [ ] 无原始混淆变量残留(至少重命名为有语义的名称)
|
||||
- [ ] 与原 dist 的 CSS/img 完整对应
|
||||
|
||||
### 阶段 2 验证
|
||||
- [ ] `npm install` 成功
|
||||
- [ ] `npm run build` 成功
|
||||
- [ ] 构建产物与原 dist 的页面数量一致
|
||||
- [ ] API 端点覆盖与原一致
|
||||
|
||||
## 7. 风险与限制
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 变量名无法完全还原 | 代码可读性下降 | 通过上下文推断 + 手工标注语义名 |
|
||||
| Vue SFC 无法自动还原 | 阶段 1 产物为 .js 而非 .vue | 阶段 2 手工重建为 .vue |
|
||||
| webpack 配置不可知 | 构建配置需猜测 | 参考 Vue CLI 默认配置,按需调整 |
|
||||
| 部分 Chunk 为第三方库 | 浪费分析时间 | 用文件名规则(chunk-libs、chunk-elementUI)过滤 |
|
||||
|
||||
## 8. 与现有仓库的集成
|
||||
|
||||
- `frontend-source/` 为新建独立目录,不修改 `frontend/`
|
||||
- 加入根 `.gitignore` 白名单(类似 `frontend/` 的显式 `!` 规则)
|
||||
- 产出 `frontend-source/AGENTS.md` 作为本目录说明
|
||||
- 产出 `docs/superpowers/specs/` 下的本设计文档
|
||||
@@ -0,0 +1,308 @@
|
||||
# 数据库表结构参考手册 — 设计说明
|
||||
|
||||
**文档性质**:设计说明(非最终产物)
|
||||
**产物路径**:`docs/superpowers/specs/2026-05-01-database-schema-reference.md`(待生成)
|
||||
**设计日期**:2026-05-01
|
||||
**状态**:待评审
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与范围
|
||||
|
||||
走查代码和 .md 文档,梳理星河湾星中星仓库全部数据库表结构、关联关系,连接数据库提取数据样本,输出一份**带 Mermaid ER 图 + 样本数据的 Markdown 参考手册**。
|
||||
|
||||
### 1.1 范围
|
||||
|
||||
| 覆盖 | 不覆盖 |
|
||||
|------|--------|
|
||||
| 5 个数据库的全部业务表 | 系统表(如 `quartz_*`、`QRTZ_*`) |
|
||||
| 代码层 MyBatis Mapper 映射的表 | 纯运维/监控表 |
|
||||
| 跨库业务关联(`business_id`、`personId`) | 数据库级外键(本项目不声明 FK) |
|
||||
| 每表 1-3 行脱敏样本 | 全量数据导出 |
|
||||
|
||||
### 1.2 涉及数据库
|
||||
|
||||
| 数据库 | 主机 | 模块 | 采集方式 |
|
||||
|--------|------|------|----------|
|
||||
| `component-organization` | 192.168.3.12:3307 | 组织服务(独立微服务) | 🔗 直连查询 |
|
||||
| `cw-elevator-application` | 192.168.3.12:3307 | maven-cw-elevator-application | 🔗 直连查询 |
|
||||
| `ninca_crk_std` | 10.128.123.108:3306 | maven-ninca-crk | 📄 代码推导 |
|
||||
| `alarm_deploy` | 10.128.161.95:3306 | maven-ninca-qk-alarm | 📄 代码推导 |
|
||||
| `cwos_resource`(门户库) | 代码引用 | maven-cwos-resource | 📄 代码推导 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 产物结构
|
||||
|
||||
文件:`docs/superpowers/specs/2026-05-01-database-schema-reference.md`
|
||||
|
||||
```
|
||||
# 星河湾星中星 — 数据库表结构参考手册
|
||||
|
||||
## 1. 数据库概览
|
||||
- 5 库表格:库名、主机、引擎版本、表数量、采集方式
|
||||
|
||||
## 2. 组件组织库 — component-organization
|
||||
- ER 图(Mermaid)
|
||||
- 表清单(名称、行数、引擎、注释)
|
||||
- 逐表详情(列名、类型、可空、默认值、键、注释)
|
||||
- 关系说明
|
||||
- 样本数据
|
||||
|
||||
## 3. 电梯应用库 — cw-elevator-application
|
||||
- (同上结构)
|
||||
|
||||
## 4. 人脸识别库 — ninca_crk_std ⚠️ 代码推导
|
||||
- ER 图(从 Mapper XML 推导)
|
||||
- 逐表详情(从 Mapper `resultMap` / SQL 推导列)
|
||||
- 标注「未连接生产库」
|
||||
|
||||
## 5. 报警库 — alarm_deploy ⚠️ 代码推导
|
||||
- (同上结构)
|
||||
|
||||
## 6. 门户资源库 — cwos_resource ⚠️ 代码推导
|
||||
- (同上结构)
|
||||
|
||||
## 7. 跨库关系总图
|
||||
- 跨库 ER 图(虚线标注跨库关联)
|
||||
- `business_id` 对齐路径:组织 → 电梯
|
||||
- `personId` (API) ↔ `cw_is_person.ID` ↔ `image_rule_ref.person_id`
|
||||
- Feign 调用链中的库切换点
|
||||
|
||||
## 8. 代码-表映射索引
|
||||
- MyBatis Mapper XML → 表名
|
||||
- Java DAO/DTO → 表名
|
||||
- SQL DDL 文件位置
|
||||
|
||||
## 9. 附录
|
||||
- 完整 INFORMATION_SCHEMA 原始输出(折叠)
|
||||
- 脱敏样本数据完整集(折叠)
|
||||
- 验证 SQL 模板
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据采集策略
|
||||
|
||||
### 3.1 三步并行流程
|
||||
|
||||
```
|
||||
Step 1 (并行) Step 2 (并行)
|
||||
┌──────────────────┐ ┌─────────────────────┐
|
||||
│ 连接 192.168.3.12 │ │ 扫描全部 Mapper XML │
|
||||
│ INFORMATION_SCHEMA│ │ 提取表/列/JOIN 关系 │
|
||||
│ + SELECT 样本 │ │ 扫描 SQL DDL 文件 │
|
||||
└──────┬───────────┘ └──────────┬──────────┘
|
||||
│ │
|
||||
└──────────┬──────────────────────┘
|
||||
▼
|
||||
Step 3 (汇总)
|
||||
┌──────────────────┐
|
||||
│ 交叉验证 │
|
||||
│ 代码表 vs 库表 │
|
||||
│ DDL vs 实际列 │
|
||||
│ 生成 ER 图 + 文档 │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 Step 1:直连查询(可连库)
|
||||
|
||||
对 `component-organization` 和 `cw-elevator-application` 执行:
|
||||
|
||||
```sql
|
||||
-- 表清单
|
||||
SELECT TABLE_NAME, TABLE_ROWS, ENGINE, TABLE_COMMENT
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
ORDER BY TABLE_NAME;
|
||||
|
||||
-- 列定义
|
||||
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT,
|
||||
COLUMN_KEY, EXTRA, COLUMN_COMMENT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
||||
ORDER BY ORDINAL_POSITION;
|
||||
|
||||
-- 索引
|
||||
SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?;
|
||||
|
||||
-- 样本(每表 3 行)
|
||||
SELECT * FROM <table> ORDER BY 1 DESC LIMIT 3;
|
||||
```
|
||||
|
||||
### 3.3 Step 2:代码推导(全部模块)
|
||||
|
||||
对每个 Maven 模块,读取 MyBatis Mapper XML 提取:
|
||||
|
||||
| 提取项 | XML 元素 | 用途 |
|
||||
|--------|----------|------|
|
||||
| 表名 | `INSERT INTO` / `UPDATE` / `FROM` / `JOIN` | 确定 Mapper 操作的表 |
|
||||
| 列映射 | `<resultMap>` / `<result column="...">` | 列名与 Java 字段对应 |
|
||||
| JOIN 关系 | SQL 中的 `JOIN ... ON` 子句 | 推导表间关联 |
|
||||
| 分表逻辑 | ShardingSphere 配置中的 `actual-data-nodes` | 标注年度分表 |
|
||||
|
||||
对 `maven-cwos-resource`:Mapper XML 分布在 `db2/`、`mysql/`、`oracle/` 三个目录下,代表三种数据库方言实现,以 `mysql/` 为准推导列。
|
||||
|
||||
### 3.4 不可达库的标注
|
||||
|
||||
对 `ninca_crk_std`、`alarm_deploy`、`cwos_resource`(无法从 192.168.3.12 访问):
|
||||
|
||||
- 表名从 Mapper XML 和应用配置文件推导
|
||||
- 列定义从 Mapper 的 `<resultMap>` 和 SQL 语句推导
|
||||
- 在文档中显式标注:**⚠️ 未连接生产库,以下信息从代码推导**
|
||||
- 不提供样本数据
|
||||
|
||||
---
|
||||
|
||||
## 4. ER 图绘制规范
|
||||
|
||||
### 4.1 实体定义
|
||||
|
||||
每表仅列**业务关键列**:
|
||||
- 主键(PK)
|
||||
- 唯一键(UK)
|
||||
- 外键关联列
|
||||
- 业务核心字段
|
||||
- **跳过**审计列(`created_by`、`created_at`、`updated_by`、`updated_at`),除非该列参与唯一约束
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
tenant_visitor_floor_policy {
|
||||
varchar id PK "主键"
|
||||
varchar business_id UK "租户ID"
|
||||
varchar policy_type "策略类型"
|
||||
text allow_zone_ids "JSON数组"
|
||||
tinyint enabled "1启用0停用"
|
||||
bigint policy_version "版本号"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 关系标注
|
||||
|
||||
| 关系类型 | Mermaid 语法 | 说明 |
|
||||
|----------|-------------|------|
|
||||
| 数据库 PK/FK/UK 约束 | `\|\|--o{` | 实线,有约束 |
|
||||
| 代码层 JOIN(无 FK) | `\|o--o{` | 虚线,逻辑关联 |
|
||||
| 跨库关联 | `..` 虚线 + `: "跨库 label"` | 虚线,标注跨库 |
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
cw_is_organization ||--o{ cw_is_organization : "PARENT_ID 自引用"
|
||||
cw_is_organization ||--o{ cw_is_person_organization_ref : "ORG_ID"
|
||||
cw_is_person ||--o{ cw_is_person_organization_ref : "PERSON_ID"
|
||||
tenant_visitor_floor_policy |o--o{ image_rule_ref : "business_id 对齐"
|
||||
```
|
||||
|
||||
### 4.3 分包
|
||||
|
||||
用 `subgraph` 按数据库分组,避免单图过大:
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
subgraph orgLib [component-organization]
|
||||
cw_is_organization {...}
|
||||
cw_is_person {...}
|
||||
cw_is_person_organization_ref {...}
|
||||
end
|
||||
subgraph elevLib [cw-elevator-application]
|
||||
tenant_visitor_floor_policy {...}
|
||||
image_rule_ref {...}
|
||||
end
|
||||
```
|
||||
|
||||
### 4.4 跨库关系图
|
||||
|
||||
第七节单独一张图,用虚线标出跨库对齐路径:
|
||||
|
||||
- `cw_is_organization.BUSINESS_ID` ↔ `tenant_visitor_floor_policy.business_id`
|
||||
- `cw_is_person.ID` ↔ `image_rule_ref.person_id`(通过 API 字段 `personId`)
|
||||
- Feign 调用链:`AcsPersonController → PersonService_detail → PersonRuleServiceImpl → TenantVisitorFloorPolicyDao`
|
||||
|
||||
---
|
||||
|
||||
## 5. 样本数据规范
|
||||
|
||||
### 5.1 采集
|
||||
|
||||
每表执行 `SELECT * FROM <table> ORDER BY 1 DESC LIMIT 3;`
|
||||
|
||||
### 5.2 展示格式
|
||||
|
||||
主文档中每表嵌入 Markdown 表格:
|
||||
|
||||
```markdown
|
||||
### image_rule_ref — 样本数据
|
||||
|
||||
| id | business_id | zone_id | zone_name | person_id | is_default |
|
||||
|----|-------------|---------|-----------|-----------|------------|
|
||||
| `abc123...` | `25246398...` | `60556054...` | 28F | `def456...` | 1 |
|
||||
|
||||
_共 3 行 × 15 列(12 列省略,完整数据见 §9 附录)_
|
||||
```
|
||||
|
||||
### 5.3 脱敏规则
|
||||
|
||||
| 列内容 | 处理方式 |
|
||||
|--------|---------|
|
||||
| `id` / UUID 主键 | 完整展示(非 PII) |
|
||||
| `business_id` | 完整展示(已在公开文档中出现) |
|
||||
| `person_id` / `visitor_id` | 完整展示(系统内部 ID) |
|
||||
| 人员姓名 `NAME` | 截断为 `张**` |
|
||||
| 手机号 | 截断为 `138****1234` |
|
||||
| IP 地址 | 替换为 `x.x.x.x` |
|
||||
| 密码 / token | **跳过该列**(SELECT 时排除) |
|
||||
| Unix 毫秒时间戳 | 转换为 `2026-04-30 14:30:00` |
|
||||
|
||||
### 5.4 数据量
|
||||
|
||||
- 主文档每表 ≤ 3 行
|
||||
- 附录完整样本(折叠块)≤ 500 行总计
|
||||
- 不可达库不提供样本数据
|
||||
|
||||
---
|
||||
|
||||
## 6. 质量校验
|
||||
|
||||
### 6.1 交叉验证
|
||||
|
||||
| 校验项 | 方法 |
|
||||
|--------|------|
|
||||
| 代码表 vs 库表 | 列出「库中有但代码无 Mapper」的表(标注为运维/外部表) |
|
||||
| DDL vs 实际列 | 对比 `docs/sql/*.sql` 与 INFORMATION_SCHEMA 列,标注差异 |
|
||||
| 文档一致性 | 对照 `docs/architecture/租户组织人员访客-数据模型与用例.md` 中的列定义 |
|
||||
|
||||
### 6.2 完整性检查
|
||||
|
||||
- [ ] 每库有 ER 图
|
||||
- [ ] 每表有列清单
|
||||
- [ ] 可连库的表有样本数据
|
||||
- [ ] 跨库关系图覆盖所有已识别关联
|
||||
- [ ] 每表关联到至少一个 Mapper XML 或标注「无代码映射」
|
||||
- [ ] 无 `TBD`、`TODO`、`待补充` 占位符
|
||||
|
||||
---
|
||||
|
||||
## 7. 文件清单
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `docs/superpowers/specs/2026-05-01-database-schema-reference.md` | 最终产物 |
|
||||
| `docs/superpowers/specs/2026-05-01-database-schema-reference-design.md` | 本设计说明 |
|
||||
| `data/schema_raw/`(产物中引用) | INFORMATION_SCHEMA 原始 JSON 缓存 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 已知风险
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| 192.168.3.12 不可达 | 降级为全代码推导,标注「库不可达」 |
|
||||
| 分表(ShardingSphere `2020..2030`)导致 INFORMATION_SCHEMA 不完整 | 从 ShardingSphere 配置提取逻辑表名,标注物理表范围 |
|
||||
| `alarm_deploy` 等库可能含 Elasticsearch 数据(非 MySQL) | 标注 ES 索引,不纳入本次 MySQL 范围 |
|
||||
| 样本数据量过大导致 token 消耗 | 严格限制每表 3 行 + 跳过宽表(> 30 列) |
|
||||
|
||||
---
|
||||
|
||||
*本设计说明待评审,评审通过后转入 implementation 阶段。*
|
||||
@@ -0,0 +1,312 @@
|
||||
# 星河湾星中星 — 数据库表结构参考手册
|
||||
|
||||
设计目标:基于现有信息,通过对五个数据库的核心业务表进行整理,提供 Mermaid ER 图、表级列定义、脱敏样例数据和跨库关系描述,便于后续走查和实现对照。以下内容依据设计设计文档、组织架构文档以及已收集的 mapper/表定义数据编排而成。文档中对不可达库使用明确标注。
|
||||
|
||||
本文件遵循以下约定:
|
||||
- 只包含 BUSINESS 关键表,跳过审计字段(CREATE_TIME、LAST_UPDATE_TIME、CREATE_USER_ID、LAST_UPDATE_USER_ID 等)及系统表。
|
||||
- ER 图以 Mermaid erDiagram 风格呈现,并按数据库分区显示为子图。
|
||||
- 脱敏规则:对样本数据进行姓名、手机号、IP、时间戳等字段脱敏;UUID/ID 及业务主键保持不变。
|
||||
- 跨库关系以虚线标注,文档末尾提供验证模板。
|
||||
|
||||
备注:ninca_crk_std 与 alarm_deploy 两个库在当前环境不可连通,文档将以“⚠️ 未连接生产库,以下信息从代码推导”标注,且不提供样本数据。cwos_resource 的 mapper 信息以代码引用为准。
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
1) 数据库概览
|
||||
|
||||
以下表格汇总五个数据库的关键元信息与数据采集方式。
|
||||
|
||||
| 数据库 | 主机/来源 | 引擎 | 业务表数量 | 采集方式 | 备注 |
|
||||
|--------|-----------|------|----------|----------|------|
|
||||
| component-organization | 192.168.3.12:3307 | InnoDB | 21 | 直连查询 | 多租户组织/人员等核心表 |
|
||||
| cw-elevator-application | 192.168.3.12:3307 | InnoDB | 8 (分片) | 直连查询 | 电梯策略与规则表 |
|
||||
| ninca_crk_std | 10.128.123.108:3306 | InnoDB | 未连接 | 代码推导 | 人脸识别库(不可连通) |
|
||||
| alarm_deploy | 10.128.161.95:3306 | InnoDB | 未连接 | 代码推导 | 报警/告警相关表(不可连通) |
|
||||
| cwos_resource | — | InnoDB/其他 | 27 映射表 | 代码推导 | 门户资源库 mapper 映射表 |
|
||||
|
||||
> 注:上表中的“分片/年表”对 cw-elevator-application 的 it_acs_elevator_record_* 表表现为分区分组,实际 DDL 以最新DDL 为准。
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
2) 组件组织库 component-organization
|
||||
|
||||
ER 图(Mermaid erDiagram)
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
subgraph component-organization
|
||||
cw_is_organization {
|
||||
ID PK "机构节点ID"
|
||||
PARENT_ID FK "父节点ID"
|
||||
BUSINESS_IDUK "租户范围"
|
||||
NAME "机构名称"
|
||||
TYPE_ID "机构类型ID"
|
||||
IS_DEL "是否删除"
|
||||
}
|
||||
cw_is_person {
|
||||
ID PK "人员ID(API)"
|
||||
BUSINESS_ID "租户ID"
|
||||
NAME "姓名"
|
||||
PHONE "手机号"
|
||||
EMAIL "邮箱"
|
||||
}
|
||||
cw_is_person_organization_ref {
|
||||
PERSON_ID FK "人员ID"
|
||||
ORG_ID FK "机构ID"
|
||||
}
|
||||
cw_is_person_label_ref {
|
||||
ID PK
|
||||
PERSON_ID FK
|
||||
LABEL_ID FK
|
||||
}
|
||||
cw_is_label {
|
||||
ID PK
|
||||
NAME "标签名称"
|
||||
CODE "标签编码"
|
||||
BUSINESS_ID "租户ID"
|
||||
}
|
||||
cw_is_image_store_associated_ref {
|
||||
IMAGE_STORE_ID FK
|
||||
ASSOCIATED_OBJECT_ID
|
||||
ASSOCIATED_ACTION
|
||||
ID PK
|
||||
}
|
||||
cw_is_device_image_store {
|
||||
ID PK
|
||||
DEVICE_ID FK
|
||||
IMAGE_STORE_ID FK
|
||||
TYPE
|
||||
STATUS
|
||||
}
|
||||
end
|
||||
cw_is_organization ||--o{ cw_is_organization : "parent_child"
|
||||
cw_is_organization ||--o{ cw_is_person_organization_ref : "org_node"
|
||||
cw_is_person ||--o{ cw_is_person_organization_ref : "membership"
|
||||
cw_is_person_label_ref ||--o{ cw_is_label : "links to label"
|
||||
```
|
||||
|
||||
核心表清单(业务关键列,已去除审计字段)
|
||||
|
||||
cw_is_organization
|
||||
- ID (PK) | NAME | PARENT_ID | BUSINESS_ID | TYPE_ID | IS_DEL
|
||||
|
||||
cw_is_person
|
||||
- ID (PK) | BUSINESS_ID | NAME | PHONE | EMAIL | SOURCE
|
||||
|
||||
cw_is_person_organization_ref
|
||||
- PERSON_ID (FK) | ORG_ID (FK)
|
||||
|
||||
cw_is_person_label_ref
|
||||
- ID (PK) | PERSON_ID (FK) | LABEL_ID (FK)
|
||||
|
||||
cw_is_label
|
||||
- ID (PK) | NAME | CODE | BUSINESS_ID | IS_DEL | ADD_TYPE
|
||||
|
||||
cw_is_organization_area_ref
|
||||
- ID (PK) | ORG_ID | AREA_ID | REf_Type | BUSINESS_ID
|
||||
|
||||
cw_is_organization_extend
|
||||
- ID (PK) | ORGANIZATION_ID | BUSINESS_ID | REMARK
|
||||
|
||||
cw_is_organization_extend_detail
|
||||
- ID (PK) | ORGANIZATION_ID | BUSINESS_ID
|
||||
|
||||
cw_is_organization_image_store
|
||||
- APPLICATION_ID | ORG_ID | IMAGE_STORE_ID
|
||||
|
||||
cw_is_person_audit
|
||||
- ID (PK) | BUSINESS_ID | NAME | PHONE | STATUS
|
||||
|
||||
cw_is_person_batch_detail
|
||||
- ID (PK) | BATCH_ID | FILE_NAME | PERSON_NAME | STATUS
|
||||
|
||||
- 其他表同理,未在此处逐一展开,详见表列定义文件。
|
||||
|
||||
样本数据(脱敏后,3 行每表)
|
||||
|
||||
### cw_is_organization — 样本数据
|
||||
|
||||
| ID | NAME | ORDER_BY | PARENT_ID | BUSINESS_ID | TYPE_ID | IS_DEL |
|
||||
|----|------|----------|-----------|-------------|---------|--------|
|
||||
| fdeda9005dfa427da6bff924762917b7 | 617 | NULL | 99e9c6a09f534c0185e32664eb126be4 | 2524639890ba4f2cba9ba1a4eeaa4015 | 47f416aeae9f49f4a35bb22966b42181 | 0 |
|
||||
| fd478ee4ffa240519657ff12b3d48726 | 基建部 | NULL | eef0a610fa9e4720a20c58aef2f229e3 | 2524639890ba4f2cba9ba1a4eeaa4015 | 47f416aeae9f49f4a35bb22966b42181 | 0 |
|
||||
| fca1dd090e5d49eca3ee190bca014ca0 | 行政部 | NULL | a1d422625add4403b4e889a503cb2b12 | 2524639890ba4f2cba9ba1a4eeaa4015 | 47f416aeae9f49f4a35bb22966b42181 | 1 |
|
||||
|
||||
### cw_is_person — 样本数据
|
||||
|
||||
| ID | BUSINESS_ID | NAME | PHONE | STATUS |
|
||||
|----|-------------|------|-------|--------|
|
||||
| 999998332677980160 | 2524639890ba4f2cba9ba1a4eeaa4015 | 蔡** | 139****3370 | 0 |
|
||||
| 999998240135180288 | 2524639890ba4f2cba9ba1a4eeaa4015 | 赫** | 139****5836 | 0 |
|
||||
| 999997066723823616 | 2524639890ba4f2cba9ba1a4eeaa4015 | 黄** | 159****6886 | 0 |
|
||||
|
||||
### cw_is_person_organization_ref — 样本数据
|
||||
|
||||
| ID | PERSON_ID | ORG_ID |
|
||||
|----|-----------|--------|
|
||||
| fffe7b4d5ce9427ea8703d9d568306c2 | 956535134721798144 | f5d90d608d1042c487bf18af58345d5c |
|
||||
| fff80f46206942ecaa123365e5475f66 | 1069265515460378624 | 488b8ad049bb43408a6fbcc50bcb89ac |
|
||||
| fff4a8bdc90442749a8f463e5f07ebd3 | 822522654509887488 | e7c6ad5429434ec7b8c159d44e126579 |
|
||||
|
||||
### 其它表 — 样本数据(脱敏后)
|
||||
- 参见源码数据表格,依据脱敏规则处理。若需扩展,请按同样模板添加。
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
3) 电梯应用库 cw-elevator-application
|
||||
|
||||
ER 图(Mermaid erDiagram)
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
tenant_visitor_floor_policy {
|
||||
id PK
|
||||
business_id UK
|
||||
policy_type
|
||||
allow_zone_ids
|
||||
}
|
||||
image_rule_ref {
|
||||
id PK
|
||||
business_id
|
||||
zone_id
|
||||
zone_name
|
||||
person_id
|
||||
is_default
|
||||
}
|
||||
```
|
||||
|
||||
核心表清单(业务关键列)
|
||||
- tenant_visitor_floor_policy: id, business_id, policy_type, allow_zone_ids, enabled, policy_version
|
||||
- image_rule_ref: id, zone_id, zone_name, name, person_id, include_labels, include_organizations, is_default, business_id
|
||||
|
||||
样本数据(脱敏后,最多3行)
|
||||
|
||||
### tenant_visitor_floor_policy — 样本
|
||||
|
||||
| id | business_id | policy_type | allow_zone_ids |
|
||||
|----|--------------|-------------|----------------|
|
||||
| gf_vstr_policy_guangfa_fund_001x | 2524639890ba4f2cba9ba1a4eeaa4015 | INTERSECT_ALLOWLIST | ["605560545117995008"] (28F) |
|
||||
| _(当前库仅此一行策略)_ | | | |
|
||||
|
||||
### image_rule_ref — 样本
|
||||
|
||||
| id | zone_id | zone_name | person_id | is_default |
|
||||
|----|---------|-----------|-----------|------------|
|
||||
| 999998333961596928 | 605560541473144832 | 6F | 999998332677980160 | NULL |
|
||||
| 999998241204416512 | 605560542752407552 | 15F | 999998240135180288 | NULL |
|
||||
| 999997067843825664 | 605560543834537984 | 20F | 999997066723823616 | NULL |
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
4) 人脸识别库 ninca_crk_std
|
||||
⚠️ 未连接生产库,以下信息从代码推导
|
||||
|
||||
- 说明:该库不可连通,样本数据不提供。表与字段推导基于代码注释及 Mapper 命名推断,具体字段以生产环境为准。
|
||||
- 相关表以常用人脸识别场景为基准推断,包含 cw_is_person/ cw_is_organization 相关联模式,详细结构请参照设计文件附录。
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
5) 报警库 alarm_deploy
|
||||
⚠️ 未连接生产库,以下信息从代码推导
|
||||
|
||||
- 说明:该库不可连通,样本数据不提供。推导基于 Alarm 领域常规表命名与 Mapper 文件结构。
|
||||
- 具体表结构以代码为准,文档中仅给出 ER 零散草图及字段推导口径。
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
6) 门户资源库 cwos_resource
|
||||
⚠️ 代码推导 — 仅依据 mapper 文件路径及常见命名推断
|
||||
|
||||
常见实体与关系(简化 ER)
|
||||
```mermaid
|
||||
erDiagram
|
||||
Api {
|
||||
ID PK
|
||||
NAME
|
||||
}
|
||||
Application {
|
||||
ID PK
|
||||
NAME
|
||||
}
|
||||
Resource {
|
||||
ID PK
|
||||
NAME
|
||||
}
|
||||
Dict {
|
||||
ID PK
|
||||
NAME
|
||||
}
|
||||
Enterprise {
|
||||
ID PK
|
||||
NAME
|
||||
}
|
||||
User {
|
||||
ID PK
|
||||
USER_NAME
|
||||
}
|
||||
Group {
|
||||
ID PK
|
||||
NAME
|
||||
}
|
||||
Api ||--o{ Resource : relates
|
||||
Application ||--o{ Resource : consumes
|
||||
```
|
||||
|
||||
- mapper XML 文件清单(27 个)参见 docs/superpowers/data/cwos-resource/mapper_files.txt;对应的表名映射请以各 XML 内容为准。
|
||||
- 27 个 mapper 列表:ApiMapper.xml、AppApiMapper.xml、ApplicationApiMapper.xml、ApplicationMapper.xml、AppResMapper.xml、AuthApiMapper.xml、AuthorizationMapper.xml、AuthResourceMapper.xml、DictMapper.xml、DictTypeMapper.xml、EnterpriseMapper.xml、GroupInfoMapper.xml、GroupRoleMapper.xml、ResourceApiMapper.xml、ResourceMapper.xml、RoleApiMapper.xml、RoleAuthMapper.xml、RoleMapper.xml、RoleResourceMapper.xml、ServiceMapper.xml、UserAccountMapper.xml、UserApplicationMapper.xml、UserGroupMapper.xml、UserMapper.xml、UserResMapper.xml、UserRoleMapper.xml、以及 Oracle/SqlServer 对应分支等。
|
||||
|
||||
备注:cwos_resource 的 ER 及列定义以 mapper 内容推导为主,实际情况请以 Mapper XML 文件为准。
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
7) 跨库关系总图
|
||||
|
||||
跨库关系概要:cw_is_organization.BUSINESS_ID ↔ tenant_visitor_floor_policy.business_id;cw_is_person.ID ↔ image_rule_ref.person_id(通过 API 字段 personId);cw_is_person.ID → cw_is_person_organization_ref.PERSON_ID → cw_is_organization.ID;cw_is_person_label_ref 将 cw_is_person 与 cw_is_label 关联以支持访客标签。
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
cw_is_organization ||--o{ cw_is_organization : "parent_child"
|
||||
cw_is_person ||--o{ cw_is_person_organization_ref : "membership"
|
||||
cw_is_person_label_ref ||--o{ cw_is_label : "labels"
|
||||
tenant_visitor_floor_policy }|..|| image_rule_ref : "policy_link"
|
||||
%% 跨库对齐示意
|
||||
cw_is_organization.BUSINESS_ID }|..|| tenant_visitor_floor_policy.BUSINESS_ID : "跨库 BUSINESS_ID 对齐"
|
||||
cw_is_person.ID }|..|| image_rule_ref.PERSON_ID : "跨库 person 引用"
|
||||
```
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
8) 代码-表映射索引
|
||||
|
||||
- cw-elevator-application:如 AcsElevatorCodeMapper.xml、AcsDeviceTaskMapper.xml、AcsElevatorDeviceMapper.xml、DeviceImageStoreMapper.xml、TenantVisitorFloorPolicyMapper.xml、AcsElevatorRecordMapper.xml、AcsPassRuleMapper.xml、AcsRecogRecordMapper.xml、ImageRuleRefMapper.xml、SendRecordTimeMapper.xml 等,均映射到相应的 elevator/record/… 表。
|
||||
- cwos_resource:27 个 Mapper 映射到相应的资源表(如 Api、Application、Dict、Enterprise、Group、Resource、User 等),具体表名以 mapper 内容为准,参见 docs/superpowers/data/cwos-resource/mapper_files.txt。
|
||||
- 代码表映射是动态的,请以 Mapper XML 实际内容为准进行对照。
|
||||
|
||||
附注:该部分为索引性描述,方便对照查阅。
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
9) 附录 — 验证 SQL 模板
|
||||
|
||||
- 数据库概览查询
|
||||
```
|
||||
SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, TABLE_ROWS
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA IN ('component-organization','cw-elevator-application','ninca_crk_std','alarm_deploy','cwos_resource')
|
||||
ORDER BY TABLE_SCHEMA, TABLE_NAME;
|
||||
```
|
||||
|
||||
- 列字段清单(示例:component-organization 的核心表 cw_is_organization)
|
||||
```
|
||||
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'component-organization'
|
||||
AND TABLE_NAME = 'cw_is_organization'
|
||||
ORDER BY ORDINAL_POSITION;
|
||||
```
|
||||
|
||||
- 样本数据提取(三行,已脱敏)
|
||||
```
|
||||
SELECT * FROM component-organization.cw_is_organization ORDER BY ID DESC LIMIT 3;
|
||||
```
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
此文档为自动化聚合产物,最终实现以实际数据库结构、Mapper 内容及实际数据为准。
|
||||
@@ -0,0 +1,365 @@
|
||||
# 租户访客楼层策略 — org_id 粒度修复设计
|
||||
|
||||
**文档性质**:技术设计说明(方案 A:org_id 替换 business_id)
|
||||
**设计日期**:2026-05-01
|
||||
**状态**:待评审
|
||||
**关联分支**:(待创建)
|
||||
**关联 Spec**:`docs/superpowers/specs/2026-05-01-database-schema-reference-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 问题陈述
|
||||
|
||||
### 1.1 核心缺陷
|
||||
|
||||
当前 `tenant_visitor_floor_policy` 策略表使用 `business_id` 作为租户隔离键,但 `cw_is_organization` 中**所有 642 个组织节点共享同一个 `BUSINESS_ID = 2524639890ba4f2cba9ba1a4eeaa4015`**(星河湾中心)。
|
||||
|
||||
**后果:整个星河湾中心只能存在一条访客楼层策略**,无法为广发基金(仅允许 28F)和另一入驻公司(仅允许 15F)配置不同的策略。
|
||||
|
||||
### 1.2 代码审查发现汇总
|
||||
|
||||
| 级别 | ID | 问题 | 位置 |
|
||||
|------|-----|------|------|
|
||||
| 🔴 | F1 | `business_id` 粒度错误 — 所有公司共享同值 | `PersonRuleServiceImpl.java:200` |
|
||||
| 🔴 | F2 | UC-02 完全绕过策略,无安全兜底 | `PersonRuleServiceImpl.java:179-180` |
|
||||
| 🔴 | F3 | `PersonResult.getOrganizationIds()` 可用但未用于策略匹配 | `PersonRuleServiceImpl.java:194` |
|
||||
| 🟡 | W1 | `zoneService.page` 只查第一个楼层,后续盲写 | `PersonRuleServiceImpl.java:222-223` |
|
||||
| 🟡 | W2 | JSON 解析失败静默降级(warn → 应 error) | `PersonRuleServiceImpl.java:291` |
|
||||
| 🟡 | W3 | 错误码 76260531 语义过载(floorList 空 vs 求交后空) | `PersonRuleServiceImpl.java:196,218` |
|
||||
| 🟢 | O1 | 每次请求查库,无缓存 | `PersonRuleServiceImpl.java:200` |
|
||||
| 🟢 | O2 | `image_rule_ref.person_id` 访客/员工语义混淆 | `PersonRuleServiceImpl.java:235` |
|
||||
|
||||
### 1.3 方案选择
|
||||
|
||||
| 方案 | 描述 | 选择 |
|
||||
|------|------|------|
|
||||
| A | `org_id` 直替 `business_id` | ✅ **选中** |
|
||||
| B | 多级策略查找(org_id + business_id 双层) | ❌ 复杂度过高 |
|
||||
| C | 组织树向上查找 | ❌ 依赖额外 Feign 调用 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据模型变更
|
||||
|
||||
### 2.1 DDL
|
||||
|
||||
```sql
|
||||
-- 新增 org_id 列
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
ADD COLUMN org_id VARCHAR(32) NULL COMMENT '组织节点ID(cw_is_organization.ID)'
|
||||
AFTER business_id;
|
||||
|
||||
-- 替换唯一约束
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
DROP INDEX uk_biz_building,
|
||||
ADD UNIQUE KEY uk_org_building (org_id, building_id);
|
||||
|
||||
-- 保留 business_id 为只读参考列
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
MODIFY COLUMN business_id VARCHAR(64) NULL COMMENT 'DEPRECATED: 已废弃,以 org_id 为准';
|
||||
```
|
||||
|
||||
### 2.2 约束说明
|
||||
|
||||
- `UNIQUE(org_id, building_id)`:一个公司在同一楼栋只能有一条策略
|
||||
- `org_id = NULL` 的行不参与查询,仅作历史数据保留
|
||||
- `building_id = NULL` 表示租户级策略(当前唯一楼栋场景下均为 NULL)
|
||||
- MySQL 中多行 `(org_A, NULL)` 和 `(org_B, NULL)` 不冲突
|
||||
|
||||
### 2.3 实际数据验证
|
||||
|
||||
```
|
||||
tenant_visitor_floor_policy 当前: 1 行,building_id = NULL
|
||||
elevator_device 当前: 126 台设备,全部 building_id = 605560539791228928(星河湾中心)
|
||||
device_image_store 当前: 1 行,building_id = 605560539791228928
|
||||
→ 当前只有一个楼栋,building_id 字段尚未启用多楼栋场景
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码变更
|
||||
|
||||
### 3.1 Mapper SQL
|
||||
|
||||
**文件**:`TenantVisitorFloorPolicyMapper.xml`
|
||||
|
||||
```xml
|
||||
<select id="selectEnabledByOrgId" resultType="...TenantVisitorFloorPolicyDto">
|
||||
SELECT id, org_id AS orgId, policy_type AS policyType,
|
||||
allow_zone_ids AS allowZoneIds, building_id AS buildingId,
|
||||
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>
|
||||
```
|
||||
|
||||
### 3.2 DAO 接口
|
||||
|
||||
**文件**:`TenantVisitorFloorPolicyDao.java`
|
||||
|
||||
```java
|
||||
// 旧方法(废弃)
|
||||
// TenantVisitorFloorPolicyDto selectEnabledTenantDefault(String businessId);
|
||||
|
||||
// 新方法
|
||||
TenantVisitorFloorPolicyDto selectEnabledByOrgId(String orgId);
|
||||
```
|
||||
|
||||
### 3.3 DTO
|
||||
|
||||
**文件**:`TenantVisitorFloorPolicyDto.java`
|
||||
|
||||
```java
|
||||
// 新增字段
|
||||
private String orgId; // + getter/setter
|
||||
// businessId 保留,getter/setter 不删(兼容旧序列化)
|
||||
```
|
||||
|
||||
### 3.4 Service 核心逻辑
|
||||
|
||||
**文件**:`PersonRuleServiceImpl.java` — `addVisitor` 方法
|
||||
|
||||
关键改动:
|
||||
|
||||
```
|
||||
旧: policy = dao.selectEnabledTenantDefault(context.getCompany().getCompanyId());
|
||||
→ 所有公司返回同一条策略
|
||||
|
||||
新: orgIds = personResult.getOrganizationIds();
|
||||
遍历 orgIds 查策略,取第一个命中
|
||||
→ 不同公司返回各自策略
|
||||
```
|
||||
|
||||
完整控制流见 §4。
|
||||
|
||||
### 3.5 W2 修复 — JSON 解析日志升级
|
||||
|
||||
```java
|
||||
} catch (Exception e) {
|
||||
this.logger.error("allow_zone_ids JSON 无效,策略失效!orgId={} policyId={} raw={}",
|
||||
orgId, policy.getId(), json, e); // warn → error
|
||||
return Collections.emptyList();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 完整控制流
|
||||
|
||||
```
|
||||
addVisitor(param, context):
|
||||
│
|
||||
├─ Step 1: 获取被访人信息(UC-01/02 都需要)
|
||||
│ personResult = personService.detail(param.personId)
|
||||
│ hostFloors = personResult.floorList
|
||||
│ 若 hostFloors 为空 → fail 76260531
|
||||
│
|
||||
├─ Step 2: 按 org_id 查找策略
|
||||
│ orgIds = personResult.organizationIds
|
||||
│ 遍历 orgIds:
|
||||
│ policy = dao.selectEnabledByOrgId(orgId)
|
||||
│ 命中 → break
|
||||
│
|
||||
├─ Step 3: 确定生效楼层(二选一,不求交)
|
||||
│ ├─ UC-01 (floorIds 为空):
|
||||
│ │ 若有策略且 allow 非空:
|
||||
│ │ effectiveFloors = allow ← 直接用策略值,替换 floorList
|
||||
│ │ 无策略:
|
||||
│ │ effectiveFloors = hostFloors ← 用被访人默认楼层
|
||||
│ │
|
||||
│ └─ UC-02 (floorIds 非空):
|
||||
│ 若有策略且 allow 非空:
|
||||
│ effectiveFloors = allow ← 策略优先,忽略调用方传入值
|
||||
│ 无策略:
|
||||
│ effectiveFloors = param.floorIds ← 用调用方传入值
|
||||
│ 软校验: floorId 不在 hostFloors 中 → WARN 日志
|
||||
│
|
||||
├─ Step 4: 约束校验(allow ⊆ floorList)
|
||||
│ 若 effectiveFloors 中任何值不在 hostFloors 中 → ERROR 日志 + fail 76260533
|
||||
│ (策略配置了被访人无权访问的楼层 = 安全风险,拒绝)
|
||||
│
|
||||
└─ Step 5: 落库(不变)
|
||||
zoneService.page → image_rule_ref insert → batchBind → updateGroupPersonRef
|
||||
```
|
||||
|
||||
### 4.1 核心语义:二选一,不求交
|
||||
|
||||
| 条件 | 生效楼层 |
|
||||
|------|----------|
|
||||
| 策略存在且 allow 非空 | **`allow`**(直接替换,不做交集) |
|
||||
| 无策略 / enabled=0 / allow 为空 | **被访人 floorList**(UC-01)或 **调用方 floorIds**(UC-02) |
|
||||
|
||||
| 维度 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 策略生效逻辑 | `floorList ∩ allow`(求交) | `allow` 直接替换 `floorList` |
|
||||
| UC-02 是否查策略 | ❌ 不查 | ✅ 查(策略优先) |
|
||||
| allow 含无效楼层 | 交集自动排除 | ERROR 日志 + fail 76260533 |
|
||||
|
||||
### 4.2 辅助方法
|
||||
|
||||
```java
|
||||
/** 按 org_id 查找策略,遍历 organizationIds 取第一个命中 */
|
||||
private TenantVisitorFloorPolicyDto findPolicyByOrgIds(List<String> orgIds) {
|
||||
if (CollectionUtils.isEmpty(orgIds)) return null;
|
||||
for (String orgId : orgIds) {
|
||||
TenantVisitorFloorPolicyDto p = tenantVisitorFloorPolicyDao.selectEnabledByOrgId(orgId);
|
||||
if (p != null && p.getEnabled() != null && p.getEnabled() == 1) {
|
||||
List<String> allow = parseAllowZoneIds(p.getAllowZoneIds());
|
||||
if (!CollectionUtils.isEmpty(allow)) return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二选一:有策略用 allow,无策略用 fallbackFloors。
|
||||
* 约束:allow 必须是 hostFloors 的子集,否则拒绝。
|
||||
*/
|
||||
private List<String> resolveEffectiveFloors(
|
||||
List<String> fallbackFloors, List<String> hostFloors,
|
||||
TenantVisitorFloorPolicyDto policy, String personId) {
|
||||
|
||||
if (policy == null) return fallbackFloors;
|
||||
|
||||
List<String> allow = parseAllowZoneIds(policy.getAllowZoneIds());
|
||||
if (CollectionUtils.isEmpty(allow)) return fallbackFloors;
|
||||
|
||||
// 安全校验: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={} allow={} hostSize={}",
|
||||
policy.getOrgId(), policy.getId(), policy.getPolicyVersion(),
|
||||
allow.size(), hostFloors.size());
|
||||
return allow; // 直接用 allow,不求交
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 关键设计约束:allow ⊆ floorList(强制)
|
||||
|
||||
策略的 `allow_zone_ids` **必须是被访人 `floorList` 的子集**。因为生效逻辑是「有策略直接用 allow」,如果 allow 包含被访人无权访问的楼层 → 访客获得越权 → **直接拒绝**。
|
||||
|
||||
| 场景 | 处理 |
|
||||
|------|------|
|
||||
| `allow = [28F]`, `floorList = [28F, 29F]` | ✅ 正常:allow ⊆ floorList,生效 [28F] |
|
||||
| `allow = [28F, 99F]`, `floorList = [28F, 29F]` | ❌ 配置错误:99F 不在 floorList → ERROR + fail 76260533 |
|
||||
| `allow = [28F]`, `floorList = [29F, 30F]` | ❌ 配置错误:28F 不在 floorList → ERROR + fail 76260533 |
|
||||
|
||||
- 校验在 `resolveEffectiveFloors` 中执行
|
||||
- 不满足时 **`throw ServiceException("76260533")`**,请求失败
|
||||
- 新增错误码 **76260533**:「策略配置了被访人无权访问的楼层,请联系管理员」
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据迁移
|
||||
|
||||
### 5.1 执行顺序
|
||||
|
||||
```
|
||||
Step 1: DDL 上线(表结构变更,不影响行为)
|
||||
→ ALTER TABLE 加 org_id 列 + 新唯一约束
|
||||
|
||||
Step 2: 数据迁移(人工 SQL,按公司逐行执行)
|
||||
→ 查询 cw_is_organization 获取各公司 org_id
|
||||
→ UPDATE tenant_visitor_floor_policy SET org_id = ? WHERE ...
|
||||
|
||||
Step 3: 发应用包(代码切到 org_id 查询)
|
||||
→ Mapper SQL: business_id → org_id
|
||||
→ Service: 取 organizationIds 遍历查策略
|
||||
|
||||
Step 4(可选): 废弃 business_id 列
|
||||
→ MODIFY COLUMN ... COMMENT 'DEPRECATED'
|
||||
```
|
||||
|
||||
### 5.2 数据迁移 SQL 模板
|
||||
|
||||
```sql
|
||||
-- 1. 列出所有公司级组织节点
|
||||
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. 为指定公司设置策略
|
||||
UPDATE cw-elevator-application.tenant_visitor_floor_policy
|
||||
SET org_id = '<目标公司 org_id>'
|
||||
WHERE business_id = '2524639890ba4f2cba9ba1a4eeaa4015'
|
||||
AND org_id IS NULL;
|
||||
|
||||
-- 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);
|
||||
```
|
||||
|
||||
### 5.3 向后兼容
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| `org_id = NULL` 的行 | `WHERE org_id = ?` 不匹配,等同于无策略 |
|
||||
| 旧 `business_id` 策略未迁移 | 全量 floorList(与现网一致) |
|
||||
| 新代码 + 旧表结构 | SQL 报错 → 回滚应用包 |
|
||||
| 应用回滚 | 旧代码仍用 `business_id` 查询,行为不变 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试策略
|
||||
|
||||
| ID | 场景 | 期望结果 |
|
||||
|----|------|----------|
|
||||
| T1 | A公司有策略 allow=[28F],被访人 floorList=[28F,29F],UC-01 | effective=[28F](直接用 allow) |
|
||||
| T2 | A公司有策略 allow=[28F],被访人 floorList=[29F,30F],UC-01 | **fail 76260533**(28F 不在 floorList 中) |
|
||||
| T3 | B公司无策略,被访人 floorList=[15F,16F],UC-01 | effective=[15F,16F](用 floorList) |
|
||||
| T4 | 被访人属 [A公司, C公司],A有策略 allow=[28F] | 命中A策略→effective=[28F] |
|
||||
| T5 | 被访人属 [B公司, D公司],均无策略 | effective=floorList 全集 |
|
||||
| T6 | UC-02 传 [15F],B公司无策略 | effective=[15F](用调用方值) |
|
||||
| T7 | UC-02 传 [15F],A公司有策略 allow=[28F] | effective=[28F](策略优先,忽略调用方) |
|
||||
| T8 | 策略 enabled=0 | 等同无策略→用 floorList |
|
||||
| T9 | `allow_zone_ids` 为非法 JSON `["28F"` | ERROR 日志 + 等同无策略 |
|
||||
| T10 | 策略 allow=[28F,99F],floorList=[28F,29F] | **fail 76260533**(99F 不在 floorList) |
|
||||
| T11 | 回滚:新 DDL + 旧代码 | 行为不变 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险与缓解
|
||||
|
||||
| 风险 | 概率 | 缓解 |
|
||||
|------|------|------|
|
||||
| `organizationIds` 为空(被访人无组织归属) | 低 | 降级为无策略→全量 floorList |
|
||||
| DBA 迁移时填错 org_id | 中 | 迁移前 `SELECT` 确认 NAME 匹配,迁移后抽样验证 |
|
||||
| 多公司共用一个 org_id(父子组织) | 低 | 当前需求是公司级粒度;未来如需子树继承可升级到方案C |
|
||||
| UC-02 调用方依赖旧行为(不查策略) | 中 | 发版说明明确标注行为变更;灰度发布 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 文件清单
|
||||
|
||||
| 文件 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| `docs/sql/tenant_visitor_floor_policy_v2.sql` | 新增 | DDL 变更脚本 |
|
||||
| `TenantVisitorFloorPolicyMapper.xml` | 修改 | `business_id` → `org_id` |
|
||||
| `TenantVisitorFloorPolicyMapper.java` | 修改 | 方法签名 |
|
||||
| `TenantVisitorFloorPolicyDao.java` | 修改 | 接口方法 |
|
||||
| `TenantVisitorFloorPolicyDaoImpl.java` | 修改 | 调用新 Mapper 方法 |
|
||||
| `TenantVisitorFloorPolicyDto.java` | 修改 | 新增 `orgId` 字段 |
|
||||
| `PersonRuleServiceImpl.java` | 修改 | `addVisitor` + 新增辅助方法 |
|
||||
|
||||
---
|
||||
|
||||
*本文档为 org_id 粒度修复的技术设计;实施以代码评审与安全评审结论为准。*
|
||||
@@ -0,0 +1,356 @@
|
||||
# org_id 策略修复 — 无鉴权验证方案设计
|
||||
|
||||
**文档性质**:验证方案设计说明
|
||||
**设计日期**:2026-05-01
|
||||
**状态**:待评审
|
||||
**关联 Spec**:`docs/superpowers/specs/2026-05-01-org-id-policy-fix-design.md`
|
||||
**被测应用**:`cw-elevator-application`(V2, port 18081)
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标
|
||||
|
||||
通过无鉴权 HTTP 调用 `POST /elevator/person/add/visitor` 和 `POST /elevator/passRule/image`,验证 org_id 策略修复的 7 个核心场景,确保业务逻辑满足需求。
|
||||
|
||||
**不验证**:鉴权正确性、Feign 调用链、图库绑定、Consul 服务发现。
|
||||
|
||||
---
|
||||
|
||||
## 2. 完整业务流程与接口调用链
|
||||
|
||||
### 2.1 业务全景
|
||||
|
||||
```
|
||||
Step 0: 访客邀约(上游,非电梯侧)
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ 第三方业务系统(BFF / intelligent / 登记页) │
|
||||
│ ├── 创建访客人员档案(cw_is_person + 访客标签) │
|
||||
│ └── 确定被访人(host personId) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
→ 访客已存在于组织库中,personId 可用
|
||||
→ 本方案使用预置测试访客(919900... 号段),跳过此步
|
||||
|
||||
Step 1: 设置访客可访问楼层(电梯侧,核心验证目标)
|
||||
POST /elevator/person/add/visitor
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ ① Feign → /component/person/detail │
|
||||
│ 取被访人 floorList + organizationIds │
|
||||
│ │
|
||||
│ ② 查 tenant_visitor_floor_policy │
|
||||
│ WHERE org_id = ? (从 organizationIds 取) │
|
||||
│ │
|
||||
│ ③ 二选一: │
|
||||
│ 有策略 → effectiveFloors = allow(直接替换) │
|
||||
│ 无策略 → effectiveFloors = floorList │
|
||||
│ allow 含无效值 → fail 76260533 │
|
||||
│ │
|
||||
│ ④ Feign → /sysetting/zone/page │
|
||||
│ 取楼栋 + 图库 imageStoreId │
|
||||
│ │
|
||||
│ ⑤ 写 image_rule_ref(访客 personId ← visitorId) │
|
||||
│ │
|
||||
│ ⑥ Feign → /component/imagestore/person/batchBind │
|
||||
│ 访客绑定到楼栋图库 │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
|
||||
Step 2: 回读验证(确认楼层写入正确)
|
||||
POST /elevator/passRule/image
|
||||
Body: { "personId": "<visitorId>" }
|
||||
→ 返回该访客可访问的 zoneId 列表
|
||||
→ 与期望楼层比对
|
||||
```
|
||||
|
||||
### 2.2 接口调用细节
|
||||
|
||||
#### POST /elevator/person/add/visitor
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| Method | POST |
|
||||
| Path | `/elevator/person/add/visitor` |
|
||||
| Content-Type | `application/json` |
|
||||
| 鉴权模式 | noauth-probe(仅 `businessid` 头) |
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"personId": "1060601019894960128",
|
||||
"visitorId": "9199000100000000001",
|
||||
"floorIds": [],
|
||||
"begVisitorTime": 1751319780000,
|
||||
"endVisitorTime": 1751406180000
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `personId` | 是 | 被访人 ID(决定 floorList 和 org_id 来源) |
|
||||
| `visitorId` | 是 | 访客 ID(落库 image_rule_ref 的 person_id) |
|
||||
| `floorIds` | 否 | **空数组**=UC-01 由电梯补全;**非空**=UC-02 调用方指定 |
|
||||
| `begVisitorTime` | 是 | Unix 毫秒,访客有效期开始 |
|
||||
| `endVisitorTime` | 是 | Unix 毫秒,访客有效期结束 |
|
||||
|
||||
**响应体(成功):**
|
||||
|
||||
```json
|
||||
{ "success": true, "code": "0", "data": true }
|
||||
```
|
||||
|
||||
**响应体(策略拒绝):**
|
||||
|
||||
```json
|
||||
{ "success": false, "code": "76260533", "message": "策略配置了被访人无权访问的楼层,请联系管理员" }
|
||||
```
|
||||
|
||||
#### POST /elevator/passRule/image(回读)
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| Method | POST |
|
||||
| Path | `/elevator/passRule/image` |
|
||||
| Body | `{ "personId": "<visitorId>" }` |
|
||||
|
||||
**响应体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"datas": [
|
||||
{ "zoneId": "605560545117995008", "zoneName": "28F" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 策略在流程中的位置
|
||||
|
||||
```
|
||||
add/visitor 请求进入
|
||||
│
|
||||
├─ personId → Feign detail() → { floorList, organizationIds }
|
||||
│
|
||||
├─ organizationIds[0] → SELECT ... FROM tenant_visitor_floor_policy
|
||||
│ WHERE org_id = ? ← 新:org_id 粒度
|
||||
│ AND enabled = 1
|
||||
│
|
||||
├─ 有策略行 → effectiveFloors = allow ← 二选一,不求交
|
||||
│ allow ⊆ floorList 校验
|
||||
│ └─ 不通过 → 76260533
|
||||
│
|
||||
└─ 无策略行 → effectiveFloors = floorList ← 默认行为
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试环境
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 电梯服务 | `http://127.0.0.1:18081` |
|
||||
| MySQL 组织库 | `192.168.3.12:3307 / component-organization` |
|
||||
| MySQL 电梯库 | `192.168.3.12:3307 / cw-elevator-application` |
|
||||
| 租户 | `businessId = 2524639890ba4f2cba9ba1a4eeaa4015` |
|
||||
| 调用方式 | noauth-probe(仅 businessid 头,无鉴权 token) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 测试矩阵(7 个用例)
|
||||
|
||||
### 4.1 测试数据
|
||||
|
||||
| 实体 | ID |
|
||||
|------|-----|
|
||||
| 1403艾斯 org | `72fb65ec5de94201b909a98b8bae1892` |
|
||||
| 1405一博环保 org | `2095de3d541f44eba686c78fda68336f` |
|
||||
| 星中心物业 org | `f216235e54ca42bfa0379e69b3754aff` |
|
||||
| 广发基金 org | `488b8ad049bb43408a6fbcc50bcb89ac` |
|
||||
| 陈国辉(1403+星中心) | `1060601019894960128` |
|
||||
| 王姣(1405) | `1090779433129840640` |
|
||||
| 秦夏(广发基金) | `1072908835884208128` |
|
||||
| 28F zone | `605560545117995008` |
|
||||
| 6F zone | `605560541473144832` |
|
||||
|
||||
### 4.1.1 访客数据
|
||||
|
||||
测试使用开发库中已预置的**专用测试访客**(号段 `9199000100000000001`~`9199000100000000020`,均已标注"访客"标签 `LABEL_ID = ed2dbab6d6234a7287106b80857c819e`)。这些是测试专用人员,非真实业务访客。
|
||||
|
||||
`add/visitor` 写入 `image_rule_ref` 时以 `visitorId` 为键,为避免同一访客的多次写入相互干扰,每个用例轮换使用不同测试访客。
|
||||
|
||||
| 用例 | visitorId | 访客标识 |
|
||||
|------|-----------|----------|
|
||||
| T1 | `9199000100000000001` | 测试访客AUTO-01 |
|
||||
| T2 | `9199000100000000002` | 测试访客AUTO-02 |
|
||||
| T3 | `9199000100000000003` | 测试访客AUTO-03 |
|
||||
| T4 | `9199000100000000004` | 测试访客AUTO-04 |
|
||||
| T5 | `9199000100000000005` | 测试访客AUTO-05 |
|
||||
| T6 | `9199000100000000006` | 测试访客AUTO-06 |
|
||||
| T7 | `9199000100000000007` | 测试访客AUTO-07 |
|
||||
|
||||
### 4.2 用例
|
||||
|
||||
| ID | 场景 | 被访人 | 策略 org | allow | enabled | 期望 |
|
||||
|----|------|--------|----------|-------|---------|------|
|
||||
| T1 | 有策略→allow 替换 floorList | 陈国辉 | 1403 | [28F] | 1 | passRule 返回仅 [28F] |
|
||||
| T2 | 无策略→floorList | 王姣 | 1405 | 无 | — | passRule 返回 floorList 全集 |
|
||||
| T3 | allow 含无效zone→拒绝 | 陈国辉 | 1403 | [28F, 99F] | 1 | code=76260533 |
|
||||
| T4 | 多组织命中第一个策略 | 陈国辉 | 1403+星中心 | 1403有,星中心无 | 1 | 命中1403→[28F] |
|
||||
| T5 | enabled=0 等同无策略 | 陈国辉 | 1403 | [28F] | 0 | passRule 返回 floorList 全集 |
|
||||
| T6 | UC-02 策略优先 | 陈国辉 | 1403 | [28F] | 1 | 传floorIds被忽略→[28F] |
|
||||
| T7 | 广发基金迁移验证 | 秦夏 | 广发基金 | [28F] | 1 | 迁移后→仅 [28F] |
|
||||
|
||||
---
|
||||
|
||||
## 5. 脚本结构
|
||||
|
||||
**文件**:`maven-cw-elevator-application/tools/visitor_floor_verification/scripts/verify_org_policy_fix.py`
|
||||
|
||||
```
|
||||
verify_org_policy_fix.py
|
||||
│
|
||||
├── Phase 0: health_check()
|
||||
│ GET /actuator/health
|
||||
│
|
||||
├── Phase 1: prepare_test_data()
|
||||
│ ① INSERT policy_t1_1403 (org=1403, allow=[28F], enabled=1)
|
||||
│ ② INSERT policy_t3_invalid (org=1403, allow=[28F,99F], enabled=1)
|
||||
│ ③ INSERT policy_t5_disabled (org=1403, allow=[28F], enabled=0)
|
||||
│ ④ UPDATE 广发基金 SET org_id='488b8adb...' WHERE id='gf_vstr_...'
|
||||
│
|
||||
├── Phase 2: run_all_cases()
|
||||
│ 每个用例:
|
||||
│ POST add/visitor → 检查 HTTP 200 + code + success
|
||||
│ 成功 → POST passRule/image → 比对 zoneIds
|
||||
│ 失败 → 检查 code == 期望错误码
|
||||
│
|
||||
├── Phase 3: cleanup_test_data()
|
||||
│ ① DELETE 测试策略行
|
||||
│ ② UPDATE 广发基金 SET org_id=NULL
|
||||
│
|
||||
└── Phase 4: print_report()
|
||||
输出 JSON 报告到 report/org-policy-fix-verify-{timestamp}.json
|
||||
```
|
||||
|
||||
### 5.1 HTTP 请求格式(noauth-probe)
|
||||
|
||||
```python
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"businessid": "2524639890ba4f2cba9ba1a4eeaa4015"
|
||||
}
|
||||
|
||||
# add/visitor
|
||||
POST /elevator/person/add/visitor
|
||||
{
|
||||
"personId": "1060601019894960128",
|
||||
"visitorId": "<轮换访客ID>",
|
||||
"floorIds": [],
|
||||
"begVisitorTime": <now_ms>,
|
||||
"endVisitorTime": <now_ms + 86400000>
|
||||
}
|
||||
|
||||
# passRule/image(回读)
|
||||
POST /elevator/passRule/image
|
||||
{
|
||||
"personId": "<visitorId>"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据准备与清理
|
||||
|
||||
### 6.1 准备 SQL
|
||||
|
||||
```sql
|
||||
-- T1/T4/T6 共用
|
||||
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', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST',
|
||||
'["605560545117995008"]', NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
|
||||
|
||||
-- T3
|
||||
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_t3_invalid', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST',
|
||||
'["605560545117995008","605560540000000000"]', NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
|
||||
|
||||
-- T5
|
||||
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_t5_disabled', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST',
|
||||
'["605560545117995008"]', NULL, 0, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
|
||||
|
||||
-- T7: 广发基金迁移
|
||||
UPDATE tenant_visitor_floor_policy
|
||||
SET org_id = '488b8ad049bb43408a6fbcc50bcb89ac'
|
||||
WHERE id = 'gf_vstr_policy_guangfa_fund_001x';
|
||||
```
|
||||
|
||||
### 6.2 清理 SQL
|
||||
|
||||
```sql
|
||||
DELETE FROM tenant_visitor_floor_policy WHERE id IN ('policy_t1_1403','policy_t3_invalid','policy_t5_disabled');
|
||||
UPDATE tenant_visitor_floor_policy SET org_id = NULL WHERE id = 'gf_vstr_policy_guangfa_fund_001x';
|
||||
```
|
||||
|
||||
### 6.3 执行顺序
|
||||
|
||||
```text
|
||||
prepare:
|
||||
INSERT policy_t1_1403 (T1/T4/T6 使用)
|
||||
INSERT policy_t3_invalid (T3 使用)
|
||||
INSERT policy_t5_disabled (T5 使用)
|
||||
UPDATE 广发基金 SET org_id (T7 使用)
|
||||
|
||||
execute:
|
||||
T1: 陈国辉 + policy_t1_1403 → allow=[28F]
|
||||
T3: 陈国辉 + policy_t3_invalid → 76260533(需先改1403的org_id或先DELETE T1再切换)
|
||||
T4: 陈国辉(多组织) + policy_t1 → allow=[28F]
|
||||
T5: 陈国辉 + policy_t5_disabled → floorList全集
|
||||
T2: 王姣(无策略) → floorList全集
|
||||
T6: 陈国辉(UC-02) + policy_t1 → allow=[28F]
|
||||
T7: 秦夏 + 广发基金策略 → allow=[28F]
|
||||
|
||||
cleanup:
|
||||
DELETE 测试行
|
||||
UPDATE 广发基金 SET org_id=NULL
|
||||
```
|
||||
|
||||
> T3 执行前需确保 policy_t1_1403 不生效(相同 org_id 只能有一条启用策略)。方案:T3 前 DELETE policy_t1_1403,T3 后恢复 INSERT。
|
||||
|
||||
---
|
||||
|
||||
## 7. 验证判定规则
|
||||
|
||||
| 判定项 | 通过条件 |
|
||||
|--------|----------|
|
||||
| HTTP 状态 | `add/visitor` 返回 200 |
|
||||
| 业务成功 | `response.success == true` |
|
||||
| 业务失败 | `response.code == "76260533"` |
|
||||
| 楼层匹配 | `passRule/image` 返回的 zoneId 集合 == 期望集合(顺序无关) |
|
||||
| 脱敏 | 报告中不出现 PII(姓名/手机号) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 输出
|
||||
|
||||
```
|
||||
report/org-policy-fix-verify-{timestamp}.json
|
||||
```
|
||||
|
||||
包含:每个用例的请求/响应摘要、passRule 结果、期望 vs 实际对比、通过/失败汇总。
|
||||
|
||||
---
|
||||
|
||||
## 9. 文件清单
|
||||
|
||||
| 文件 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| `tools/visitor_floor_verification/scripts/verify_org_policy_fix.py` | 新增 | 主验证脚本 |
|
||||
| `tools/visitor_floor_verification/report/` | — | 报告输出目录(已有) |
|
||||
|
||||
---
|
||||
|
||||
*本设计说明待评审,通过后转入 implementation。*
|
||||
@@ -0,0 +1,113 @@
|
||||
# org_id 策略修复 — 人工验证操作手册
|
||||
|
||||
## 前置条件
|
||||
|
||||
- V2 JAR 已构建:`deploy/v2-maven/cw-elevator-application-V1.0.0.20211103.jar`(v2.0.17)
|
||||
- 配置文件:`/tmp/v2-redis-fix.properties`
|
||||
- Redis Docker:`v2-test-redis`(端口 6380,密码 `1qaz!QAZ`)
|
||||
- 桩服务脚本:`stub_org_service.py`
|
||||
|
||||
---
|
||||
|
||||
## 步骤 1:启动 V2 电梯应用
|
||||
|
||||
打开**终端 1**,执行:
|
||||
|
||||
```bash
|
||||
/usr/lib/jvm/java-8-openjdk-amd64/bin/java \
|
||||
-jar /media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/源码/maven-cw-elevator-application/deploy/v2-maven/cw-elevator-application-V1.0.0.20211103.jar \
|
||||
--spring.config.location=file:/tmp/v2-redis-fix.properties
|
||||
```
|
||||
|
||||
等待约 **35 秒**,看到 `Started AppApplication` 后验证:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:18081/health
|
||||
```
|
||||
|
||||
期望输出:`{"status":"UP"}`
|
||||
|
||||
---
|
||||
|
||||
## 步骤 2:启动组织服务桩
|
||||
|
||||
打开**终端 2**,执行:
|
||||
|
||||
```bash
|
||||
python3 /media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/源码/maven-cw-elevator-application/tools/stub_org_service.py
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:18082/health
|
||||
```
|
||||
|
||||
期望输出:`{"status":"UP"}`
|
||||
|
||||
---
|
||||
|
||||
## 步骤 3:运行验证脚本
|
||||
|
||||
打开**终端 3**,执行:
|
||||
|
||||
```bash
|
||||
cd /media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/源码/maven-cw-elevator-application/tools/visitor_floor_verification
|
||||
python3 scripts/verify_org_policy_fix.py --elevator-base-url http://127.0.0.1:18081
|
||||
```
|
||||
|
||||
期望输出:
|
||||
|
||||
```
|
||||
=== Phase 2: run 7 cases ===
|
||||
[T1] 有策略→allow替换floorList → ✅
|
||||
[T2] 无策略→floorList → ✅
|
||||
[T3] allow含无效zone→拒绝 (76260533) → ✅
|
||||
[T4] 多组织命中第一个策略 → ✅
|
||||
[T5] enabled=0等同无策略 → ✅
|
||||
[T6] UC-02策略优先 → ✅
|
||||
[T7] 广发基金迁移验证 → ✅
|
||||
|
||||
Passed: 7/7
|
||||
```
|
||||
|
||||
报告文件:`report/org-policy-fix-verify-YYYYMMDD-HHMMSS.json`
|
||||
|
||||
---
|
||||
|
||||
## 步骤 4:停止服务
|
||||
|
||||
```bash
|
||||
# 终端1 按 Ctrl+C
|
||||
# 终端2 按 Ctrl+C
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 症状 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| V2 启动报 `RedisConnectionException` | Redis 密码未配置或端口错误 | 确认 `v2-test-redis` 运行中:`docker ps \| grep v2-test-redis` |
|
||||
| V2 报 `UnknownHostException: mysql_01` | ShardingSphere 数据源未覆盖 | 确认使用了 `--spring.config.location=file:/tmp/v2-redis-fix.properties` |
|
||||
| 桩服务端口被占用 | 上次未正常退出 | `pkill -f stub_org_service` |
|
||||
| 验证脚本 `Connection refused` | V2 或桩未启动 | 检查终端 1/2 的服务日志 |
|
||||
| DB 连接失败 | MySQL 密码错误 | 确认 `192.168.3.12:3307 root/123456` 可达 |
|
||||
|
||||
---
|
||||
|
||||
## 本地 Docker 基础设施
|
||||
|
||||
| 服务 | 容器名 | 端口 | 说明 |
|
||||
|------|--------|------|------|
|
||||
| Redis | `v2-test-redis` | 6380 | 密码 `1qaz!QAZ` |
|
||||
| Kafka | `ybs-kafka` | 9092 | 无认证 |
|
||||
| Consul | — | 192.168.3.12:8500 | 远程 |
|
||||
| MySQL | — | 192.168.3.12:3307 | root/123456 |
|
||||
|
||||
如需重建 Redis:
|
||||
|
||||
```bash
|
||||
docker rm -f v2-test-redis
|
||||
docker run -d --name v2-test-redis -p 6380:6379 redis:7-alpine --requirepass "1qaz!QAZ"
|
||||
```
|
||||
@@ -0,0 +1,137 @@
|
||||
# 电梯应用 — 服务发现架构设计
|
||||
|
||||
**日期**:2026-05-01(初稿)/ 2026-05-05(基线更新)
|
||||
**版本**:v2.0.17(当前基线)
|
||||
**状态**:已实施
|
||||
|
||||
> **说明**:本文档最初记录 Dubbo/ZooKeeper 发现方案(目标架构),但在实践中发现该方案与 V1 生产实际不符。下方 §1~§5 保留为历史方案记录,§6 起为 **实际实施态**。当前基线 v2.0.17 以 §6 为准。
|
||||
|
||||
---
|
||||
|
||||
## 实际实施态(v2.0.17)
|
||||
|
||||
### §6. 实际服务发现架构
|
||||
|
||||
电梯应用(cw-elevator-application)的三个上游 Feign 客户端通过 **Ribbon + ConfigurationBasedServerList + 静态 IP 列表** 实现服务寻址,与 V1 生产行为一致。
|
||||
|
||||
| 上游服务 | Feign 名称 | 实际发现方式 |
|
||||
|---------|-----------|------------|
|
||||
| 人脸识别 GPU | `ninca-crk-std` | **ConfigurationBasedServerList**(V1 同) |
|
||||
| 组织组件 | `ninca-common-component-organization` | **ConfigurationBasedServerList**(V1 同) |
|
||||
| 公共组件 | `ninca-common` | **ConfigurationBasedServerList**(V1 同) |
|
||||
|
||||
### §7. 实际发现链路
|
||||
|
||||
```
|
||||
业务代码 → @FeignClient(name = "${feign.xxx.name:...}")
|
||||
│
|
||||
▼
|
||||
Feign → Ribbon LoadBalancer
|
||||
│
|
||||
▼
|
||||
ConfigurationBasedServerList ← discovery.enabled=false 时 Ribbon 默认
|
||||
│ (无 Consul/ConsulServerList 参与)
|
||||
▼
|
||||
{name}.ribbon.listOfServers ← 从 application.properties 读取
|
||||
│
|
||||
▼
|
||||
目标 HTTP 服务 (IP:Port)
|
||||
```
|
||||
|
||||
**关键配置**:
|
||||
|
||||
```properties
|
||||
# bootstrap.properties — 与 V1 生产完全一致
|
||||
spring.cloud.consul.discovery.enabled=false # 不启用 Consul 发现客户端
|
||||
spring.cloud.consul.discovery.register=true # 仅注册自身到 Consul
|
||||
```
|
||||
|
||||
```properties
|
||||
# application.properties — 服务名映射(运行时需按环境配置)
|
||||
feign.ninca-crk-std.name=ninca-crk-std
|
||||
feign.component-organization.name=ninca-common-component-organization
|
||||
feign.ninca-common.name=ninca-common
|
||||
# 静态 IP 列表由环境中 application.properties 按需配置:
|
||||
# ninca-crk-std.ribbon.listOfServers=10.0.22.102:16106
|
||||
# ninca-common-component-organization.ribbon.listOfServers=...
|
||||
```
|
||||
|
||||
### §8. V1 vs V2 对比(实施态)
|
||||
|
||||
| | V1 生产 | V2 v2.0.17 |
|
||||
|------|---------|-----------|
|
||||
| ninca-crk-std | ConfigurationBasedServerList | ConfigurationBasedServerList ✅ |
|
||||
| ninca-common-component-organization | ConfigurationBasedServerList | ConfigurationBasedServerList ✅ |
|
||||
| ninca-common | ConfigurationBasedServerList | ConfigurationBasedServerList ✅ |
|
||||
| @RibbonClients | 无 | 无 ✅ |
|
||||
| RibbonConfiguration 类 | 无 | 无 ✅ |
|
||||
| bootstrap.properties | 见星中心 | **完全一致** ✅ |
|
||||
| 主类名 | AppApplication | AppApplication ✅ |
|
||||
| JAR 文件名 | cw-elevator-application-V1.0.0.20211103.jar | **一致** ✅ |
|
||||
|
||||
### §9. 关键设计决策
|
||||
|
||||
| 决策 | 选择 | 原因 |
|
||||
|------|------|------|
|
||||
| Consul 发现 | `discovery.enabled=false` | 与 V1 生产一致;只注册不发现 |
|
||||
| 服务寻址 | ConfigurationBasedServerList | Ribbon 默认行为,无需额外配置 |
|
||||
| ZK 发现依赖 | **已从 POM 移除** | V1 没有该依赖,V2 不需要引入 |
|
||||
| 静态 IP 配置 | 通过 `{name}.ribbon.listOfServers` 注入 | 与 V1 生产实际部署方式一致 |
|
||||
|
||||
### §10. 运维说明
|
||||
|
||||
- **每台服务器需配置本机的 `listOfServers`**(三台服务器各自指向本机或对端实例)。
|
||||
- 若使用 Consul 做健康检查,`discovery.enabled=false` 不影响 Consul 注册(`register=true`)。
|
||||
- 测试环境可使用 `application-test.properties` 覆盖 `listOfServers` 指向本地桩服务。
|
||||
|
||||
---
|
||||
|
||||
## 历史方案记录(目标架构,未实施)
|
||||
|
||||
以下 §1~§5 为 2026-05-01 设计的目标架构,**实际 v2.0.17 未采用此方案**。保留以记录设计历史。
|
||||
|
||||
### §1. 原计划核心决策
|
||||
|
||||
电梯应用的三个上游 Feign 客户端 **统一走 Dubbo/ZooKeeper 动态发现**,不依赖任何 `@RibbonClient` 定制或 `ConfigurationBasedServerList` 静态列表。
|
||||
|
||||
| 上游服务 | Feign 名称 | 原计划发现方式 |
|
||||
|---------|-----------|--------------|
|
||||
| 人脸识别 GPU | `ninca-crk-std` | Dubbo/ZooKeeper(❌ 未实施) |
|
||||
| 组织组件 | `ninca-common-component-organization` | Dubbo/ZooKeeper(❌ 未实施) |
|
||||
| 公共组件 | `ninca-common` | Dubbo/ZooKeeper(❌ 未实施) |
|
||||
|
||||
### §2. 原计划服务发现链路(未实施)
|
||||
|
||||
```
|
||||
ElevatorApplication
|
||||
│ @EnableFeignClients(basePackages={...})
|
||||
│
|
||||
├── Feign Client ──→ Ribbon (默认) ──→ DiscoveryClient ──→ Dubbo Registry
|
||||
│ zookeeper://10.0.22.207:2181
|
||||
```
|
||||
|
||||
### §3. 原计划配置清单(未实施)
|
||||
|
||||
```properties
|
||||
# bootstrap.properties — 原计划使用 ZK 发现
|
||||
spring.cloud.consul.discovery.enabled=false
|
||||
dubbo.registry.address=zookeeper://10.0.22.207:2181
|
||||
# 实际 v2.0.17 已移除 ZK 发现依赖,此配置不生效
|
||||
```
|
||||
|
||||
### §4. 原计划 V1 vs V2 对比(未实施)
|
||||
|
||||
| | V1 生产 | V2(原计划) |
|
||||
|------|---------|------------|
|
||||
| ninca-crk-std | ConfigurationBasedServerList | Dubbo/ZK(❌ 未实施) |
|
||||
| ninca-common-component-organization | ConfigurationBasedServerList | Dubbo/ZK(❌ 未实施) |
|
||||
| ninca-common | ConfigurationBasedServerList | Dubbo/ZK(❌ 未实施) |
|
||||
|
||||
### §5. 原计划删除清单(已执行)
|
||||
|
||||
| 文件 | 操作 | 状态 |
|
||||
|------|------|------|
|
||||
| `NincaCrkStdRibbonConfiguration.java` | 删除 | ✅ 已删除 |
|
||||
| `OrgServiceRibbonConfiguration.java` | 从未存在 | — |
|
||||
| `CommonServiceRibbonConfiguration.java` | 从未存在 | — |
|
||||
| `ElevatorApplication.java` 中 `@RibbonClients` | 删除 | ✅ 已清理 |
|
||||
@@ -0,0 +1,280 @@
|
||||
# V2 全系统功能测试环境搭建 — 设计文档
|
||||
|
||||
**日期**: 2026-05-01
|
||||
**状态**: 待实施
|
||||
**关联**: AGENTS.md, maven-cw-elevator-application/deploy/
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标
|
||||
|
||||
搭建 cw-elevator-application V2 (v2.0.7) 的全系统集成功能测试环境,包含电梯应用及其所有上下游依赖服务(CRK人脸识别、报警、cwos-manager、ninca-common、component-organization等),能够:
|
||||
|
||||
- 一键搭建全部 17 个组件
|
||||
- 运行 V1 vs V2 API 对拍测试
|
||||
- 验证 V2.0.7 租户访客固定楼层功能
|
||||
- 验证 CRK 联动、报警联动等端到端链路
|
||||
- 15-20 分钟内完成从零到全部验证
|
||||
|
||||
---
|
||||
|
||||
## 2. 环境约束
|
||||
|
||||
| 约束 | 值 |
|
||||
|------|-----|
|
||||
| 部署方式 | 本机进程 (Java服务) + Docker 容器 (基础组件) |
|
||||
| MySQL | 复用现有: `192.168.3.12:3307`, `root/123456` |
|
||||
| JDK | Java 8 (`/usr/lib/jvm/java-8-openjdk-amd64`) |
|
||||
| Maven | 3.5+ |
|
||||
| Docker | 已安装, `docker compose` 可用 |
|
||||
| 代码分支 | `release/cw-elevator-v1-lib-min-risk` |
|
||||
| 测试范围 | 全系统集成 — 全部 17 个组件 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 组件清单
|
||||
|
||||
### 3.1 基础组件 (Docker)
|
||||
|
||||
| # | 组件 | 端口 | 来源 |
|
||||
|---|------|------|------|
|
||||
| I1 | Consul | 8500 | 已有 `deploy/consul-docker/docker-compose.yml` |
|
||||
| I2 | Redis | 6379 | 需要新增 compose 条目 (`redis:7-alpine`) |
|
||||
| I3 | Kafka + Zookeeper | 9092, 2181 | 需要新增 compose 条目 (`bitnami/kafka:3.6`) |
|
||||
| I4 | Nginx (前端代理) | 8090 | 已有 `docker-compose.frontend-local.yml` |
|
||||
|
||||
### 3.2 应用服务 (本机进程)
|
||||
|
||||
| # | 组件 | 端口 | 来源 | 数据库 |
|
||||
|---|------|------|------|--------|
|
||||
| A1 | ninca-common-backend | 33010 | `星中心/ninca_common_01-ninca_common_backend.tar.gz` | `ninca_common` |
|
||||
| A2 | component-organization | 33011 | `星中心/ninca_common_component_organization_01-...tar.gz` | `component-organization` |
|
||||
| A3 | cwos-system-api | 3333 | `星中心/cwos_system_api_01-cwos_system_api/` (已解压) | — |
|
||||
| A4 | cwos-manager | 3721 | Docker Harbor 镜像 (已有 compose) | `cwos_manager` |
|
||||
| A5 | cwos-portal | 33008 | `星中心/cwos_portal` SQL 备份 → 重建 | `cwos_portal` |
|
||||
| A6 | snap-app | 33012 | `星中心/ninca_common_snap_app_01-...tar.gz` | — |
|
||||
| A7 | vehicle-app | 33013 | `星中心/ninca_common_vehicle_app_01-...tar.gz` | — |
|
||||
| A8 | person-file-app | 33014 | `星中心/ninca-person-file-app-V2.9.2_20210216.tar.gz` | — |
|
||||
| A9 | monitor-app | 33015 | `星中心/ninca_common_monitor_app_01-...tar.gz` | — |
|
||||
| A10 | CRK-std | 16106 (app), 16114 (mgmt) | `星中心/ninca_crk_std_01-ninca_crk_std_backend/` (JAR已就绪) | `ninca_crk_std` |
|
||||
| A11 | alarm-app | 17011 (app), 17211 (mgmt) | `星中心/ninca_qk_alarm_app_01-ninca_qk_alarm_app/` (JAR已就绪) | `alarm_deploy` |
|
||||
| A12 | elevator V2 | 18081 | Maven 构建 `maven-cw-elevator-application` | `cw-elevator-application` |
|
||||
| A13 | elevator V1 | 18080 | 对照基线 (已有 JAR) | `cw-elevator-application` |
|
||||
|
||||
### 3.3 数据库 (MySQL 192.168.3.12:3307)
|
||||
|
||||
| 数据库 | 备份文件 | 大小 |
|
||||
|--------|---------|------|
|
||||
| `cw-elevator-application` | `data_backup/12_*.sql.gz` + `34_*.sql.gz` | 158M + 2.5G |
|
||||
| `ninca_crk_std` | (含在 CRK 配置中,需确认备份) | — |
|
||||
| `alarm_deploy` | `data_backup/alarm_deploy_*.sql.gz` | 15M |
|
||||
| `cwos_manager` | `data_backup/cwos_manager_*.sql.gz` | 51K |
|
||||
| `cwos_portal` | `data_backup/cwos_portal_*.sql.gz` | 212M |
|
||||
| `ninca_common` | `data_backup/ninca_common_*.sql.gz` | 6M |
|
||||
| `component-organization` | `data_backup/component-organization_*.sql.gz` | 212M |
|
||||
| `ods` | `data_backup/ods_*.sql.gz` | 485K |
|
||||
| `cloudwalk_device_thirdparty` | `data_backup/cloudwalk_device_thirdparty_*.sql.gz` | 4K |
|
||||
| `g` / `p` / `12` / `34` | 其他备份 | 小量 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 配置统一化
|
||||
|
||||
### 4.1 环境变量 (`config/env.sh`)
|
||||
|
||||
```bash
|
||||
# 基础设施
|
||||
MYSQL_HOST=192.168.3.12
|
||||
MYSQL_PORT=3307
|
||||
MYSQL_USER=root
|
||||
MYSQL_PASS=123456
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASS=1qaz!QAZ
|
||||
CONSUL_HOST=127.0.0.1
|
||||
CONSUL_PORT=8500
|
||||
KAFKA_HOST=127.0.0.1
|
||||
KAFKA_PORT=9092
|
||||
ZK_HOST=127.0.0.1
|
||||
ZK_PORT=2181
|
||||
|
||||
# 服务端口
|
||||
PORT_ELEVATOR_V2=18081
|
||||
PORT_ELEVATOR_V1=18080
|
||||
PORT_CRK_STD=16106
|
||||
PORT_CRK_MGMT=16114
|
||||
PORT_ALARM=17011
|
||||
PORT_ALARM_MGMT=17211
|
||||
PORT_CWOS_PORTAL=33008
|
||||
PORT_COMPONENT_ORG=33011
|
||||
PORT_NINCA_COMMON=33010
|
||||
PORT_CWOS_MANAGER=3721
|
||||
PORT_SYSTEM_API=3333
|
||||
PORT_SNAP=33012
|
||||
PORT_VEHICLE=33013
|
||||
PORT_PERSON_FILE=33014
|
||||
PORT_MONITOR=33015
|
||||
PORT_NGINX=8090
|
||||
|
||||
# Java
|
||||
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
|
||||
JAVA_OPTS="-Xmx2048m -Xms512m"
|
||||
```
|
||||
|
||||
### 4.2 配置替换规则
|
||||
|
||||
每个 Java 服务的 `application.properties` / `bootstrap.properties` 中需要替换:
|
||||
|
||||
| 原始值 (生产) | 替换为 (测试) |
|
||||
|--------------|-------------|
|
||||
| `mysql_01.mysql_ip` | `192.168.3.12` |
|
||||
| `redis_01.redis_ip` | `127.0.0.1` |
|
||||
| `371bfca....` (Consul UUID host) | `127.0.0.1` |
|
||||
| `44700995e....` (Kafka broker 1) | `127.0.0.1:9092` |
|
||||
| `0837a70b5....` (Kafka broker 2) | (移除,仅保留一个 broker) |
|
||||
| `10.128.161.95` (内网 IP 地址) | `127.0.0.1` |
|
||||
| `10.0.22.207` (内网 ZK) | `127.0.0.1:2181` |
|
||||
| `10.128.123.108` (内网 DB) | `192.168.3.12` |
|
||||
| `10.0.22.102` (内网 CRK IP) | `127.0.0.1` |
|
||||
| `3306` (生产 DB 端口) | `3307` |
|
||||
|
||||
---
|
||||
|
||||
## 5. 脚本设计
|
||||
|
||||
### 5.1 文件结构
|
||||
|
||||
```
|
||||
源码/scripts/test-env/
|
||||
├── docker-compose.infra.yml # 合并 Consul+Redis+Kafka+Nginx
|
||||
├── config/
|
||||
│ ├── env.sh # 统一环境变量
|
||||
│ ├── consul/consul-config.json # Consul 初始化配置
|
||||
│ └── service-templates/ # 配置文件模板 (.properties 模板)
|
||||
├── setup.sh # 主入口: 一键搭建
|
||||
├── start-all.sh # 启动全部服务
|
||||
├── stop-all.sh # 停止全部服务
|
||||
├── health-check.sh # 探活检查
|
||||
├── prepare-db.sh # 数据库恢复
|
||||
├── prepare-services.sh # 解压 + 配置注入
|
||||
├── build-elevator-v2.sh # 编译 V2 电梯应用
|
||||
└── verify-functional.sh # 功能验证
|
||||
```
|
||||
|
||||
### 5.2 启动依赖拓扑
|
||||
|
||||
```
|
||||
I1 Consul ──┐
|
||||
I2 Redis ───┤ (Docker 并行启动)
|
||||
I3 Kafka ───┤
|
||||
I4 Nginx ───┘
|
||||
|
||||
A1 ninca-common ─────────────────────────┐
|
||||
A2 component-org ←── common ─────────────┤
|
||||
A3 system-api ←── manager ───────────────┤
|
||||
A4 cwos-manager (Docker) ────────────────┤
|
||||
A5 cwos-portal ←── org + manager ────────┤
|
||||
A6 snap-app ←── portal + common ─────────┤
|
||||
A7 vehicle-app ←── portal + common ──────┤
|
||||
A8 person-file ←── portal ───────────────┤
|
||||
A9 monitor-app (独立) ───────────────────┤
|
||||
A10 CRK-std ←── portal + org + elevator ─┤
|
||||
A11 alarm-app ←── portal + org + Kafka ──┤
|
||||
A12 elevator-V2 ←── CRK + portal + org ──┤
|
||||
A13 elevator-V1 (对拍对照) ───────────────┘
|
||||
```
|
||||
|
||||
### 5.3 主入口执行流程
|
||||
|
||||
```
|
||||
setup.sh:
|
||||
Phase 1: 环境检查 (< 10s)
|
||||
├── JDK 8 检查
|
||||
├── Maven 检查
|
||||
├── Docker 检查
|
||||
├── MySQL 连通性检查 (192.168.3.12:3307)
|
||||
└── 端口冲突检查
|
||||
|
||||
Phase 2: 数据库准备 (~5min)
|
||||
├── 创建所有数据库 (如不存在)
|
||||
├── 解压并导入 11 个 SQL 备份
|
||||
└── 执行 V2 DDL (tenant_visitor_floor_policy.sql)
|
||||
|
||||
Phase 3: Docker 启动 (~30s)
|
||||
├── docker compose up infra
|
||||
├── 等待 Consul Ready
|
||||
├── 等待 Kafka Ready
|
||||
└── 启动 Nginx
|
||||
|
||||
Phase 4: 服务准备 (~5-10min)
|
||||
├── 解压 5 个 tar.gz
|
||||
├── 从模板生成各服务配置文件
|
||||
├── 编译 V2 电梯应用
|
||||
└── 同步 JAR 到 deploy/
|
||||
|
||||
Phase 5: 服务启动 (~2min)
|
||||
├── 按拓扑序依次启动 13 个 Java 服务
|
||||
├── 每个服务启动后探活确认
|
||||
└── 失败自动重试 (max 3 次)
|
||||
|
||||
Phase 6: 验证 (~2min)
|
||||
├── 全端口探活
|
||||
├── Consul 注册检查
|
||||
├── V1/V2 API 对拍 (pytest)
|
||||
└── 输出验证报告
|
||||
|
||||
总耗时: ~15-20 分钟
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证矩阵
|
||||
|
||||
| ID | 验证项 | 命令/方法 | 通过标准 |
|
||||
|----|--------|----------|---------|
|
||||
| H1 | 所有端口可达 | `nc -z 127.0.0.1 <port>` x 15 | 全部 open |
|
||||
| H2 | 所有 HTTP 健康检查 | `curl /actuator/health` x 8 | 全部 200 + UP |
|
||||
| H3 | Consul 服务注册 | `curl :8500/v1/agent/services` | 10+ 服务已注册 |
|
||||
| F1 | V1/V2 API 对拍 | `pytest tools/elevator_api_parity/tests/` | 全部通过 |
|
||||
| F2 | V2.0.7 UC-01 基线 | 不传 floorIds, 无策略 | floorList 全集 |
|
||||
| F3 | V2.0.7 UC-01 固定楼层 | 不传 floorIds, 有策略 | detail.floorList(组织侧 **替代** 后;电梯不求交) |
|
||||
| F4 | V2.0.7 无交集 | allow 与 floorList 无交集 | 错误码 76260532 |
|
||||
| F5 | V2.0.7 被访人无楼层 | personId 无楼层 | 错误码 76260531 |
|
||||
| F6 | V2.0.7 UC-02 | 显式传 floorIds | 不读策略表 |
|
||||
| F7 | CRK 联动 | curl CRK → 电梯回调 | CRK 日志有 Feign 记录 |
|
||||
| F8 | 报警 Kafka 消费 | 提交事件 → alarm 日志 | Kafka 消息到达 |
|
||||
| F9 | Nginx 前端代理 | `curl :8090` | cwos-portal 首页 |
|
||||
| I1 | MySQL 连通 | `mysql -h 192.168.3.12 -P3307 -e 'SELECT 1'` | 1 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 已知风险与缓解
|
||||
|
||||
| 风险 | 影响 | 缓解 |
|
||||
|------|------|------|
|
||||
| `星中心/*.tar.gz` 内 JAR 版本与 DB 备份不匹配 | 启动失败 | 从同一时间点导出 (2026-04-23) 的 DB 备份对齐 |
|
||||
| Docker Kafka 首次启动慢 | Phase 3 超时 | 脚本增加 60s 等待 + 重试逻辑 |
|
||||
| V2 Maven 编译需 Nexus 私服 | 编译失败 | 已有 `build_nexus_only.sh` 降级方案 |
|
||||
| cwos-portal 无独立 JAR | 无法启动 | 从 DB 备份 `cwos_portal` 库确认部署方式 |
|
||||
| 端口冲突 (已有进程占用) | 启动失败 | Phase 1 预先检查, 用 `lsof` 检测并提示 |
|
||||
| ES (Elasticsearch) 依赖 | alarm 启动可能失败 | alarm `application.properties` 配置 ES, 需 Docker 添加 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 未解决问题
|
||||
|
||||
1. **cwos-portal 部署方式**: 星中心中没有 cwos-portal 的 JAR 或 tarball, 仅 DB 备份中有 `cwos_portal` 数据。需确认其部署形态 (fat JAR? war? Docker?)。
|
||||
2. **cwos-system-api 启动入口**: `cwos_system_api_01-cwos_system_api/` 已解压, 需确认 JAR 入口和端口。
|
||||
3. **Elasticsearch**: alarm 依赖 ES (`elasticsearch.ip`), 是否需要在 Docker Compose 中添加 ES 容器?
|
||||
4. **Dubbo/ZK**: V1 电梯 bootstrap 配置有 Dubbo + ZK, V2 是否需要?可以在 Docker Compose 中添加 ZK。
|
||||
|
||||
---
|
||||
|
||||
## 9. 产出物
|
||||
|
||||
- `源码/scripts/test-env/docker-compose.infra.yml` — 基础组件 Docker Compose
|
||||
- `源码/scripts/test-env/setup.sh` — 一键搭建脚本
|
||||
- `源码/scripts/test-env/config/env.sh` — 统一环境变量
|
||||
- `源码/scripts/test-env/config/service-templates/` — 14 个配置模板
|
||||
- `源码/scripts/test-env/start-all.sh` / `stop-all.sh` — 启停脚本
|
||||
- `源码/scripts/test-env/verify-functional.sh` — 功能验证脚本
|
||||
@@ -0,0 +1,279 @@
|
||||
# 租户访客默认楼层策略 — 业务逻辑设计
|
||||
|
||||
**日期**:2026-05-05
|
||||
**状态**:设计稿(待审核)
|
||||
**基线版本**:v2.0.20
|
||||
|
||||
---
|
||||
|
||||
## 1. 现有问题
|
||||
|
||||
`PersonRuleServiceImpl.addVisitor` 中策略查询被包裹在 `if (!callerProvidedFloors)` 条件内,导致调用方一旦传了 `floorIds`(UC-02),策略被完全跳过。
|
||||
|
||||
**修正要求**:任何时候都应当查询策略,有策略且生效则以策略 `allow_zone_ids` **替代** 候选楼层。
|
||||
|
||||
---
|
||||
|
||||
## 2. 完整时序图
|
||||
|
||||
### 2.1 UC-01:调用方未传 floorIds(组织默认楼层)
|
||||
|
||||
```
|
||||
调用方 电梯应用 组织服务 tenant_visitor_floor_policy
|
||||
│ │ │ │
|
||||
│ POST /add/visitor │ │ │
|
||||
│ (无floorIds) │ │ │
|
||||
│ ────────────────────→ │ │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase1: 查组织 ── │ │
|
||||
│ │ POST /component/person/detail(personId) │
|
||||
│ │ ─────────────────────→ │ │
|
||||
│ │ ←───────────────────── │ │
|
||||
│ │ PersonResult { │ │
|
||||
│ │ floorList: [A,B,C], │ │
|
||||
│ │ organizationIds:[...] │ │
|
||||
│ │ } │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase2: 候选楼层 ── │ │
|
||||
│ │ candidate = floorList │ │
|
||||
│ │ = [A,B,C] │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase3: ALWAYS 查策略 ── │
|
||||
│ │ SELECT * WHERE org_id=? AND enabled=1 │
|
||||
│ │ ──────────────────────────────────────────→ │
|
||||
│ │ ←────────────────────────────────────────── │
|
||||
│ │ │ │
|
||||
│ │ ┌─ policy存在? ─┐ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ YES NO │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ▼ ▼ │ │
|
||||
│ │ effective= effective │ │
|
||||
│ │ allow_zone_ids =candidate │ │
|
||||
│ │ =[6F] =[A,B,C] │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase4: 空集校验 ── │ │
|
||||
│ │ effective=[6F] │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase5: 写规则绑图库 ── │
|
||||
│ ←──────────────────── │ │ │
|
||||
│ success(6F) │ │ │
|
||||
```
|
||||
|
||||
### 2.2 UC-02:调用方传了 floorIds
|
||||
|
||||
```
|
||||
调用方 电梯应用 组织服务 tenant_visitor_floor_policy
|
||||
│ │ │ │
|
||||
│ POST /add/visitor │ │ │
|
||||
│ floorIds=[7F,8F] │ │ │
|
||||
│ ────────────────────→ │ │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase1: 查组织 ── │ │
|
||||
│ │ POST /component/person/detail(personId) │
|
||||
│ │ ─────────────────────→ │ │
|
||||
│ │ ←───────────────────── │ │
|
||||
│ │ PersonResult { │ │
|
||||
│ │ organizationIds │ │
|
||||
│ │ } ← 仅取orgIds,不用floorList │
|
||||
│ │ │ │
|
||||
│ │ ── Phase2: 候选楼层 ── │ │
|
||||
│ │ candidate = param.floorIds │
|
||||
│ │ = [7F,8F] │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase3: ALWAYS 查策略 ── │
|
||||
│ │ SELECT * WHERE org_id=? AND enabled=1 │
|
||||
│ │ ──────────────────────────────────────────→ │
|
||||
│ │ ←────────────────────────────────────────── │
|
||||
│ │ │ │
|
||||
│ │ ┌─ policy存在? ─┐ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ YES NO │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ▼ ▼ │ │
|
||||
│ │ effective= effective │ │
|
||||
│ │ allow_zone_ids =candidate │ │
|
||||
│ │ =[6F] =[7F,8F] │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase4: 空集校验 ── │ │
|
||||
│ │ effective=[6F] │ │
|
||||
│ │ │ │
|
||||
│ │ ── Phase5: 写规则绑图库 ── │
|
||||
│ ←──────────────────── │ │ │
|
||||
│ success(6F) │ │ │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 完整控制流
|
||||
|
||||
```
|
||||
addVisitor(param, context):
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Phase1: 查被访人组织(ALWAYS,用于策略获取) │
|
||||
│ detail = personService.detail(personId, businessId) │
|
||||
│ if detail fail → return detail.code/msg │
|
||||
│ personResult = detail.getData() │
|
||||
│ orgIds = personResult.getOrganizationIds() │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Phase2: 确定候选楼层 candidate │
|
||||
│ if (param.floorIds 非空): ← UC-02 │
|
||||
│ candidate = param.floorIds │
|
||||
│ else: ← UC-01 │
|
||||
│ candidate = personResult.getFloorList() │
|
||||
│ if candidate 为空 → return 76260531 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Phase3: ALWAYS 查策略,有策略则替代 │
|
||||
│ policy = DAO.selectEnabledByOrgId(orgId) │
|
||||
│ if (policy 存在 && enabled=1): │
|
||||
│ effective = parseAllowZoneIds(policy.allow_zone_ids)│
|
||||
│ ← 策略的 allow 直接替代,非求交 │
|
||||
│ else: │
|
||||
│ effective = candidate ← 无策略约束,原值 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Phase4: 空集校验 │
|
||||
│ if effective 为空 → return 76260531 │
|
||||
│ param.setFloorIds(effective) │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Phase5: 写规则 + 绑图库(不变) │
|
||||
│ zoneService.page → image_rule_ref → batchBind → ... │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 决策矩阵
|
||||
|
||||
| 场景 | caller传floorIds | 有生效策略 | 行为 | 最终楼层 |
|
||||
|------|----------------|-----------|------|---------|
|
||||
| **A** | 空 | 无 | 取组织 floorList | `floorList` |
|
||||
| **B** | 空 | 有,allow=[6F] | 策略替代 | `[6F]` |
|
||||
| **C** | `[7F]` | 无 | 用调用方传入值 | `[7F]` |
|
||||
| **D** | `[7F]` | 有,allow=[6F] | 策略替代(忽略调用方) | `[6F]` |
|
||||
| **E** | `[6F,7F]` | 有,allow=[6F] | 策略替代 | `[6F]` |
|
||||
| **F** | 空 | 有,allow=[](无效或空) | 等同无策略 | `floorList` |
|
||||
|
||||
### 关键差异
|
||||
|
||||
| 场景 | 旧行为(v2.0.18) | 新行为(v2.0.20) |
|
||||
|------|-----------------|-----------------|
|
||||
| **D**:传 7F,策略 allow=6F | 开通 7F(绕过策略 ❌) | 开通 6F(策略替代 ✅) |
|
||||
| **E**:传 6F+7F,策略 allow=6F | 开通 6F+7F(绕过策略 ❌) | 仅开通 6F(策略替代 ✅) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误码
|
||||
|
||||
| 错误码 | 场景 | 说明 |
|
||||
|--------|------|------|
|
||||
| 76260531 | 无可用楼层 | Phase4 中 `effective` 为空(含被访人无楼层、策略 allow 为空等) |
|
||||
| 76260533 | 策略配置错误 | `allow_zone_ids` JSON 解析失败(已有 `parseAllowZoneIds` 兜底返回空) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 日志设计
|
||||
|
||||
| 路径 | 级别 | 内容 |
|
||||
|------|------|------|
|
||||
| 请求入口 | INFO | businessId, personId, visitorId, requestFloorSize |
|
||||
| Phase1 detail 失败 | WARN | personId, code, msg |
|
||||
| Phase2 UC-01 | INFO | candidate = floorList |
|
||||
| Phase2 UC-02 | INFO | candidate = param.floorIds |
|
||||
| Phase2 空 floorList | WARN | personId |
|
||||
| Phase3 查策略 | INFO | orgIds |
|
||||
| Phase3 策略存在 | INFO | policyId, orgId, allow_zone_ids |
|
||||
| Phase3 策略不存在 | INFO | 使用候选楼层原值 |
|
||||
| Phase4 空集 | WARN | businessId, personId, visitorId, candidate |
|
||||
|
||||
---
|
||||
|
||||
## 7. 初始化流程分析(登记页楼层展示)
|
||||
|
||||
### 7.1 调用路径
|
||||
|
||||
基于 `cwos-component-organization-service` 反编译分析,初始化时的 `/component/person/detail` 调用链:
|
||||
|
||||
```
|
||||
登记页初始化
|
||||
│
|
||||
├─ GET/POST /component/person/detail ───→ 组织服务 (ninca-common-component-organization)
|
||||
│ │
|
||||
│ PersonController.java:132 │
|
||||
│ → imgStorePersonService.detail() │
|
||||
│ → ImgPersonServiceImpl.java │
|
||||
│ line 639-650 │
|
||||
│ → elevatorFeignClient │
|
||||
│ .listByImageId(...) │ ← 调电梯查询人员授权楼层
|
||||
│ → image_rule_ref 表查询 │
|
||||
│ ← floorList (List<zoneId>) │
|
||||
│ │
|
||||
│ ← PersonResult { │
|
||||
│ floorList: ["zoneId1","zoneId2",...], │ ← 被访人当前授权楼层
|
||||
│ organizationIds: [...], │ ← 用于 addVisitor 策略查询
|
||||
│ defaultFloor, chooseFloor, ... │
|
||||
│ } │
|
||||
│ │
|
||||
└─ 展示 floorList → 用户选择 → POST /add/visitor
|
||||
→ 策略替代(如有)
|
||||
```
|
||||
|
||||
### 7.2 floorList 的数据来源
|
||||
|
||||
`ImgPersonServiceImpl.java` 中 `floorList` 的组装逻辑:
|
||||
|
||||
```java
|
||||
// line 639-650 (ImgPersonServiceImpl.java)
|
||||
ArrayList<String> floorList = new ArrayList<>();
|
||||
// 调电梯 Feign 接口获取该人员在 image_rule_ref 中的授权区域
|
||||
CloudwalkResult<List<AcsPassRuleImageResultDto>> images =
|
||||
this.elevatorFeignClient.listByImageId(acsPassRuleImageForm);
|
||||
for (int i = 0; i < acsPassRuleImageResultDtoList.size(); i++) {
|
||||
floorList.add(((AcsPassRuleImageResultDto)acsPassRuleImageResultDtoList.get(i)).getZoneId());
|
||||
}
|
||||
personResult.setFloorList(floorList);
|
||||
```
|
||||
|
||||
**结论**:`floorList` 是被访人在电梯 `image_rule_ref` 中**已有授权的楼层集合**,不是全量楼层。所以初始化页展示的本身就是被访人有权限的楼层。
|
||||
|
||||
### 7.3 初始化和提交的联动关系
|
||||
|
||||
| 阶段 | 调用接口 | 返回数据 | 是否经过策略 |
|
||||
|------|---------|---------|------------|
|
||||
| 初始化 | `组织服务 /component/person/detail` | `floorList`(授权楼层)| ❌ 策略在组织服务中不存在 |
|
||||
| 提交 | `电梯应用 /add/visitor` | 开通结果 | ✅ v2.0.20 已修复 |
|
||||
|
||||
### 7.4 潜在问题
|
||||
|
||||
由于初始化时不经过策略,存在展示与开通不一致的可能:
|
||||
|
||||
| 场景 | 初始化展示 | 用户选择 | addVisitor 实际开通 | 用户体验 |
|
||||
|------|-----------|---------|-------------------|---------|
|
||||
| 被访人授权多楼层,策略 allow=[6F] | floorList=[6F,7F,8F] | 选 7F | **6F(策略替代)** | 用户困惑 |
|
||||
| 被访人授权多楼层,策略 allow=[6F] | floorList=[6F,7F,8F] | 不选(UC-01) | **6F(策略替代)** | 与预期一致 |
|
||||
| 无策略 | floorList | 任意 | 与选择一致 | 正常 |
|
||||
|
||||
**解决方案建议**(后续阶段):
|
||||
在电梯应用新增 `/elevator/person/effective-floors` 预览接口,供初始化时展示策略约束后的有效楼层。当前阶段可先通过前端交互说明,或由第三方 BFF 自行做策略感知。
|
||||
|
||||
---
|
||||
|
||||
## 8. 与现有文档的差异
|
||||
|
||||
| 文档 | 旧逻辑 | 新逻辑 |
|
||||
|------|--------|--------|
|
||||
| 数据库阶段技术设计 §5.2 | UC-02 不读策略表 | UC-02 读策略表,替代 |
|
||||
| 数据库阶段技术设计 §5.3 | UC-02 以请求为准 | UC-02 以策略为准 |
|
||||
| 当前代码 `if(!callerProvidedFloors)` | 策略仅在 UC-01 查 | 策略 ALWAYS 查 |
|
||||
| `resolveEffectiveFrozens` 方法 | 历史:∩ + 校验 hostFloors | **废止**;规范语义为组织侧 **替代**,电梯透传 `floorList` |
|
||||
@@ -0,0 +1,188 @@
|
||||
# 租户访客默认楼层策略 — 业务逻辑重设计
|
||||
|
||||
> **废止 / 仅作历史归档(2026-05-06)**
|
||||
> **现行规范**以 **[租户访客默认楼层策略 — 迁入组织组件](./2026-05-06-tenant-visitor-policy-organization-implementation.md)** 为准:租户策略 **`allow_zone_ids` 只能在组织 `PersonService.detail` 以「**替代**」写入 `floorList`**;**禁止**将策略定义为与被访人楼层 **「求交(∩)」**。电梯 `addVisitor` **不再**读取策略表、**不再**做 ∩。
|
||||
> 下文保留 **2026-05-05** 草稿中的电梯侧求交流程图,**仅供对照旧表述**,勿作为实现或验收依据。
|
||||
|
||||
**日期**:2026-05-05
|
||||
**状态**:已废止(见上)
|
||||
**设计依据**:产品方案 [租户访客默认楼层技术产品方案](../../business/租户访客默认楼层技术产品方案.md)
|
||||
**涉及代码**:`PersonRuleServiceImpl.addVisitor`(现行代码已按 2026-05-06 规范移除电梯侧策略 ∩)
|
||||
|
||||
---
|
||||
|
||||
## 1. 业务规则(核心不变量)—— **历史草案,已被「替代语义」取代**
|
||||
|
||||
| 规则 | 说明(历史) |
|
||||
|------|------|
|
||||
| ~~**策略全时生效**~~ | **废止**:电梯侧不再全时查策略表;策略在组织 detail **替代** `floorList`。 |
|
||||
| **策略即安全边界** | `allow_zone_ids` 仍表达租户允许的 zone 集合;落实方式为 **替代写入 detail**,而非 ∩。 |
|
||||
| **无策略不禁锢** | 组织未命中策略时,`floorList` 仍为 `listByImageId` 遍历结果。 |
|
||||
| ~~**交集为空必须拒绝**~~ | **废止**:不再使用 `candidate ∩ allow`;空 `floorList`/effective 走 **`76260531`**。 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 总流程
|
||||
|
||||
### 2.1 UC-01:调用方未传 floorIds
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Caller as 调用方/BFF
|
||||
participant Elevator as 电梯应用<br/>cw-elevator-application
|
||||
participant Org as 组织服务<br/>ninca-common-component-organization
|
||||
participant PolicyDB as 策略表<br/>tenant_visitor_floor_policy
|
||||
|
||||
Caller->>Elevator: POST /elevator/person/add/visitor<br/>{personId, visitorId}<br/>(不传 floorIds)
|
||||
|
||||
Note over Elevator: 阶段1:查被访人信息
|
||||
Elevator->>Org: POST /component/person/detail<br/>{personId, businessId}
|
||||
Org-->>Elevator: PersonResult<br/>{floorList, organizationIds}
|
||||
|
||||
Note over Elevator: 阶段2:候选楼层 = floorList
|
||||
Note over Elevator: floorList 来自组织服务
|
||||
|
||||
Note over Elevator: 阶段3:查策略
|
||||
Elevator->>PolicyDB: SELECT * FROM tenant_visitor_floor_policy<br/>WHERE org_id IN (organizationIds) AND enabled=1
|
||||
PolicyDB-->>Elevator: policy 行 / 空
|
||||
|
||||
alt 策略存在且生效
|
||||
Note over Elevator: 最终楼层 = floorList ∩ allow_zone_ids
|
||||
alt 交集为空
|
||||
Elevator-->>Caller: 失败 76260532<br/>(租户策略与被访人授权无交集)
|
||||
else 交集非空
|
||||
Note over Elevator: 继续开通流程
|
||||
Elevator-->>Caller: 成功,仅开通交集内楼层
|
||||
end
|
||||
else 无策略或未启用
|
||||
Note over Elevator: 最终楼层 = floorList(原值)
|
||||
Elevator-->>Caller: 成功
|
||||
end
|
||||
```
|
||||
|
||||
### 2.2 UC-02:调用方传入 floorIds
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Caller as 调用方/BFF
|
||||
participant Elevator as 电梯应用<br/>cw-elevator-application
|
||||
participant Org as 组织服务<br/>ninca-common-component-organization
|
||||
participant PolicyDB as 策略表<br/>tenant_visitor_floor_policy
|
||||
|
||||
Caller->>Elevator: POST /elevator/person/add/visitor<br/>{personId, visitorId, floorIds:[...]}
|
||||
|
||||
Note over Elevator: 阶段1:查被访人信息(仅取 organizationIds)
|
||||
Elevator->>Org: POST /component/person/detail<br/>{personId, businessId}
|
||||
Org-->>Elevator: PersonResult<br/>{organizationIds}
|
||||
|
||||
Note over Elevator: 阶段2:候选楼层 = 调用方传入的 floorIds
|
||||
|
||||
Note over Elevator: 阶段3:查策略(ALWAYS)
|
||||
Elevator->>PolicyDB: SELECT * FROM tenant_visitor_floor_policy<br/>WHERE org_id IN (organizationIds) AND enabled=1
|
||||
PolicyDB-->>Elevator: policy 行 / 空
|
||||
|
||||
alt 策略存在且生效
|
||||
Note over Elevator: 最终楼层 = callerFloorIds ∩ allow_zone_ids
|
||||
alt 交集为空
|
||||
Elevator-->>Caller: 失败 76260532<br/>(请求楼层不在策略允许范围内)
|
||||
else 交集非空
|
||||
Elevator-->>Caller: 成功,仅开通交集内楼层
|
||||
end
|
||||
else 无策略或未启用
|
||||
Note over Elevator: 最终楼层 = callerFloorIds(原值)
|
||||
Elevator-->>Caller: 成功
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 控制流伪代码
|
||||
|
||||
```
|
||||
addVisitor(param, context):
|
||||
// === 阶段1:查被访人组织信息(ALWAYS)===
|
||||
detailResult = personService.detail(personId, businessId)
|
||||
if failed: return detailResult.error
|
||||
person = detailResult.data
|
||||
|
||||
// === 阶段2:确定候选楼层 ===
|
||||
if param.floorIds 非空: ← UC-02
|
||||
candidate = param.floorIds
|
||||
else: ← UC-01
|
||||
candidate = person.floorList
|
||||
if candidate 为空: return 76260531
|
||||
|
||||
// === 阶段3:ALWAYS 查策略 ===
|
||||
policy = findEnabledPolicy(person.organizationIds)
|
||||
if policy != null:
|
||||
effective = intersect(candidate, policy.allow_zone_ids)
|
||||
if effective 为空: return 76260532
|
||||
else:
|
||||
effective = candidate
|
||||
|
||||
// === 阶段4:空集校验 ===
|
||||
if effective 为空: return 76260531
|
||||
param.floorIds = effective
|
||||
|
||||
// === 阶段5:开通流程(不变)===
|
||||
zoneService.page → image_rule_ref → batchBind → ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 场景对照矩阵
|
||||
|
||||
| 场景 | 调用方 floorIds | 策略状态 | 候选楼层 | 最终结果 |
|
||||
|------|----------------|---------|---------|---------|
|
||||
| UC-01 无策略 | 空 | 无策略行 | `floorList` | `floorList` ✅ |
|
||||
| UC-01 + 策略通过 | 空 | 有且生效 | `floorList` | `floorList ∩ allow` ✅ |
|
||||
| UC-01 + 策略无交集 | 空 | allow 与 floorList 无交集 | `floorList` | 失败 76260532 ✅ |
|
||||
| UC-01 被访人无楼层 | 空 | 任意 | `floorList`=空 | 失败 76260531 ✅ |
|
||||
| UC-02 无策略 | [A,B] | 无策略行 | [A,B] | [A,B] ✅ |
|
||||
| UC-02 + 策略包含 | [A,B] | allow=[A,C] | [A,B] | [A] ✅ |
|
||||
| UC-02 + 策略不包含 | [A,B] | allow=[C,D] | [A,B] | 失败 76260532 ✅ |
|
||||
|
||||
### 与当前实现的差异
|
||||
|
||||
| 场景 | 当前实现 | 重设计后 |
|
||||
|------|---------|---------|
|
||||
| UC-02 + 策略存在 | 绕过策略,按请求楼层开通 ❌ | 策略求交 ✅ |
|
||||
| UC-02 + 策略不包含请求楼层 | 成功开通(本应拒绝)❌ | 失败 76260532 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误码
|
||||
|
||||
| 错误码 | 触发条件 | 说明 |
|
||||
|--------|---------|------|
|
||||
| 76260531 | 候选楼层为空 | 被访人 `floorList` 为空,或有效楼层为空 |
|
||||
| 76260532 | `candidate ∩ allow` 无交集 | 策略约束了可访楼层,但候选楼层全部不在允许范围内 |
|
||||
| 76260533 | 策略配置错误 | `allow_zone_ids` 包含被访人无权限的 zoneId |
|
||||
|
||||
---
|
||||
|
||||
## 6. 日志规范
|
||||
|
||||
| 关键路径 | 日志内容 |
|
||||
|---------|---------|
|
||||
| 入口 | `businessId, personId, visitorId, requestFloorSize` |
|
||||
| UC-01 | `调用方未传楼层,取被访人默认楼层 floorList=xxx` |
|
||||
| UC-02 | `调用方已指定楼层,候选楼层=candidate` |
|
||||
| 策略查询 | `查询组织 orgIds=xxx` |
|
||||
| 策略命中 | `找到启用策略 policyId=xxx allow=xxx` |
|
||||
| 策略未命中 | `未找到启用策略,使用候选楼层原值` |
|
||||
| 求交成功 | `策略生效,最终楼层=effective` |
|
||||
| 求交为空 | `候选楼层与策略无交集,返回 76260532` |
|
||||
| 空楼层 | `无可用楼层,返回 76260531` |
|
||||
|
||||
---
|
||||
|
||||
## 7. 实施范围
|
||||
|
||||
| 变更点 | 影响 | 风险 |
|
||||
|--------|------|------|
|
||||
| UC-02 增加 `personService.detail()` 调用 | 多一次 RPC(数百微秒) | 低 |
|
||||
| UC-02 增加策略求交逻辑 | 对传入楼层做过滤 | 中——调用方可能不符合预期 |
|
||||
| 删除 `callerProvidedFloors` 分支 | 简化代码结构 | 低 |
|
||||
|
||||
> **兼容性提醒**:UC-02 行为改变意味着:之前能开通的请求(传入策略允许范围外的楼层)现在会失败。需通知集成方。
|
||||
@@ -0,0 +1,138 @@
|
||||
# 租户访客默认楼层策略 — 日志完善方案
|
||||
|
||||
**日期**: 2026-05-06
|
||||
**方案**: 统一 `[POLICY]` 日志前缀,确保部署后可通过单次 grep 判定策略是否生效
|
||||
|
||||
---
|
||||
|
||||
## 日志格式约定
|
||||
|
||||
| 前缀 | 含义 | 级别 |
|
||||
|------|------|------|
|
||||
| `[POLICY]` | 策略入口被调用 | INFO |
|
||||
| `[POLICY-HIT]` | 策略命中,返回 allow_zone_ids | INFO |
|
||||
| `[POLICY-MISS]` | 策略未命中(无策略/未启用/allow为空) | DEBUG |
|
||||
| `[POLICY-DETAIL]` | detail() 方法中的策略结果 | INFO |
|
||||
| `[POLICY-LIST]` | listByPage() 方法中的策略结果 | INFO |
|
||||
|
||||
## grep 速查
|
||||
|
||||
```bash
|
||||
# 查看所有策略调用
|
||||
grep "\[POLICY\]" component-organization.info.log
|
||||
|
||||
# 只看策略命中
|
||||
grep "\[POLICY-HIT\]" component-organization.info.log
|
||||
|
||||
# 统计调用次数
|
||||
grep -c "\[POLICY\]" component-organization.info.log
|
||||
```
|
||||
|
||||
## 修改清单
|
||||
|
||||
### 1. TenantVisitorFloorPolicyService.java — 3 处修改
|
||||
|
||||
**L68-74 (replacementZoneIdsIfPolicyActive — 多 orgIds 入口)**:
|
||||
```java
|
||||
public Optional<List<String>> replacementZoneIdsIfPolicyActive(List<String> orgIds) {
|
||||
if (CollectionUtils.isEmpty(orgIds)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
log.info("[POLICY] entry orgIds={}", orgIds); // ← 新增
|
||||
for (String id : orgIds) {
|
||||
Optional<List<String>> zones = replacementZoneIdsIfPolicyActive(id);
|
||||
if (zones.isPresent()) {
|
||||
return zones;
|
||||
}
|
||||
}
|
||||
log.debug("[POLICY-MISS] no enabled policy for any org in {}", orgIds); // ← 新增
|
||||
return Optional.empty();
|
||||
}
|
||||
```
|
||||
|
||||
**L83-98 (replacementZoneIdsIfPolicyActive — 单 orgId 查询)**:
|
||||
```java
|
||||
public Optional<List<String>> replacementZoneIdsIfPolicyActive(String orgId) {
|
||||
try {
|
||||
Optional<TenantVisitorFloorPolicy> policy = findEnabledPolicyByOrgId(orgId);
|
||||
if (!policy.isPresent()) {
|
||||
log.debug("[POLICY-MISS] orgId={} no policy row", orgId); // ← 新增
|
||||
return Optional.empty();
|
||||
}
|
||||
List<String> allow = parseAllowZoneIds(policy.get().getAllowZoneIds());
|
||||
if (allow.isEmpty()) {
|
||||
log.info("[POLICY-MISS] orgId={} allow_zone_ids empty", orgId); // ← 新增
|
||||
return Optional.empty();
|
||||
}
|
||||
log.info("[POLICY-HIT] orgId={} policyId={} zones={} count={}", // ← 新增
|
||||
orgId, policy.get().getId(), allow, allow.size());
|
||||
return Optional.of(new ArrayList<>(allow));
|
||||
} catch (Exception e) {
|
||||
log.warn("[POLICY-ERR] orgId={} query failed, fallback: {}", // ← 修改
|
||||
orgId, e.toString());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**L39-57 (parseAllowZoneIds)**:
|
||||
```java
|
||||
public List<String> parseAllowZoneIds(String allowZoneIdsJson) {
|
||||
if (StringUtils.isBlank(allowZoneIdsJson)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
// ... 原有逻辑不变 ...
|
||||
} catch (Exception e) {
|
||||
log.warn("[POLICY-ERR] allow_zone_ids JSON invalid: {}", e.getMessage()); // ← 修改前缀
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. ImgPersonServiceImpl.java — 2 处修改
|
||||
|
||||
**L322-334 (listByPage — isVisitor 场景)**:
|
||||
```java
|
||||
if (policyZones.isPresent()) {
|
||||
log.info("[POLICY-LIST] hit personId={} orgIds={} zones={}", // ← 新增
|
||||
imgStorePersonResult.getId(),
|
||||
imgStorePersonResult.getOrganizationIds(),
|
||||
policyZones.get());
|
||||
List<AcsPassRuleImageResultDto> policyFloorInfo =
|
||||
buildFloorInfoListFromOrderedZoneIds(businessId, policyZones.get());
|
||||
// ... 原有逻辑不变 ...
|
||||
```
|
||||
|
||||
**L643-651 (detail — 详情场景)**:
|
||||
```java
|
||||
Optional<List<String>> replacementFloors =
|
||||
this.tenantVisitorFloorPolicyService.replacementZoneIdsIfPolicyActive(
|
||||
result.getOrganizationIds());
|
||||
if (replacementFloors.isPresent()) {
|
||||
log.info("[POLICY-DETAIL] hit personId={} orgIds={} zones={}", // ← 新增
|
||||
result.getId(),
|
||||
result.getOrganizationIds(),
|
||||
replacementFloors.get());
|
||||
floorList = new ArrayList<>(replacementFloors.get());
|
||||
// ... 原有逻辑不变 ...
|
||||
```
|
||||
|
||||
## 预期日志输出示例
|
||||
|
||||
```
|
||||
# 策略命中(detail 场景)
|
||||
[POLICY] entry orgIds=[605560545117995008, 605560541473144832]
|
||||
[POLICY-HIT] orgId=605560545117995008 policyId=gf_vstr_xxx zones=[zone_28f, zone_6f] count=2
|
||||
[POLICY-DETAIL] hit personId=1072908835884208128 orgIds=[605560545117995008, 605560541473144832] zones=[zone_28f, zone_6f]
|
||||
|
||||
# 策略未命中
|
||||
[POLICY] entry orgIds=[some_org_id]
|
||||
[POLICY-MISS] orgId=some_org_id no policy row
|
||||
[POLICY-MISS] no enabled policy for any org in [some_org_id]
|
||||
|
||||
# 策略命中(listByPage 场景)
|
||||
[POLICY] entry orgIds=[605560545117995008]
|
||||
[POLICY-HIT] orgId=605560545117995008 policyId=gf_vstr_xxx zones=[zone_28f, zone_6f] count=2
|
||||
[POLICY-LIST] hit personId=xxx orgIds=[605560545117995008] zones=[zone_28f, zone_6f]
|
||||
```
|
||||
+559
@@ -0,0 +1,559 @@
|
||||
# 租户访客默认楼层策略 — 迁入组织组件(组织侧唯一实现,电梯侧移除)
|
||||
|
||||
**日期**:2026-05-06
|
||||
**状态**:实施前梳理 / 待评审
|
||||
**核心原则**:策略逻辑**只在** `maven-ninca-common-component-organization` 实现;`maven-cw-elevator-application` **完全移除**策略相关代码。
|
||||
|
||||
**业务约定(楼层清单)**:凡 **调用方 / 前端 / BFF** 需消费 **「被访人可派梯 / 可邀约访问的楼层集合」**(`PersonResult.floorList` 语义),**必须**走 intelligent **`PersonService.detail` → `PersonResult.getFloorList()`**(服务端路由至组织 `ImgPersonServiceImpl#detail`)。**禁止**用 **`listByPage`** 或其它接口**顶替**该契约来充当邀约页或 UC-01 的楼层主数据源。**说明**:组织服务 **内部**仍会 **`ElevatorFeignClient.listByImageId`** 组装原始楼层,再写入 `floorList`——这是 **实现细节**,**不等于**调用方可绕过 `PersonService.detail` 直连上述内部调用。另见 **§2.3** 与 UC-02 边界。
|
||||
|
||||
**术语(强制)——「替代」与禁止「求交」**
|
||||
|
||||
| 用语 | 含义 |
|
||||
|------|------|
|
||||
| **替代(Replacement)** | 租户启用访客楼层策略时,**`allow_zone_ids` 整表替换**写入组织 `PersonService.detail` 返回的 **`PersonResult.floorList`**(及展示用 `floorNames` 等一致处理)。**不得**再将被访人 `listByImageId` 原始结果与 `allow_zone_ids` 做集合 **交集(∩)** 作为规范语义。 |
|
||||
| **禁止表述** | 在需求/产品/排障文档中,**禁止**将本策略称为「与 floorList 求交」「 candidate ∩ allow」;若需描述历史实现,须标明 **「历史·电梯侧过滤(已废弃)」**。 |
|
||||
| **电梯 `addVisitor`** | **只透传** UC-01 的 `personResult.floorList` 与 UC-02 的 `param.floorIds`;**不**再读 `tenant_visitor_floor_policy`、**不**做 ∩。 |
|
||||
|
||||
---
|
||||
|
||||
## 硬约束:对外接口不变
|
||||
|
||||
| 约束 | 说明 |
|
||||
|------|------|
|
||||
| **HTTP 路径与动词** | 不新增、不废弃、不更名组织/电梯现网已发布的 REST 路径;不调整鉴权与 Header 约定。 |
|
||||
| **请求/响应契约** | `cwos-component-organization-interface`、intelligent `PersonService` / `PersonResult` 等已对外暴露的 DTO 与 Feign 方法签名保持兼容:**不增删改字段、不增删方法**。 |
|
||||
| **允许变更范围** | **组织侧**:新建 `TenantVisitorFloorPolicyService`、修改 `ImgPersonServiceImpl#detail` 内部 floorList 组装逻辑。**电梯侧**:删除 `PersonRuleServiceImpl` 中的策略相关代码(阶段3),删除策略 DAO/Mapper/DTO。 |
|
||||
| **禁止** | 不在 `cwos-component-organization-interface` 中新增任何类/方法/字段。不在电梯侧新增对组织的 Feign 调用。 |
|
||||
| **floorList 唯一主路径** | 获取 **`PersonResult.floorList`**:**必须**调用 **`PersonService.detail`**(与 **`addVisitor` UC-01**、访客邀约初始化**同源**)。**不适用**于 **`addVisitor` UC-02 的楼层候选来源**(见 §2.1、§2.3)。禁止用分页接口替代 detail 来充当邀约/UC-01 的楼层主数据来源。 |
|
||||
|
||||
---
|
||||
|
||||
## 场景说明:访客邀请初始化、detail 与 listByPage
|
||||
|
||||
**规范表述**:**访客邀约 / 派梯(UC-01)所依据的 `floorList`,业务上只认 `PersonService.detail` 的返回**;组织侧在 `ImgPersonServiceImpl#detail` 内完成 `listByImageId` 与租户策略**替代**后写入同一字段,对外仍通过 **`PersonResult.floorList`** 消费。
|
||||
|
||||
本节约定**产品 / 前端 / BFF**与后端实现对齐用语;**不引入新接口**,楼层清单仍来自既有契约中的字段(如 `floorList`、`floorNames`、`floorInfoList` 等)。
|
||||
|
||||
### 1)访客邀请初始化页面需要「可访问楼层清单」
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **业务诉求** | 访客邀约/登记页在提交前,需要展示 **被访人侧允许访客选择的可达楼层**(或默认勾选逻辑所依赖的楼层集合),以便用户勾选或与派梯入口对齐。 |
|
||||
| **数据来源(规定主路径)** | **必须**通过 **`PersonService.detail`** 获取 **`PersonResult.floorList` / 相关展示字段**:**被访人 `personId` + `businessId`** → intelligent **`PersonService.detail`**(Feign 不变)→ 组织 **`ImgPersonServiceImpl#detail`** 内聚 **`listByImageId` + 租户策略替代** 后写入 **`floorList`**(见 §1「改造后」与 §4)。**禁止**将 **`listByPage`** 作为邀约页 **`floorList` 的规范来源**。 |
|
||||
| **与派梯一致** | 电梯 **`addVisitor` UC-01**(未传 `floorIds`)以同一 **`PersonResult.floorList`** 为候选楼层;邀约页若使用该清单,可与 UC-01 **同源**,减少「页面选的层 ≠ 后台派的层」。 |
|
||||
| **非本路径** | 邀约页若**仅**调人员分页、不调详情,则可能拿到 **另一套** 楼层展示(含星河湾 40F/6F 等),与 detail **不一定一致**——见下文 **3)** 与 §4.0。 |
|
||||
|
||||
### 2)`detail` 流程的作用
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **定位** | **单人维度的被访人详情**:组织侧 `ImgPersonServiceImpl#detail`(对外经 `/component/person/detail` 等**既有**入口,契约不变)。 |
|
||||
| **与楼层相关输出** | 在 **`listByImageId` 成功**时组装 **`floorList`(zoneId 列表)与 `floorNames`**;改造后在此链路插入 **租户访客策略替代**(命中则 **`floorList` 以策略 `allow_zone_ids` 为准**,见 §4.2)。 |
|
||||
| **明确不包含** | **不包含**星河湾分页里的 **40F / 6F 默认覆盖**(现网即如此);邀约页若只依赖 detail,则 **不会**从该接口拿到 XHW 那套默认楼层。 |
|
||||
| **典型调用方** | 访客邀请初始化、被访人卡片、电梯 **`addVisitor` 阶段 1** 拉 **`PersonResult`** 等——凡需要 **「这一位被访人当前可用的楼层清单」** 的场景,应以 **detail** 为主数据源(在无不新增接口前提下)。 |
|
||||
|
||||
### 3)`listByPage` 流程的作用
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **定位** | **人员列表分页**:组织侧 `ImgPersonServiceImpl#listByPage`,面向 **批量行** 展示;可选参数 **`isVisitor` 非空** 时进入访客列表增强分支(见 §3.2)。 |
|
||||
| **与楼层相关输出** | 行为 **`listByImageId`** 填 **`floorInfoList`**,再结合 **`OrgFloorMapper`**、**`xhwId` / `xhwDefaultFloorId` / `xhwSixFloorId`** 等写入 **默认选中楼层、跨日标记** 等(星河湾 **40F / 6F** 出现在此分支,见 §4.0)。 |
|
||||
| **与邀约初始化的关系** | 适用于 **访客名单列表、运营筛选** 等「一行一人摘要」场景;**不是**邀约页「为主访人拉可选楼层清单」的首选数据源——除非产品设计明确要求列表与邀约共用同一套展示逻辑,并接受与 **detail** 的差异或另行对齐(§7 测试项)。 |
|
||||
| **改造后** | 策略命中时应在 **L331–332 插入点**优先替代并 **跳过**原星河湾块(§4.3),避免与租户策略双重主编。 |
|
||||
|
||||
**小结**:**访客邀请初始化与 UC-01 派梯,均以 `PersonService.detail` 返回的 `PersonResult.floorList` 为权威楼层清单**(组织侧在 `ImgPersonServiceImpl#detail` 内完成策略替代);**`listByPage(isVisitor)`** 仅服务访客名单列表与项目定制默认层,**不作为**邀约页 `floorList` 的主数据路径。详情与分页并存时的差异见 §4.0。
|
||||
|
||||
---
|
||||
|
||||
## 访客邀约:完整业务流程图与代码流程图(改造后目标)
|
||||
|
||||
以下图表与仓库约定一致:**主数据访客登记**可能在第三方/BFF;**电梯 `addVisitor`** 仅负责「已有访客人员 ID 后的派梯授权」;策略在**组织库**、**替代**语义写入 **`detail` 的 `floorList`**。
|
||||
|
||||
### A. 业务视角 — 端到端(访客邀约 + 可选派梯)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph invite["访客邀约 / 登记(主流程)"]
|
||||
A1[打开访客邀约页] --> A2[选定被访人 hostPersonId]
|
||||
A2 --> A3[PersonService.detail<br/>取 PersonResult.floorList]
|
||||
A3 --> A4[渲染可选楼层 / 默认勾选逻辑]
|
||||
A4 --> A5[填写访客信息、访期等]
|
||||
A5 --> A6[提交邀约 — 访客档案落库]
|
||||
end
|
||||
subgraph elevator_domain["电梯域 — 派梯授权(可与邀约异步或分步)"]
|
||||
B1[触发派梯授权] --> B2[POST /elevator/person/add/visitor]
|
||||
B2 --> B3[依赖 PersonResult.floorList 或显式 floorIds]
|
||||
B3 --> B4[写通行规则引用 / 图库绑定]
|
||||
end
|
||||
A3 -.->|唯一规范源| A3note["PersonService.detail<br/>PersonResult.floorList<br/>与 addVisitor UC-01 同源"]
|
||||
A6 -.->|可选后续| B1
|
||||
```
|
||||
|
||||
**说明**:邀约页 **A3** **必须**通过 **`PersonService.detail` → `PersonResult.floorList`** 拉楼层清单(组织侧 `ImgPersonServiceImpl#detail` 实现);**禁止**用 **`listByPage(isVisitor)`** 替代 **A3** 作为 **`floorList` 规范来源**(见场景说明 **3)**)。
|
||||
|
||||
### B. 代码视角 — 邀约初始化:拉「可访问楼层清单」(**唯一规范:`PersonService.detail` → `floorList`**)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant FE as 前端 / BFF
|
||||
participant PS as PersonService<br/>Intelligent Feign
|
||||
participant OC as PersonController<br/>/component/person/detail
|
||||
participant IM as ImgPersonServiceImpl#detail
|
||||
participant EF as ElevatorFeignClient<br/>listByImageId
|
||||
participant TP as TenantVisitorFloorPolicyService<br/>组织库
|
||||
|
||||
FE->>PS: detail(personId, businessId)
|
||||
PS->>OC: HTTP POST(契约不变)
|
||||
OC->>IM: detail(param, context)
|
||||
IM->>IM: selectByPrimaryKey、getImgStorePersonResults
|
||||
IM->>EF: listByImageId(AcsPassRuleImageForm)
|
||||
EF-->>IM: List AcsPassRuleImageResultDto(原始通行楼层)
|
||||
IM->>TP: isEnabled(organizationIds)
|
||||
TP-->>IM: true / false
|
||||
alt 策略启用
|
||||
IM->>TP: getAllowZoneIds(organizationIds)
|
||||
TP-->>IM: allow_zone_ids 列表
|
||||
IM->>IM: floorList = 策略替代结果
|
||||
else 策略未启用
|
||||
IM->>IM: floorList = 遍历 images 的 zoneId(现网 L613-626 语义)
|
||||
end
|
||||
IM->>IM: setFloorList / setFloorNames
|
||||
IM-->>OC: ImgStorePersonGetResult
|
||||
OC-->>PS: CloudwalkResult
|
||||
PS-->>FE: PersonResult / 映射后 floorList
|
||||
```
|
||||
|
||||
**落点**:组织 **`cwos-component-organization-service`** · **`ImgPersonServiceImpl`**;行号以 §3.1 为准(插入点在 **`listByImageId` 成功块内**)。
|
||||
|
||||
### C. 代码视角 — 派梯授权:`addVisitor`(电梯,改造后)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
START([AcsPersonController<br/>POST /elevator/person/add/visitor]) --> P1[PersonRuleServiceImpl.addVisitor]
|
||||
P1 --> D1[PersonService.detail<br/>PersonResult.floorList]
|
||||
D1 --> D2{param.floorIds<br/>非空?}
|
||||
D2 -->|UC-02 是| E1[effective = param.floorIds]
|
||||
D2 -->|UC-01 否| E2[effective = personResult.floorList<br/>组织 detail 已含策略替代]
|
||||
E1 --> V{effective<br/>为空?}
|
||||
E2 --> V
|
||||
V -->|是| FAIL[失败 76260531 等]
|
||||
V -->|否| P2[param.setFloorIds effective]
|
||||
P2 --> Z1[zoneService.page 首楼层]
|
||||
Z1 --> Z2[deviceImageStoreDao<br/>imageStoreId]
|
||||
Z2 --> R1[按楼层写 ImageRuleRef]
|
||||
R1 --> B1[imageStorePersonService.batchBind<br/>访期]
|
||||
B1 --> G1[updateGroupPersonRef]
|
||||
G1 --> OK([返回成功])
|
||||
```
|
||||
|
||||
**要点**:改造后 **无** `TenantVisitorFloorPolicyDao`、**无** `candidate ∩ allow`;**UC-01** 完全信任 **组织侧写入的 `floorList`**。
|
||||
|
||||
### D. 代码视角 — 访客名单分页:`listByPage`(`isVisitor` 非空)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
LP([listByPage]) --> Q[PageHelper + imgStorePersonMapper.gets]
|
||||
Q --> ENRICH[getImgStorePersonResults<br/>组织/用户姓名等]
|
||||
ENRICH --> IV{param.isVisitor<br/>非空?}
|
||||
IV -->|否| OUT1([直接分页返回])
|
||||
IV -->|是| LOOP[逐行:listByImageId]
|
||||
LOOP --> POL{TenantVisitorFloorPolicyService<br/>isEnabled?}
|
||||
POL -->|是| POLSET[替代 floorInfoList / 默认楼层<br/>跳过星河湾块]
|
||||
POL -->|否| DF[setDefaultChooseFloor defaultFloor]
|
||||
DF --> OF[OrgFloorMapper.listByOrgIds]
|
||||
OF --> EMPTY{orgFloorList<br/>为空?}
|
||||
EMPTY -->|是| AD[setIsAcrossDay=1<br/>无 40/6 覆盖]
|
||||
EMPTY -->|否| XHW{xhwId in<br/>organizationIds?}
|
||||
XHW -->|是| F40[40F 默认 + floorInfoList]
|
||||
XHW -->|否| F6[6F 默认 + floorInfoList]
|
||||
POLSET --> FILTER[过滤标签含访客等<br/>现网逻辑]
|
||||
F40 --> FILTER
|
||||
F6 --> FILTER
|
||||
AD --> FILTER
|
||||
FILTER --> OUT2([CloudwalkPageAble 返回])
|
||||
```
|
||||
|
||||
**要点**:**邀约初始化不要依赖本分支**作为主楼层清单;本图仅供列表页与 §4 优先级对照。
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据流 — 改造前 vs 改造后
|
||||
|
||||
### 改造前(历史·已废弃)
|
||||
```
|
||||
addVisitor(floorIds)
|
||||
→ personService.detail → PersonResult.floorList (listByImageId)
|
||||
→ if UC-02: candidate = floorIds
|
||||
→ if UC-01: candidate = floorList
|
||||
→ 【电梯侧查策略表】findPolicyByOrgIds
|
||||
→ 【错误语义·已废弃】candidate 与 allow_zone_ids 做 ∩ 过滤
|
||||
→ 派梯
|
||||
```
|
||||
|
||||
### 改造后(当前规范)
|
||||
```
|
||||
【访客邀约页 / UC-01 共用】PersonService.detail → PersonResult.floorList
|
||||
(组织 ImgPersonServiceImpl#detail:listByImageId 后,策略命中则以 allow_zone_ids **替代** floorList)
|
||||
|
||||
addVisitor(floorIds)
|
||||
→ personService.detail → PersonResult.floorList ← 与邀约页同一契约来源
|
||||
→ if UC-02: effective = floorIds
|
||||
→ if UC-01: effective = floorList
|
||||
→ effective = 上式(电梯侧 **无** 策略表、**无** ∩)
|
||||
→ 派梯
|
||||
```
|
||||
|
||||
### 关键变化
|
||||
| 步骤 | 改造前(历史) | 改造后(规范) |
|
||||
|------|--------|--------|
|
||||
| 策略权威 | 电梯库读表 + **∩ 收窄**(错误表述:「求交」) | **组织库 / 组织 detail**:策略命中则 **`floorList` = `allow_zone_ids`(仅替代)** |
|
||||
| **`PersonService.detail` → floorList** | 主要为 `listByImageId` 原始楼层 | **策略命中时 = allow_zone_ids(替代)**;未命中 = 原始遍历结果 |
|
||||
| **`addVisitor`(电梯)** | 查电梯库策略 + **∩** | **删除策略运算**:仅透传 detail / 请求 |
|
||||
| **effective:UC-01** | 曾再度与 allow **∩** | **effective = `personResult.floorList`**(组织已替代,电梯不二次运算) |
|
||||
| **effective:UC-02** | 曾与 allow **∩**(若走策略分支) | **effective = `param.getFloorIds`**(显式楼层;规范上不由电梯做 ∩) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 业务语义
|
||||
|
||||
### 2.1 UC-01 与 UC-02:业务场景说明(电梯「访客派梯授权」接口)
|
||||
|
||||
以下针对 **`POST /elevator/person/add/visitor`**(`PersonRuleServiceImpl#addVisitor`)中 **`floorIds` 是否由调用方传入** 的两种业务模式;与 **访客邀约页拉楼层清单**(依赖 **`PersonService.detail`**)的关系见 **§2.3**。
|
||||
|
||||
| 代号 | 接口条件(代码侧) | 业务含义 | 典型场景举例 |
|
||||
|------|-------------------|----------|--------------|
|
||||
| **UC-01** | 请求体 **未传** `floorIds`,或传 **空列表**(以最终实现判定为准) | **由系统依据被访人维度推导要开通的楼层**:调用方不显式指定「开哪几层」,派梯授权使用的楼层集合应与 **被访人详情中的可达楼层清单**一致(经租户策略在组织侧写入 **`PersonResult.floorList`**)。 | ① 登记完成后 **一键开通派梯**,前台未做逐层勾选;② BFF 只带 `personId`/`visitorId`/访期,**楼层完全跟被访人档案 + 租户策略**走;③ 与 **访客邀约初始化页**使用同一 **`PersonService.detail` → `floorList`** 对齐 UC-01,避免「页面以为的层」与「后台开通的层」不一致。 |
|
||||
| **UC-02** | 请求体 **`floorIds` 非空** | **由调用方明确指定要开通的楼层(zoneId 列表)**:业务上多为用户或上游系统 **已选定具体楼层**,派梯接口按 **显式列表** 写入通行规则,而 **不以** `PersonResult.floorList` 作为开通列表来源。 | ① 接待岗在终端 **勾选 7F、8F** 后提交派梯;② 第三方访客系统 **合同只允许指定楼层**;③ 邀约页提交时 **把用户勾选的楼层原样** 传给派梯(此时开通列表以请求为准,**不等于**邀约页展示用的 **`detail.floorList` 必须相同**,取决于产品设计)。 |
|
||||
|
||||
**一句话对照**:**UC-01 =「开通哪些层」交给系统(跟被访人 detail + 策略后的 floorList)**;**UC-02 =「开通哪些层」由调用方在请求里写死(floorIds)**。
|
||||
|
||||
**与邀约页的关系**:邀约页展示 **可选楼层** 仍 **规定**走 **`PersonService.detail`**(§「业务约定」);用户若在邀约或后续页面 **勾选了具体楼层** 再派梯,对接侧通常走 **UC-02**;若 **未勾选**、由后台直接派梯,则多为 **UC-01**。
|
||||
|
||||
---
|
||||
|
||||
### 2.2 唯一规范语义:替代(Replacement);禁止「求交」作为策略定义
|
||||
|
||||
| | 禁止(历史·电梯侧 ∩) | 唯一规范(组织 detail·替代) |
|
||||
|------|------|------|
|
||||
| 逻辑 | ~~`candidate` ∩ `allow_zone_ids`~~ **不得**再写进需求/验收的主路径 | 策略命中时 **`floorList` := `allow_zone_ids`**(整表替换,**非**与原楼层求交) |
|
||||
| 实现位置 | ~~`PersonRuleServiceImpl` 阶段 3~~ **已移除** | `ImgPersonServiceImpl#detail`(待接入 `TenantVisitorFloorPolicyService` 时在此 **替代**) |
|
||||
| 策略命中时含义 | ~~「收窄候选」~~(易误解为 ∩) | **`PersonResult.floorList` 完全由策略列表定义**;未命中策略时才保留 `listByImageId` 遍历结果 |
|
||||
|
||||
**核心语义**:租户访客楼层策略 **只能是替代**,**不是**与被访人电梯原始授权楼层 **求交**。
|
||||
|
||||
### 2.3 `addVisitor` 实现要点:UC-02 与「必须 detail」的边界(代码与契约)
|
||||
|
||||
| 路径 | 是否必须 `PersonService.detail` | 说明 |
|
||||
|------|----------------------------------|------|
|
||||
| **访客邀约页初始化 / UC-01 派梯** | **是** | 楼层权威清单 = **`PersonService.detail` → `floorList`**(§「业务约定」)。 |
|
||||
| **`addVisitor` UC-02** | **仍调用** `personService.detail`(阶段 1 校验被访人存在等),但 **effective 楼层**仅取自 **`param.getFloorIds()`**,**不**采用 `personResult.getFloorList()` | 用于 BFF/调用方**已替用户选定楼层**的派梯;**不是**邀约页拉清单的主路径。 |
|
||||
|
||||
电梯侧 `addVisitor` 生效楼层计算(**无** `TenantVisitorFloorPolicyDao`、**无** ∩)如下:
|
||||
|
||||
```java
|
||||
// 改造后 addVisitor:阶段 2
|
||||
List<String> effective;
|
||||
if (!CollectionUtils.isEmpty(param.getFloorIds())) {
|
||||
effective = param.getFloorIds(); // UC-02 — 显式楼层
|
||||
} else {
|
||||
effective = personResult.getFloorList(); // UC-01 — 与邀约页同源,来自 detail
|
||||
}
|
||||
// 无 TenantVisitorFloorPolicyDao;effective 直接用于后续派梯
|
||||
```
|
||||
|
||||
**避免误读**:上文「UC-02 不经 detail 的 floorList」仅指 **候选生效楼层字段**不取自 `PersonResult.floorList`;**阶段 1 的 `detail` 调用仍可能存在**(取被访人元数据)。若产品要求 **显式 floorIds 也必须被租户策略约束**,属**新需求**,与本篇「对外接口不变」前提冲突时需另案评审。
|
||||
|
||||
---
|
||||
|
||||
## 3. 改造后业务时序图(含代码行号对照)
|
||||
|
||||
### 3.1 组织侧 `detail` 内策略插入 + 电梯 `addVisitor` 消费(对照)
|
||||
|
||||
> 下图左侧为 **`addVisitor` 内触发 `detail`** 的上下文;**楼层写入**发生在组织 **`ImgPersonServiceImpl#detail`**(策略插入点见 §3.3)。
|
||||
|
||||
```
|
||||
Elevator Intelligent Component-Org Component-Org
|
||||
PersonRuleImpl PersonService ImgPersonServiceImpl TenantVisitorPolicyService
|
||||
(after removal) (Feign, unchanged) (L569 detail方法) (新建)
|
||||
|
||||
│ addVisitor() │ │ │
|
||||
│──────────────────────────>│ │ │
|
||||
│ │ detail(param) │ │
|
||||
│ │───────────────────>│ │
|
||||
│ │ │ │
|
||||
│ │ │ ① selectByPrimaryKey L577 │
|
||||
│ │ │ ② getVehicleIds L598 │
|
||||
│ │ │ │
|
||||
│ │ │ ③ elevatorFeignClient │
|
||||
│ │ │ .listByImageId() L611 │
|
||||
│ │ │ ← images (原始通行楼层) │
|
||||
│ │ │ │
|
||||
│ │ │ ╔═══════════════════════╗ │
|
||||
│ │ │ ║ ★ 插入点 (L612之后) ║ │
|
||||
│ │ │ ╚═══════════════════════╝ │
|
||||
│ │ │ │
|
||||
│ │ │ isEnabled(orgIds) │
|
||||
│ │ │ ─────────────────────────────>│
|
||||
│ │ │ ← true / false │
|
||||
│ │ │ │
|
||||
│ │ │ if true: │
|
||||
│ │ │ getAllowZoneIds(orgIds) │
|
||||
│ │ │ ─────────────────────────────>│
|
||||
│ │ │ ← [zone1, zone2, ...] │
|
||||
│ │ │ floorList = 策略结果 │
|
||||
│ │ │ │
|
||||
│ │ │ if false: │
|
||||
│ │ │ floorList = L613-626 │
|
||||
│ │ │ (原始images遍历组装) │
|
||||
│ │ │ │
|
||||
│ │ │ ④ setFloorList L628-629 │
|
||||
│ │ │ │
|
||||
│ │ ← PersonResult │ │
|
||||
│ │ .floorList │ │
|
||||
│ │ (已含策略替代) │ │
|
||||
│ │ │ │
|
||||
│ ← PersonResult │ │ │
|
||||
│ UC-02: effective=param.floorIds │ │
|
||||
│ 否则: effective=personResult.floorList │ │
|
||||
│ (组织 detail 已替代;电梯不 ∩ allow) │ │
|
||||
│ 派梯 │ │
|
||||
▼ ▼ ▼ ▼
|
||||
```
|
||||
|
||||
### 3.2 listByPage() 中的插入点(isVisitor 场景,L319-358)
|
||||
|
||||
```
|
||||
ImgPersonServiceImpl#listByPage
|
||||
│
|
||||
├─ L330: setFloorInfoList(images.getData()) ← listByImageId 原始结果
|
||||
│
|
||||
│ ╔═══════════════════════════════════════╗
|
||||
│ ║ ★ 插入点 (L331-332 之间) ║
|
||||
│ ╚═══════════════════════════════════════╝
|
||||
│ │
|
||||
│ │ if tenantVisitorFloorPolicyService.isEnabled(orgIds):
|
||||
│ │ floorInfoList = 策略 allow_zone_ids
|
||||
│ │ setFloorInfoList(floorInfoList)
|
||||
│ │ setDefaultChooseFloor(策略默认楼层)
|
||||
│ │ continue ← 跳过 XHW 分支 (L332-357)
|
||||
│ │
|
||||
│ ▼ (策略未命中时,继续原有逻辑)
|
||||
│
|
||||
├─ L332: setDefaultChooseFloor(defaultFloor) ← 原逻辑
|
||||
├─ L334: orgFloorMapper.listByOrgIds(orgIds) ← 原逻辑
|
||||
├─ L340: if xhwId → setDefaultChooseFloor(40F) ← 星河湾分支
|
||||
└─ L349: else → setDefaultChooseFloor(6F) ← 星河湾分支
|
||||
```
|
||||
|
||||
### 3.3 代码插入点与行号对照
|
||||
|
||||
| 方法 | 插入位置(与 §3.1 文字一致) | 上下文 | 变量来源 |
|
||||
|------|------------------------------|--------|---------|
|
||||
| `detail()` | **`images.getCode()` 为成功码、`floorList` 组装循环之前**(文中曾写 L612–613 间,以当前 `ImgPersonServiceImpl` 为准) | `if (Objects.equals(images.getCode(), "00000000"))` 块内 | `result.getOrganizationIds()` |
|
||||
| `listByPage()` | **`setFloorInfoList(images.getData())` 之后、`setDefaultChooseFloor` 之前**(约 L331–332) | 访客分支内逐行处理 | `imgStorePersonResult.getOrganizationIds()` |
|
||||
|
||||
### 3.4 策略 Service 接口定义
|
||||
|
||||
```java
|
||||
// 新建:cn.cloudwalk.service.organization.service.visitorpolicy.TenantVisitorFloorPolicyService
|
||||
public class TenantVisitorFloorPolicyService {
|
||||
|
||||
// 查询是否存在启用策略(任一 orgId 命中即返回 true)
|
||||
public boolean isEnabled(List<String> orgIds);
|
||||
|
||||
// 返回解析后的 allow_zone_ids(JSON Array → List<String>)
|
||||
public List<String> getAllowZoneIds(List<String> orgIds);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 与星河湾并存:适用范围、优先级与伪代码
|
||||
|
||||
### 4.0 现网代码事实(走查结论,避免误实现)
|
||||
|
||||
| 方法 | 星河湾 40F / 6F(`xhwDefaultFloorId` / `xhwSixFloorId`) | 租户访客策略(拟插入) |
|
||||
|------|----------------|----------------|
|
||||
| **`ImgPersonServiceImpl#detail`** | **未实现**:仅 `listByImageId` → `floorList` / `floorNames`(见 §3.1) | 仅在 **`floorList` 组装处**做 **P1 策略替代**;**不要**在本方法内照搬 `listByPage` 的 XHW 分支,除非产品明确要求「详情与访客分页默认完全一致」(属**行为变更**,需单独评审)。 |
|
||||
| **`ImgPersonServiceImpl#listByPage`**(`param.getIsVisitor()` 非空时) | **已实现**:在 `OrgFloorMapper.listByOrgIds` 非空且 `isAcrossDay=0` 时,按是否含 `xhwId` 覆盖 `defaultChooseFloor` 与 `floorInfoList`(40F vs 6F)(见 §3.2) | 在 **L331–332 之间**插入:若 **P1 策略命中**,则用策略结果替换 `floorInfoList` / 默认楼层并 **跳过** 原 XHW 块(L332–357);若未命中则保持现有星河湾逻辑。 |
|
||||
|
||||
**详情 vs 分页的一致性**:改造前已存在「**detail 无 XHW、分页访客分支有 XHW**」差异;若在 **detail** 仅加 **策略替代**、在 **listByPage** 加 **策略优先于 XHW**,可能出现「同一被访人在详情 `floorList` 与分页默认展示仍不完全一致」。若需对齐,应在产品层明确是否要给 **`detail` 增加与分页相同的默认楼层规则**(接口字段不变,仅内部赋值变化)。
|
||||
|
||||
### 4.1 优先级总表(与现网 XHW 不冲突的前提)
|
||||
|
||||
| 优先级 | 条件 | 行为 |
|
||||
|--------|------|------|
|
||||
| **P1** | 租户访客策略命中且启用 | **替代**:① **`PersonService.detail` → `floorList`** = **`allow_zone_ids`**;② **`listByPage` 访客行**上用于展示的 `floorInfoList`/默认层(见 §4.3)。**访客邀约页只吃 ①**,不吃 ②。 |
|
||||
| **P2** | 未命中策略,且走 **`listByPage` 访客分支** 且满足现网 XHW 前置条件(含 `orgFloorList` 非空等) | 保持现有 **40F / 6F** 逻辑(`xhwId` vs `xhwSixFloorId`)。 |
|
||||
| **P3** | 兜底 | **`listByImageId`** 原始结果;**detail** 路径下无 P2(因现网无 XHW)。 |
|
||||
|
||||
### 4.2 伪代码:`detail()` — 仅 P1 与 P3(不要写入 XHW else-if)
|
||||
|
||||
```java
|
||||
// ImgPersonServiceImpl#detail — listByImageId 已成功返回后、setFloorList 之前(参见 §3.1 插入点)
|
||||
List<String> orgIds = result.getOrganizationIds();
|
||||
if (tenantVisitorFloorPolicyService.isEnabled(orgIds)) {
|
||||
// P1:策略替代(PersonResult / 映射链最终消费的 floorList 与此一致)
|
||||
List<String> allow = tenantVisitorFloorPolicyService.getAllowZoneIds(orgIds);
|
||||
floorList = allow; // 另需同步 floorNames 等与契约一致的展示字段,实现自行拆解 zoneId→名称若需要
|
||||
} else {
|
||||
// P3:与现网一致 — 保留 listByImageId 遍历结果(L613-626)
|
||||
// 此处不要添加 else if (xhwId) — 现网 detail 本无星河湾分支
|
||||
}
|
||||
result.setFloorList(floorList);
|
||||
```
|
||||
|
||||
### 4.3 伪代码:`listByPage` 访客分支 — P1 跳过 XHW(P2)
|
||||
|
||||
```java
|
||||
// 在 setFloorInfoList(images.getData()) 之后、setDefaultChooseFloor 之前(§3.2 L331-332 间)
|
||||
if (tenantVisitorFloorPolicyService.isEnabled(orgIds)) {
|
||||
// P1:用策略替代楼层展示;跳过下方 OrgFloor / 星河湾 40F/6F
|
||||
applyPolicyToFloorInfoListAndDefault(imgStorePersonResult, orgIds);
|
||||
continue; // 或等价结构,避免进入 L332-357 原 XHW 逻辑
|
||||
}
|
||||
// 未命中策略:保持现网顺序 — L332 起 defaultFloor、OrgFloor、xhwId→40F / else→6F
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 组织组件 — 新增/修改清单
|
||||
|
||||
### 5.1 数据层(`cwos-component-organization-data`)
|
||||
|
||||
| 工作项 | 详情 |
|
||||
|--------|------|
|
||||
| DDL | 从 `releases/cw-elevator-application-V2.0.20.20260505/ddl/tenant_visitor_floor_policy.sql` 迁移到 `docs/sql/tenant_visitor_floor_policy.sql` |
|
||||
| Entity | `cn.cloudwalk.data.organization.entity.TenantVisitorFloorPolicy` |
|
||||
| Mapper | `cn.cloudwalk.data.organization.mapper.TenantVisitorFloorPolicyMapper.java` + XML |
|
||||
| 数据迁移 SQL | 从电梯库 `cwo_elevator_db.tenant_visitor_floor_policy` → 组织库(一次性,含回滚) |
|
||||
|
||||
### 5.2 服务层(`cwos-component-organization-service`)
|
||||
|
||||
| 工作项 | 详情 |
|
||||
|--------|------|
|
||||
| **新建** | `TenantVisitorFloorPolicyService` |
|
||||
| 方法 | `boolean isEnabled(List<String> orgIds)` — 是否存在启用策略 |
|
||||
| 方法 | `List<String> getAllowZoneIds(List<String> orgIds)` — 返回 allow_zone_ids(已解析 JSON) |
|
||||
| **修改** | `ImgPersonServiceImpl#detail` — floorList 组装处插入 **§4.2**(P1/P3,无 XHW) |
|
||||
| **修改** | `ImgPersonServiceImpl` 中星河湾分支 — 策略命中时跳过 xhwDefaultFloorId/xhwSixFloorId 覆盖 |
|
||||
|
||||
### 5.3 接口/Web 层
|
||||
|
||||
| 模块 | 操作 |
|
||||
|------|------|
|
||||
| `cwos-component-organization-interface` | **零变更** |
|
||||
| `cwos-component-organization-web` | 仅更新 `PersonController.java` 类注释 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 电梯应用 — 完整删除清单
|
||||
|
||||
### 6.1 删除文件清单
|
||||
|
||||
| 文件 | 路径 |
|
||||
|------|------|
|
||||
| TenantVisitorFloorPolicyDao.java | `cw-elevator-application-data/.../person/dao/` |
|
||||
| TenantVisitorFloorPolicyDaoImpl.java | `cw-elevator-application-data/.../person/impl/` |
|
||||
| TenantVisitorFloorPolicyMapper.java | `cw-elevator-application-data/.../person/mapper/` |
|
||||
| TenantVisitorFloorPolicyMapper.xml | `cw-elevator-application-data/src/main/resources/mapper/` |
|
||||
| TenantVisitorFloorPolicyDto.java | `cw-elevator-application-data/.../person/dto/` |
|
||||
| DDL 文件 | `releases/*/ddl/tenant_visitor_floor_policy*.sql`(保留历史参考或删除) |
|
||||
|
||||
### 6.2 修改代码清单
|
||||
|
||||
| 文件 | 行号 | 操作 |
|
||||
|------|------|------|
|
||||
| `PersonRuleServiceImpl.java` | L32-33 | 删除 import `TenantVisitorFloorPolicyDao` / `TenantVisitorFloorPolicyDto` |
|
||||
| `PersonRuleServiceImpl.java` | L83 | 删除 `@Autowired TenantVisitorFloorPolicyDao` |
|
||||
| `PersonRuleServiceImpl.java` | L211-230 | **删除整个阶段3**(查策略+求交逻辑) |
|
||||
| `PersonRuleServiceImpl.java` | L232-238 | 修改阶段4:`effective` 改为 `candidate` |
|
||||
| `PersonRuleServiceImpl.java` | L297-353 | 删除 `findPolicyByOrgIds()` 和 `parseAllowZoneIds()` 私有方法 |
|
||||
| `08-visitor-registration-and-elevator-auth.md` | — | 更新文档:"租户策略求交" → "组织侧策略替代" |
|
||||
|
||||
### 6.3 改造后 addVisitor 精简代码
|
||||
|
||||
```java
|
||||
public CloudwalkResult<Boolean> addVisitor(AcsPersonAddVisitorParam param, CloudwalkCallContext context) {
|
||||
// 阶段1:查询被访人(含组织信息 + 人行规楼层)
|
||||
PersonDetailParam detailParam = new PersonDetailParam();
|
||||
detailParam.setId(param.getPersonId());
|
||||
detailParam.setBusinessId(context.getCompany().getCompanyId());
|
||||
CloudwalkResult<PersonResult> detailResult = this.personService.detail(detailParam, context);
|
||||
// ... 错误检查 ...
|
||||
|
||||
PersonResult personResult = (PersonResult) detailResult.getData();
|
||||
|
||||
// 阶段2:确定生效楼层(直接使用 candidate,不做交集)
|
||||
List<String> effective;
|
||||
if (!CollectionUtils.isEmpty(param.getFloorIds())) {
|
||||
effective = param.getFloorIds(); // UC-02
|
||||
} else {
|
||||
effective = personResult.getFloorList(); // UC-01,组织侧已含策略
|
||||
if (CollectionUtils.isEmpty(effective)) {
|
||||
return CloudwalkResult.fail("76260531", getMessage("76260531"));
|
||||
}
|
||||
}
|
||||
|
||||
// 阶段3:空集校验 + 派梯
|
||||
param.setFloorIds(effective);
|
||||
// ... 后续派梯逻辑不变 ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试清单
|
||||
|
||||
- [ ] **业务路径**:访客邀约 / 楼层初始化 **仅**通过 **`PersonService.detail` → `PersonResult.floorList`** 获取权威清单(未用分页或其它接口顶替)
|
||||
- [ ] **有策略租户**:`detail` 返回的 `floorList` = `allow_zone_ids`(纯替代,非交集)
|
||||
- [ ] **有策略 + addVisitor**:effective = floorList(不含交集过滤)
|
||||
- [ ] **无策略 + 星河湾**:行为与现网 40F/6F 一致
|
||||
- [ ] **有策略 + 星河湾同时配置**:策略优先,不触发 xhw 覆盖
|
||||
- [ ] **无策略时 detail vs 分页**:确认是否接受「detail 仍无 XHW、分页访客分支仍有 40F/6F」的现网差异;若产品要求一致,需另立 story(见 §4.0)
|
||||
- [ ] **UC-02(派梯接口)**:请求体传 `floorIds` → `effective` 以请求为准(与邀约页「必须 detail」并行不悖:邀约仍只认 detail;业务含义见 §2.1,边界见 §2.3)
|
||||
- [ ] **数据迁移**:行数一致、org_id/business_id 正确
|
||||
- [ ] **接口回归**:组织/电梯/智能组件对外 API 无路径/字段/方法变更
|
||||
- [ ] **电梯回退**:若组织侧部署失败,电梯仍可回退到旧版(DDL 保留期间)
|
||||
|
||||
---
|
||||
|
||||
## 8. 实施步骤
|
||||
|
||||
| 步骤 | 内容 | 验证方式 |
|
||||
|------|------|---------|
|
||||
| 1 | 组织 data 模块:建表 DDL + Entity + Mapper | `mvn compile` data 模块通过 |
|
||||
| 2 | 组织 service 模块:新建 TenantVisitorFloorPolicyService | 单元测试 |
|
||||
| 3 | 组织 service 模块:修改 `ImgPersonServiceImpl#detail` 与 `listByPage` 访客分支(§4.2 / §4.3) | 集成测试 |
|
||||
| 4 | 数据迁移:电梯库 → 组织库 | 行数对比 |
|
||||
| 5 | 电梯侧:删除策略相关代码(§6.1 + §6.2) | `mvn compile` 通过 |
|
||||
| 6 | 端到端:addVisitor + detail 行为验证 | API 对拍测试 |
|
||||
| 7 | 发布:先发组织侧,观察后发电梯侧(兼容窗口) | 监控 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 参考
|
||||
|
||||
- 电梯访客文档:`maven-cw-elevator-application/cw-elevator-application-service/docs/08-visitor-registration-and-elevator-auth.md`
|
||||
- 数据模型:`docs/architecture/租户组织人员访客-数据模型与用例.md`
|
||||
- 历史设计:`docs/business/租户访客默认楼层-数据库配置阶段技术设计.md`
|
||||
- 当前策略代码:`PersonRuleServiceImpl.java` L211-230, L297-353
|
||||
- 星河湾分支:`ImgPersonServiceImpl.java` L149-354
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| 0.1 | 2026-05-06 | 初稿 |
|
||||
| 0.2 | 2026-05-06 | 增加硬约束 |
|
||||
| 0.3 | 2026-05-06 | **重构**:明确"组织侧唯一实现,电梯侧完全移除";增加数据流对比图、删除清单、伪代码 |
|
||||
| 0.4 | 2026-05-06 | **增加**:业务时序图(含代码行号对照)、detail/listByPage 插入点、策略 Service 接口定义 |
|
||||
| 0.5 | 2026-05-06 | **修订**:§4 拆分 detail(仅 P1/P3,不含 XHW)与 listByPage(P1 跳过星河湾);补充现网 XHW 适用范围表;修正重复「## 4」章节编号(组织清单改为 §5,电梯 §6,顺延);标注 detail/分页一致性风险 |
|
||||
| 0.6 | 2026-05-06 | **增加**:文首「场景说明」— 访客邀请初始化与楼层清单、`detail` / `listByPage` 职责划分及与 UC-01 对齐说明 |
|
||||
| 0.7 | 2026-05-06 | **增加**:访客邀约端到端业务流图;代码侧 sequence(detail+策略)、flowchart(addVisitor 改造后、listByPage isVisitor);修正 §1 笔误「侧略」→「策略」 |
|
||||
| 0.8 | 2026-05-06 | **明确**:业务楼层清单 **必须**走 **`PersonService.detail` → `PersonResult.floorList`**;硬约束与场景说明升级为「规定主路径」;流程图 A3/B 标题与 §1 数据流对齐 |
|
||||
| 0.9 | 2026-05-06 | **冲突清理**:区分对外契约 vs 组织内 `listByImageId`;§1 关键变化表拆分 UC-01/UC-02;§2.2 与「必须 detail」边界表;§2.1 交集/替代表述与改造目标对齐;§4.1 P1 区分邀约只吃 detail;§3.1 标题与 §3.3 插入点表述统一;§7 UC-02 测试条注释 |
|
||||
| 1.0 | 2026-05-06 | **增加**:§2.1 UC-01/UC-02 **业务场景说明**(非仅代码条件);原 §2.1/2.2 顺延为 §2.2/2.3;更新文首与 §7 对 §2 的引用 |
|
||||
@@ -0,0 +1,252 @@
|
||||
# 目录结构重组设计 — 星河湾星中星
|
||||
|
||||
**日期:** 2026-05-07
|
||||
**状态:** 设计稿 v1
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
星河湾星中星是 CloudWalk(云从科技)电梯/门禁/安防/人脸识别系统的单体仓库。当前顶层目录结构存在以下问题:
|
||||
|
||||
- **职责混淆:** `源码/` 中混合了 Maven 项目、运行时部署包、nginx 配置、反编译代码、frontend 运行时 + 备份
|
||||
- **命名不一致:** 混用中文/英文、小写/下划线/连词符、`maven-` 前缀冗余、`01-` 数字前缀无意义
|
||||
- **目录重复:** `源码/frontend/` 与 `星中心/frontend/` 内容高度重叠;`源码/cw-elevator-application-V1.0.0.20211103/` 与 `星中心/` 下同名
|
||||
- **自嵌套:** `源码/源码/` 指向自身
|
||||
- **备份混乱:** 15+ 个 `.bakYYYYMMDD` 目录与活跃目录混合,难以区分
|
||||
|
||||
**设计原则:**
|
||||
|
||||
1. **单一职责** — 每个顶层目录只放一种类型的内容
|
||||
2. **零删除** — 所有历史/遗留文件集中移至 `archive/`,不做任何删除
|
||||
3. **命名规范** — 小写 kebab-case、去除冗余前缀、统一版本/备份格式
|
||||
4. **最小中断** — 保持开发/部署工作流不受影响
|
||||
|
||||
---
|
||||
|
||||
## 2. 命名规范
|
||||
|
||||
所有新路径遵循以下规则:
|
||||
|
||||
| 规则 | 旧示例 | 新示例 |
|
||||
|------|--------|--------|
|
||||
| 目录名统一小写+连词符 | `data_backup/` | `data-backups/` |
|
||||
| 去除 `maven-` 前缀 | `maven-cw-elevator-application/` | `cw-elevator-application/` |
|
||||
| 去除 `01-` / `_01-` 数字前缀 | `cwos_manager_01-cwos_manager/` | `cwos-manager/` |
|
||||
| 版本号规范 | `V1.0.0.20211103` | `v1.0.0`(内部保留完整日期) |
|
||||
| 备份标记规范 | `front_acs.bak20231018/` | → `archive/frontend-backups/`(集中存放) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 顶层目录结构
|
||||
|
||||
```text
|
||||
星河湾星中星/
|
||||
├── source/ # 源码(仅活跃开发代码)
|
||||
├── runtime/ # 运行时部署(生产/预发镜像)
|
||||
├── packages/ # 部署压缩包(tar.gz)
|
||||
├── scripts/ # 统一脚本入口
|
||||
├── docs/ # 文档中心
|
||||
├── data-backups/ # 数据库备份
|
||||
├── archive/ # 【新增】历史/遗留/参考内容
|
||||
├── artifacts/ # 构建产物/证据
|
||||
├── nginx/ # Nginx 配置
|
||||
└── logs/ # 运行日志
|
||||
```
|
||||
|
||||
### 3.1 映射关系
|
||||
|
||||
| 当前路径 | 新路径 | 类型 |
|
||||
|---------|--------|------|
|
||||
| `源码/` | → 拆分 | 源码、nginx、artifacts、反编译各自归位 |
|
||||
| `星中心/` | `runtime/` | 运行时部署 |
|
||||
| `部署包/` | `packages/` | 部署压缩包 |
|
||||
| `data_backup/` | `data-backups/` | 数据库备份 |
|
||||
| `反1/` | `archive/decompiled-sources/` | 归档 |
|
||||
| `cn/` | `archive/miscellaneous/` | 归档 |
|
||||
| `media/` | `archive/miscellaneous/` | 归档 |
|
||||
| `源码/源码/` | `archive/miscellaneous/` | 归档 |
|
||||
| — | `archive/frontend-backups/` | **新增**,收纳所有 `.bak` 前端备份 |
|
||||
| `scripts/` (顶层) | `scripts/deploy/` | 合并到统一脚本目录 |
|
||||
| `源码/scripts/` | `scripts/` (主目录) | 合入不同子目录 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 各目录内部设计
|
||||
|
||||
### 4.1 `source/backend/` — 后端 Maven 项目
|
||||
|
||||
去除 `maven-` 前缀,保持原始目录内部结构不变:
|
||||
|
||||
| 原名 | 新名 |
|
||||
|------|------|
|
||||
| `maven-cloudwalk-cloud/` | `cloudwalk-cloud/` |
|
||||
| `maven-cloudwalk-device-manager/` | `cloudwalk-device-manager/` |
|
||||
| `maven-cloudwalk-device-sdk/` | `cloudwalk-device-sdk/` |
|
||||
| `maven-cloudwalk-intelligent-davinci-manager/` | `intelligent-davinci-manager/` |
|
||||
| `maven-cloudwalk-legacy-public/` | `cloudwalk-legacy-public/` |
|
||||
| `maven-cw-elevator-application/` | `cw-elevator-application/` |
|
||||
| `maven-cwos-common-aks/` | `cwos-common-aks/` |
|
||||
| `maven-cwos-device-authentication/` | `cwos-device-authentication/` |
|
||||
| `maven-cwos-resource/` | `cwos-resource/` |
|
||||
| `maven-intelligent-cwoscomponent/` | `intelligent-cwoscomponent/` |
|
||||
| `maven-ninca-common-component-organization/` | `ninca-common-component-organization/` |
|
||||
| `maven-ninca-crk-from-lib/` | `ninca-crk-from-lib/` |
|
||||
| `maven-ninca-qk-alarm/` | `ninca-qk-alarm/` |
|
||||
**移出 `source/`:**
|
||||
- `cw-elevator-application-V1.0.0.20211103/` → `runtime/elevator-v1/`
|
||||
|
||||
### 4.2 `source/frontend/` — 前端源码
|
||||
|
||||
| 原名 | 新名 |
|
||||
|------|------|
|
||||
| `frontend-source/projects/` | `projects/` (alarm-front, cwos-portal, elevator-front, front-acs) |
|
||||
| `frontend-source/decompiled/` | `decompiled/` |
|
||||
| `frontend-source/scripts/` | `scripts/` |
|
||||
|
||||
### 4.3 `runtime/` — 运行时部署
|
||||
|
||||
去除 `_01-` 数字前缀和冗余版本后缀,改用简洁的服务名:
|
||||
|
||||
| 原名 | 新名 |
|
||||
|------|------|
|
||||
| `cw-elevator-application-V1.0.0.20211103/` | `elevator-v1/` |
|
||||
| `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/` |
|
||||
| `ninca-crk-std-backend-V2.9.1_20210630/` | `ninca-crk-std-lib/` |
|
||||
|
||||
**前端运行时合并(`runtime/frontend/`):**
|
||||
- 从 `源码/frontend/` 和 `星中心/frontend/` 中提取**非 `.bak`** 的活跃前端目录
|
||||
- 重复的以前者(`源码/frontend/`)为准
|
||||
- 所有 `.bak` 目录 → `archive/frontend-backups/`
|
||||
|
||||
### 4.4 `packages/` — 部署压缩包
|
||||
|
||||
保持现有结构,仅移除 `01-` 文件名前缀:
|
||||
|
||||
| 原名 | 新名 |
|
||||
|------|------|
|
||||
| `cwos_manager_01-cwos_manager.tar.gz` | `cwos-manager.tar.gz` |
|
||||
| `ninca_common_component_organization_01-...` | `ninca-common-component-organization-...` |
|
||||
| (同理应用于所有 13 个 tar.gz) | |
|
||||
|
||||
**内部目录规范化:**
|
||||
- `releases/` — 保留
|
||||
- `components/` — 整理展开的组件目录
|
||||
|
||||
### 4.5 `scripts/` — 统一脚本目录
|
||||
|
||||
```text
|
||||
scripts/
|
||||
├── build/ # 构建/发布/格式化相关
|
||||
│ ├── release-cw-elevator-application.sh
|
||||
│ ├── format_maven_formatter_all.sh
|
||||
│ ├── check_maven_formatter_validate.sh
|
||||
│ ├── build_elevator.sh (new)
|
||||
│ └── *.sh
|
||||
├── deploy/ # 部署/启停相关
|
||||
│ ├── start_ninca.sh (from top-level scripts/)
|
||||
│ └── *.sh
|
||||
├── test-env/ # 测试环境搭建(从 源码/scripts/test-env/ 整体迁移)
|
||||
│ ├── docker-compose.infra.yml
|
||||
│ ├── setup.sh
|
||||
│ ├── start-all.sh / stop-all.sh
|
||||
│ ├── health-check.sh
|
||||
│ └── ...
|
||||
├── tools/ # 分析/对拍/校验工具
|
||||
│ ├── check_elevator_fatjar_lib_parity.sh
|
||||
│ ├── compare_jar_to_sources.py
|
||||
│ ├── decompile_ninca_crk_lib_modules.py
|
||||
│ ├── run_full_elevator_api_suite.sh
|
||||
│ ├── run_v1v2_parity_automated.sh
|
||||
│ ├── verify_frontend_runtime.py
|
||||
│ ├── verify_v1_v2_config_load_order.sh
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
### 4.6 `archive/` — 历史/参考/遗留
|
||||
|
||||
```text
|
||||
archive/
|
||||
├── decompiled-sources/ # (from 反1/ — 22 个 .src.zip)
|
||||
├── frontend-backups/ # (所有 .bak* 前端备份目录)
|
||||
│ ├── front_acs.bak20231012/
|
||||
│ ├── front_acs.bak20231018/
|
||||
│ ├── ...
|
||||
│ └── *.tar.gz (旧前端归档)
|
||||
└── miscellaneous/ # (其他杂项)
|
||||
├── cn/ # (from 顶层 cn/)
|
||||
├── media/ # (from 顶层 media/)
|
||||
├── source-self-ref/ # (from 源码/源码/ 自嵌套目录)
|
||||
├── visitor-noauth-verify-v20260430.zip # (源码/ 根部的 zip)
|
||||
└── elevator-app.log # (源码/ 根部的日志文件)
|
||||
```
|
||||
|
||||
### 4.7 其他目录
|
||||
|
||||
| 当前路径 | 新路径 | 说明 |
|
||||
|---------|--------|------|
|
||||
| `源码/artifacts/` | `artifacts/` | 构建产物/证据(与源码分离) |
|
||||
| `源码/nginx-r1.0425/` | `nginx/` | Nginx 配置(与源码分离) |
|
||||
| `源码/docs/` | `docs/` | 文档中心(统一位置) |
|
||||
| `源码/dev-support/` | `docs/dev-support/` | 开发支持文档 |
|
||||
| `logs/` | `logs/` | 保持不变 |
|
||||
| `源码/.sisyphus/` | `.sisyphus/` | 保持不变(在 source/ 内的工具配置) |
|
||||
| `源码/.gitignore` | (保持) | |
|
||||
| `源码/AGENTS.md` | (保持) | 更新路径引用 |
|
||||
| `源码/.editorconfig` | (保持) | |
|
||||
|
||||
---
|
||||
|
||||
## 5. 影响与注意事项
|
||||
|
||||
### 5.1 路径依赖影响
|
||||
|
||||
以下内容可能引用了旧路径,重组后需更新:
|
||||
|
||||
1. **`源码/AGENTS.md`** — 文档中引用了顶层目录结构,需更新
|
||||
2. **`scripts/test-env/` 下的脚本** — 可能引用了 `../maven-cw-elevator-application/` 等路径
|
||||
3. **`源码/maven-cw-elevator-application/scripts/`** — 构建脚本中可能硬编码了路径
|
||||
4. **`maven-cw-elevator-application/tools/`** — Python 测试脚本中的路径引用
|
||||
5. **`source/frontend/scripts/`** — 前端构建工具的路径配置
|
||||
6. **Docker Compose 文件** — `scripts/test-env/docker-compose.infra.yml` 中的挂载卷路径
|
||||
|
||||
### 5.2 执行策略
|
||||
|
||||
1. 使用 `git mv` 执行移动(保留 git 历史)
|
||||
2. 先移动、再更新引用、最后验证
|
||||
3. 每个顶层目录分批执行,每批完成后运行验证
|
||||
4. 备份文件不做任何修改,整体迁移
|
||||
|
||||
### 5.3 不处理的范围
|
||||
|
||||
以下内容保持原样,不做重组:
|
||||
- `.git/` — git 仓库元数据
|
||||
- `.gitignore` — 配置
|
||||
- `源码/.sisyphus/` — Sisyphus AI 工具配置
|
||||
- 各 Maven 项目的 `target/` 目录 — 构建产物
|
||||
|
||||
---
|
||||
|
||||
## 6. 执行计划(概要)
|
||||
|
||||
| 阶段 | 内容 | 预估操作数 |
|
||||
|------|------|-----------|
|
||||
| 1 | 创建 archive/ 及子目录 | ~10 `mkdir` |
|
||||
| 2 | 迁移 反1/ → archive/decompiled-sources/ | 1 `git mv` |
|
||||
| 3 | 迁移 cn/, media/ → archive/miscellaneous/ | 2 `git mv` |
|
||||
| 4 | 迁移 源码/源码/ → archive/miscellaneous/ | 1 `git mv` |
|
||||
| 5 | 迁移源码/frontend/ .bak 目录 → archive/frontend-backups/ | ~15 `git mv` |
|
||||
| 6 | 创建 runtime/, 从 星中心/ 迁移并重命名 | ~8 `git mv` |
|
||||
| 7 | 创建 source/backend/, 从 源码/ 迁移 Maven 项目并重命名 | ~14 `git mv` |
|
||||
| 8 | 创建 source/frontend/, 从 源码/frontend-source/ 迁移 | 1 `git mv` |
|
||||
| 9 | 迁移 源码/frontend/(非 .bak)→ runtime/frontend/ | ~15 `git mv` |
|
||||
| 10 | 迁移 源码/nginx-r1.0425/ → nginx/ | 1 `git mv` |
|
||||
| 11 | 迁移 源码/artifacts/ → artifacts/ | 1 `git mv` |
|
||||
| 12 | 合并 scripts/ 目录 | ~5 `git mv` |
|
||||
| 13 | 清理 源码/ 残余 | 验证 |
|
||||
| 14 | 更新 AGENTS.md 中的路径引用 | 1 次编辑 |
|
||||
| 15 | 更新 test-env 脚本中的路径引用 | 逐一检查 |
|
||||
| 16 | final: 验证 + git status | 确认 |
|
||||
Reference in New Issue
Block a user