重构:统一 IoT 模型到 internal/model/ 目录
将所有 IoT 相关的数据模型从 internal/iot/model/ 迁移到 internal/model/, 实现全局统一的模型层架构,符合项目横向分层设计原则。 变更内容: - 迁移 11 个 IoT 模型文件(carrier, iot_card, device, order, package 等) - 删除 internal/iot/model/ 目录 - 更新文档中的模型路径引用(25 处) - 创建重构总结文档 - 归档 OpenSpec 变更为 2026-01-12-refactor-iot-model-location - 创建 model-organization 规格文档 验证结果: - 编译通过(go build 成功) - 静态分析通过(go vet 无错误) - 代码格式通过(go fmt 无变更) - 无 Go 代码引用旧路径 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
('CTCC', '中国电信', 1);
|
||||
```
|
||||
|
||||
**GORM 模型**: `internal/iot/model/carrier.go:5`
|
||||
**GORM 模型**: `internal/model/carrier.go:5`
|
||||
|
||||
---
|
||||
|
||||
@@ -132,7 +132,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `owner_type` 和 `owner_id` 组合表示所有权
|
||||
- `enable_polling = false` 的卡不参与轮询检查
|
||||
|
||||
**GORM 模型**: `internal/iot/model/iot_card.go:8`
|
||||
**GORM 模型**: `internal/model/iot_card.go:8`
|
||||
|
||||
---
|
||||
|
||||
@@ -167,7 +167,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `sim_slots` 取值范围 1-4
|
||||
- 通过 `device_sim_bindings` 表管理设备与 IoT 卡的绑定关系
|
||||
|
||||
**GORM 模型**: `internal/iot/model/device.go:5`
|
||||
**GORM 模型**: `internal/model/device.go:5`
|
||||
|
||||
---
|
||||
|
||||
@@ -203,7 +203,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 使用虚拟商品编码映射运营商订单
|
||||
- 从上游平台下单,不涉及本地库存管理
|
||||
|
||||
**GORM 模型**: `internal/iot/model/number_card.go:8`
|
||||
**GORM 模型**: `internal/model/number_card.go:8`
|
||||
|
||||
---
|
||||
|
||||
@@ -235,7 +235,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 套餐系列用于分组管理套餐
|
||||
- 分佣规则可以按套餐系列统一配置
|
||||
|
||||
**GORM 模型**: `internal/iot/model/package.go:7`
|
||||
**GORM 模型**: `internal/model/package.go:7`
|
||||
|
||||
---
|
||||
|
||||
@@ -274,7 +274,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 停机判断基于 `virtual_data_mb` (虚流量用完即停机)
|
||||
- `package_type = 'formal'` 表示正式套餐,`'addon'` 表示附加套餐
|
||||
|
||||
**GORM 模型**: `internal/iot/model/package.go:23`
|
||||
**GORM 模型**: `internal/model/package.go:23`
|
||||
|
||||
---
|
||||
|
||||
@@ -307,7 +307,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `retail_price` 是代理的零售价格
|
||||
- 佣金 = `retail_price - cost_price`
|
||||
|
||||
**GORM 模型**: `internal/iot/model/package.go:48`
|
||||
**GORM 模型**: `internal/model/package.go:48`
|
||||
|
||||
---
|
||||
|
||||
@@ -341,7 +341,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `slot_position` 取值范围 1-4
|
||||
- 同一设备的同一插槽位置只能绑定一张激活状态的 IoT 卡
|
||||
|
||||
**GORM 模型**: `internal/iot/model/package.go:66`
|
||||
**GORM 模型**: `internal/model/package.go:66`
|
||||
|
||||
---
|
||||
|
||||
@@ -385,7 +385,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `data_usage_mb = real_data_usage_mb + virtual_data_usage_mb`
|
||||
- 轮询检查时更新 `last_package_check_at`
|
||||
|
||||
**GORM 模型**: `internal/iot/model/package.go:85`
|
||||
**GORM 模型**: `internal/model/package.go:85`
|
||||
|
||||
---
|
||||
|
||||
@@ -426,7 +426,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 可以按卡状态和运营商配置不同的轮询策略
|
||||
- 优先级控制多个配置的执行顺序
|
||||
|
||||
**GORM 模型**: `internal/iot/model/polling.go:7`
|
||||
**GORM 模型**: `internal/model/polling.go:7`
|
||||
|
||||
---
|
||||
|
||||
@@ -459,7 +459,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `carrier_sync_data` 存储从运营商 Gateway 同步的原始数据
|
||||
- 用于历史查询和统计分析
|
||||
|
||||
**GORM 模型**: `internal/iot/model/data_usage.go:5`
|
||||
**GORM 模型**: `internal/model/data_usage.go:5`
|
||||
|
||||
---
|
||||
|
||||
@@ -510,7 +510,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `number_card_id` 必须有值
|
||||
- `package_id` 为空
|
||||
|
||||
**GORM 模型**: `internal/iot/model/order.go:11`
|
||||
**GORM 模型**: `internal/model/order.go:11`
|
||||
|
||||
---
|
||||
|
||||
@@ -544,7 +544,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `agent_path` 存储完整的代理路径,便于查询上级链
|
||||
- 一级代理的 `parent_agent_id` 为 NULL
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:8`
|
||||
**GORM 模型**: `internal/model/commission.go:8`
|
||||
|
||||
---
|
||||
|
||||
@@ -585,7 +585,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `unfreeze_mode = 'auto'`: 时间到期 OR 流量达标,满足其一即可自动解冻
|
||||
- `unfreeze_mode = 'manual'`: 需要人工审批解冻
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:24`
|
||||
**GORM 模型**: `internal/model/commission.go:24`
|
||||
|
||||
---
|
||||
|
||||
@@ -619,7 +619,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
51+ 单: 20元/单
|
||||
```
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:47`
|
||||
**GORM 模型**: `internal/model/commission.go:47`
|
||||
|
||||
---
|
||||
|
||||
@@ -652,7 +652,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 一次性条件立即发放
|
||||
- 长期条件冻结后按规则解冻
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:68`
|
||||
**GORM 模型**: `internal/model/commission.go:68`
|
||||
|
||||
---
|
||||
|
||||
@@ -697,7 +697,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
```
|
||||
- 满足时间到期 **OR** 流量达标,任一条件满足即可自动解冻
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:88`
|
||||
**GORM 模型**: `internal/model/commission.go:88`
|
||||
|
||||
---
|
||||
|
||||
@@ -731,7 +731,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 当分佣规则的 `unfreeze_mode = 'manual'` 时,需要创建审批记录
|
||||
- 审批通过后才能解冻和发放佣金
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:116`
|
||||
**GORM 模型**: `internal/model/commission.go:116`
|
||||
|
||||
---
|
||||
|
||||
@@ -760,7 +760,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- `template_data` 存储分佣规则的完整配置
|
||||
- 可以基于模板快速创建新的分佣规则
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:137`
|
||||
**GORM 模型**: `internal/model/commission.go:137`
|
||||
|
||||
---
|
||||
|
||||
@@ -794,7 +794,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 按月统计与运营商的交易情况
|
||||
- 支持结算和支付状态跟踪
|
||||
|
||||
**GORM 模型**: `internal/iot/model/commission.go:155`
|
||||
**GORM 模型**: `internal/model/commission.go:155`
|
||||
|
||||
---
|
||||
|
||||
@@ -838,7 +838,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
}
|
||||
```
|
||||
|
||||
**GORM 模型**: `internal/iot/model/financial.go:8`
|
||||
**GORM 模型**: `internal/model/financial.go:8`
|
||||
|
||||
---
|
||||
|
||||
@@ -869,7 +869,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 每个代理可以有独立的提现规则
|
||||
- `withdrawal_fee_rate` 为 0.005 表示 0.5% 手续费
|
||||
|
||||
**GORM 模型**: `internal/iot/model/financial.go:31`
|
||||
**GORM 模型**: `internal/model/financial.go:31`
|
||||
|
||||
---
|
||||
|
||||
@@ -907,7 +907,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
}
|
||||
```
|
||||
|
||||
**GORM 模型**: `internal/iot/model/financial.go:51`
|
||||
**GORM 模型**: `internal/model/financial.go:51`
|
||||
|
||||
---
|
||||
|
||||
@@ -940,7 +940,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
- 用于配置系统的开发能力开关
|
||||
- `capability_config` 存储能力的详细配置
|
||||
|
||||
**GORM 模型**: `internal/iot/model/system.go:5`
|
||||
**GORM 模型**: `internal/model/system.go:5`
|
||||
|
||||
---
|
||||
|
||||
@@ -977,7 +977,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
|
||||
2. 管理员审核 (status=2/3)
|
||||
3. 审核通过后完成换卡 (status=4)
|
||||
|
||||
**GORM 模型**: `internal/iot/model/system.go:25`
|
||||
**GORM 模型**: `internal/model/system.go:25`
|
||||
|
||||
---
|
||||
|
||||
|
||||
252
docs/refactor-iot-model-location/重构总结.md
Normal file
252
docs/refactor-iot-model-location/重构总结.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# IoT 模型位置重构 - 总结文档
|
||||
|
||||
## 📋 变更概述
|
||||
|
||||
**变更 ID**: `refactor-iot-model-location`
|
||||
|
||||
**变更类型**: 架构重构
|
||||
|
||||
**实施日期**: 2026-01-12
|
||||
|
||||
**状态**: ✅ 已完成
|
||||
|
||||
## 🎯 重构动机
|
||||
|
||||
### 问题背景
|
||||
|
||||
在实施 IoT SIM 管理模块时,IoT 相关的数据模型被放置在 `internal/iot/model/` 目录下,这与项目的其他模型(Shop、Account、Enterprise 等)不一致。其他模型都统一放在 `internal/model/` 目录下。
|
||||
|
||||
### 存在的问题
|
||||
|
||||
1. **目录结构不一致**
|
||||
- IoT 模型使用 `internal/iot/model/`
|
||||
- 其他模型使用 `internal/model/`
|
||||
- 导致项目结构混乱,违反统一性原则
|
||||
|
||||
2. **违反项目架构约定**
|
||||
- 项目采用严格的横向四层架构:Handler → Service → Store → Model
|
||||
- Model 层应该是全局统一的,不应该按业务模块分散
|
||||
- 当前的 `internal/iot/model/` 违反了横向分层原则
|
||||
|
||||
3. **导入路径冗余**
|
||||
- 引用 IoT 模型时需要写 `internal/iot/model`
|
||||
- 引用其他模型只需要写 `internal/model`
|
||||
- 增加了开发者的认知负担
|
||||
|
||||
4. **违反 Go 语言惯用设计**
|
||||
- Go 推荐扁平化包结构,避免深层嵌套
|
||||
- 包应该按功能组织,不是按业务模块分散
|
||||
- `internal/iot/model` 是不必要的复杂性
|
||||
|
||||
## 🔧 实施过程
|
||||
|
||||
### 迁移文件清单
|
||||
|
||||
共迁移 11 个 IoT 模型文件:
|
||||
|
||||
| 序号 | 文件名 | 说明 |
|
||||
|------|--------|------|
|
||||
| 1 | carrier.go | 运营商模型 |
|
||||
| 2 | commission.go | 佣金模型 |
|
||||
| 3 | data_usage.go | 流量使用记录模型 |
|
||||
| 4 | device.go | 设备模型 |
|
||||
| 5 | financial.go | 财务记录模型 |
|
||||
| 6 | iot_card.go | IoT 卡模型 |
|
||||
| 7 | number_card.go | 号卡模型 |
|
||||
| 8 | order.go | 订单模型 |
|
||||
| 9 | package.go | 套餐模型 |
|
||||
| 10 | polling.go | 轮询配置模型 |
|
||||
| 11 | system.go | 系统配置模型 |
|
||||
|
||||
### 实施步骤
|
||||
|
||||
1. **准备工作**
|
||||
- ✅ 确认 `internal/model/` 目录存在且可写
|
||||
- ✅ 确认 `internal/iot/model/` 包含 11 个模型文件
|
||||
- ✅ 搜索并确认无 Go 代码引用旧路径
|
||||
|
||||
2. **迁移执行**
|
||||
- ✅ 使用 `git mv` 命令移动所有 11 个文件到 `internal/model/`
|
||||
- ✅ Git 正确识别了所有文件的重命名(保留文件历史)
|
||||
|
||||
3. **清理工作**
|
||||
- ✅ 验证 `internal/iot/model/` 目录为空
|
||||
- ✅ 删除空的 `internal/iot/model/` 目录
|
||||
- ✅ 保留 `internal/iot/` 目录(用于未来的 Handler/Service/Store 层)
|
||||
|
||||
4. **验证工作**
|
||||
- ✅ 确认项目中无 `internal/iot/model` 的 Go 代码引用
|
||||
- ✅ 验证所有模型文件包名统一为 `package model`
|
||||
- ✅ 运行 `go fmt ./...` 格式检查通过
|
||||
- ✅ 运行 `go vet ./...` 静态分析通过
|
||||
- ✅ 运行 `go build` 编译成功
|
||||
|
||||
5. **文档更新**
|
||||
- ✅ 更新 `docs/iot-sim-management/表结构详细说明.md` 中的 25 处引用
|
||||
- ✅ 更新 OpenSpec 变更文档(tasks.md、proposal.md)
|
||||
|
||||
## 📊 影响范围
|
||||
|
||||
### 代码影响
|
||||
|
||||
- **Go 代码**: 零影响
|
||||
- 经过搜索,项目中没有任何 Go 代码引用 `internal/iot/model`
|
||||
- IoT 模块尚未完全实现业务逻辑层,因此无代码依赖
|
||||
|
||||
- **文档影响**: 25 处引用更新
|
||||
- `docs/iot-sim-management/表结构详细说明.md` 中的模型路径引用已全部更新
|
||||
|
||||
- **数据库影响**: 无影响
|
||||
- 模型位置变更不影响数据库表结构
|
||||
- 数据库迁移脚本不受影响
|
||||
|
||||
- **API 影响**: 无影响
|
||||
- 模型位置变更不影响 API 接口定义
|
||||
|
||||
### 架构改进
|
||||
|
||||
**重构前的目录结构:**
|
||||
```
|
||||
internal/
|
||||
├── model/ # 大部分模型
|
||||
│ ├── account.go
|
||||
│ ├── shop.go
|
||||
│ └── ...
|
||||
└── iot/
|
||||
└── model/ # IoT 模型单独存放 ❌
|
||||
├── carrier.go
|
||||
└── ...
|
||||
```
|
||||
|
||||
**重构后的目录结构:**
|
||||
```
|
||||
internal/
|
||||
├── model/ # 统一的模型层 ✅
|
||||
│ ├── account.go
|
||||
│ ├── shop.go
|
||||
│ ├── carrier.go # IoT 模型已迁移
|
||||
│ ├── iot_card.go # IoT 模型已迁移
|
||||
│ └── ...
|
||||
└── iot/ # 保留用于未来的业务逻辑层
|
||||
├── handler.go # (未来)IoT Handler 层
|
||||
├── service.go # (未来)IoT Service 层
|
||||
└── store.go # (未来)IoT Store 层
|
||||
```
|
||||
|
||||
## ✅ 验证结果
|
||||
|
||||
### 验收标准验证
|
||||
|
||||
| 验收标准 | 状态 | 验证方法 |
|
||||
|----------|------|----------|
|
||||
| 所有 IoT 模型文件已迁移到 `internal/model/` | ✅ | `ls internal/model/` 包含全部 11 个文件 |
|
||||
| `internal/iot/model/` 目录已删除 | ✅ | `test ! -d internal/iot/model/` 返回成功 |
|
||||
| 项目可以正常编译 | ✅ | `go build ./cmd/api` 编译成功 |
|
||||
| 所有测试通过 | ✅ | 项目暂无测试 |
|
||||
| 项目中不存在 `internal/iot/model` 的引用 | ✅ | 已更新文档中的引用 |
|
||||
|
||||
### 代码质量验证
|
||||
|
||||
| 验证项 | 状态 | 结果 |
|
||||
|--------|------|------|
|
||||
| Go 格式检查 | ✅ | `go fmt ./...` 通过 |
|
||||
| 静态分析 | ✅ | `go vet ./...` 无错误 |
|
||||
| 编译验证 | ✅ | `go build` 成功 |
|
||||
| 包名一致性 | ✅ | 所有文件包名统一为 `package model` |
|
||||
|
||||
### Git 历史保留
|
||||
|
||||
- ✅ 使用 `git mv` 命令移动文件,保留了完整的 Git 历史
|
||||
- ✅ 可以使用 `git log --follow <file>` 追踪文件的完整历史
|
||||
|
||||
## 📈 收益总结
|
||||
|
||||
### 直接收益
|
||||
|
||||
1. **架构一致性** ✅
|
||||
- 所有模型统一放在 `internal/model/` 目录
|
||||
- 符合项目横向四层架构约定
|
||||
- 提高了代码组织的可预测性
|
||||
|
||||
2. **简化导入路径** ✅
|
||||
- 所有模型统一使用 `internal/model` 导入
|
||||
- 减少了认知负担和开发成本
|
||||
|
||||
3. **符合 Go 惯用设计** ✅
|
||||
- 扁平化包结构
|
||||
- 按功能组织,不是按业务模块分散
|
||||
- 提高了 Go 代码的地道性(idiomatic Go)
|
||||
|
||||
4. **提高可维护性** ✅
|
||||
- 开发者只需要在一个地方查找所有数据模型
|
||||
- 降低了新开发者的上手难度
|
||||
- 避免了"模型应该放在哪里"的困惑
|
||||
|
||||
### 长期收益
|
||||
|
||||
1. **可扩展性**
|
||||
- 为未来的 IoT 业务逻辑层(Handler/Service/Store)预留了清晰的位置
|
||||
- 符合项目长期架构演进方向
|
||||
|
||||
2. **代码质量**
|
||||
- 统一的架构约定有助于保持代码质量
|
||||
- 减少了技术债务的累积
|
||||
|
||||
3. **团队协作**
|
||||
- 明确的架构约定减少了团队内部的讨论成本
|
||||
- 提高了代码审查的效率
|
||||
|
||||
## 📝 经验教训
|
||||
|
||||
### 做得好的地方
|
||||
|
||||
1. **提前搜索引用**
|
||||
- 在迁移前搜索了所有引用,确认无代码依赖
|
||||
- 降低了重构风险
|
||||
|
||||
2. **使用 Git 工具**
|
||||
- 使用 `git mv` 保留了文件历史
|
||||
- 便于未来追溯和回滚
|
||||
|
||||
3. **完整的验证流程**
|
||||
- 编译、静态分析、格式检查一个都没少
|
||||
- 确保了重构的安全性
|
||||
|
||||
4. **文档同步更新**
|
||||
- 及时更新了相关文档中的引用
|
||||
- 避免了文档与代码不一致的问题
|
||||
|
||||
### 可以改进的地方
|
||||
|
||||
1. **更早发现问题**
|
||||
- 如果在 IoT 模块设计初期就遵循统一的架构约定,就不需要这次重构
|
||||
- 建议:在代码审查时严格检查架构约定
|
||||
|
||||
2. **自动化检查**
|
||||
- 可以添加 CI 检查,防止模型被放在错误的位置
|
||||
- 建议:添加 linter 规则检查目录结构
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
- **变更提案**: `openspec/changes/refactor-iot-model-location/proposal.md`
|
||||
- **设计文档**: `openspec/changes/refactor-iot-model-location/design.md`
|
||||
- **任务清单**: `openspec/changes/refactor-iot-model-location/tasks.md`
|
||||
- **项目架构规范**: `openspec/project.md`
|
||||
- **IoT SIM 管理规格**: `openspec/specs/iot-card/spec.md`
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
本次重构成功地将 11 个 IoT 模型文件从 `internal/iot/model/` 迁移到 `internal/model/`,实现了项目架构的统一性和一致性。
|
||||
|
||||
**重构特点:**
|
||||
- ✅ 零代码影响(无 Go 代码依赖)
|
||||
- ✅ 完整的验证流程(编译、静态分析、格式检查)
|
||||
- ✅ 保留了完整的 Git 历史
|
||||
- ✅ 同步更新了相关文档
|
||||
|
||||
**架构改进:**
|
||||
- ✅ 符合横向四层架构约定
|
||||
- ✅ 符合 Go 语言惯用设计
|
||||
- ✅ 提高了代码可维护性和可扩展性
|
||||
|
||||
本次重构为项目的长期健康发展奠定了坚实的基础。
|
||||
@@ -129,4 +129,3 @@ func (s *AccountStore) List(ctx context.Context, opts *store.QueryOptions, filte
|
||||
|
||||
return accounts, total, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-12
|
||||
@@ -0,0 +1,3 @@
|
||||
# refactor-iot-model-location
|
||||
|
||||
将 IoT 模型从 internal/iot/model/ 迁移到统一的 internal/model/ 目录
|
||||
@@ -0,0 +1,230 @@
|
||||
# 设计文档:IoT 模型位置重构
|
||||
|
||||
## 问题陈述 (Problem Statement)
|
||||
|
||||
当前 IoT 模块的数据模型被放置在 `internal/iot/model/` 目录下,这与项目的架构约定不一致。根据 `openspec/project.md` 的架构规范,项目采用严格的四层架构:
|
||||
|
||||
```
|
||||
Handler 层 → Service 层 → Store 层 → Model 层
|
||||
```
|
||||
|
||||
其中 **Model 层** 应该是全局统一的,所有模型都应该放在 `internal/model/` 目录下。当前的 IoT 模型位置违反了这一约定。
|
||||
|
||||
## 当前架构问题分析
|
||||
|
||||
### 1. 目录结构不一致
|
||||
|
||||
**当前状态:**
|
||||
```
|
||||
internal/
|
||||
├── model/ # 大部分模型在这里
|
||||
│ ├── account.go
|
||||
│ ├── shop.go
|
||||
│ ├── enterprise.go
|
||||
│ ├── personal_customer.go
|
||||
│ ├── role.go
|
||||
│ ├── permission.go
|
||||
│ └── ...
|
||||
├── iot/
|
||||
│ └── model/ # IoT 模型单独在这里 ❌
|
||||
│ ├── carrier.go
|
||||
│ ├── commission.go
|
||||
│ ├── iot_card.go
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
**存在的问题:**
|
||||
- 模型分散在两个位置,违反了单一数据模型层的设计原则
|
||||
- 导入路径不一致:`internal/model` vs `internal/iot/model`
|
||||
- 新开发者容易困惑,不知道应该把新模型放在哪里
|
||||
|
||||
### 2. 违反项目架构约定
|
||||
|
||||
根据项目架构规范,四层架构应该是 **横向分层**,而不是 **纵向按模块分层**:
|
||||
|
||||
**正确的架构(横向分层):**
|
||||
```
|
||||
internal/
|
||||
├── handler/ # 所有 Handler
|
||||
│ ├── user_handler.go
|
||||
│ ├── shop_handler.go
|
||||
│ └── iot_handler.go # IoT 的 Handler
|
||||
├── service/ # 所有 Service
|
||||
│ ├── user_service.go
|
||||
│ ├── shop_service.go
|
||||
│ └── iot_service.go # IoT 的 Service
|
||||
├── store/ # 所有 Store
|
||||
│ ├── user_store.go
|
||||
│ ├── shop_store.go
|
||||
│ └── iot_store.go # IoT 的 Store
|
||||
└── model/ # 所有 Model ✅
|
||||
├── user.go
|
||||
├── shop.go
|
||||
└── iot_card.go # IoT 的 Model
|
||||
```
|
||||
|
||||
**错误的架构(纵向按模块分层):**
|
||||
```
|
||||
internal/
|
||||
├── user/ # 用户模块(纵向)
|
||||
│ ├── handler.go
|
||||
│ ├── service.go
|
||||
│ ├── store.go
|
||||
│ └── model.go
|
||||
├── shop/ # 店铺模块(纵向)
|
||||
│ ├── handler.go
|
||||
│ ├── service.go
|
||||
│ ├── store.go
|
||||
│ └── model.go
|
||||
└── iot/ # IoT 模块(纵向)❌
|
||||
├── handler.go
|
||||
├── service.go
|
||||
├── store.go
|
||||
└── model/ # 违反横向分层原则
|
||||
└── iot_card.go
|
||||
```
|
||||
|
||||
### 3. Go 语言惯用设计原则
|
||||
|
||||
根据 Go 语言的包组织最佳实践:
|
||||
|
||||
- **包应该扁平化**:避免深层嵌套(最多 2-3 层)
|
||||
- **包应该按功能组织**:`model` 包应该包含所有数据模型,不是按业务模块分散
|
||||
- **包名应该描述功能**:`model` 表示数据模型,而不是 `iot/model`(冗余)
|
||||
|
||||
当前 `internal/iot/model` 的包名虽然是 `package model`,但实际导入路径是 `internal/iot/model`,这是不必要的复杂性。
|
||||
|
||||
## 设计决策 (Design Decision)
|
||||
|
||||
**决策:** 将所有 IoT 模型迁移到 `internal/model/` 目录,与项目的其他模型保持一致。
|
||||
|
||||
### 为什么选择统一的 Model 层?
|
||||
|
||||
1. **符合项目架构约定**:遵循严格的横向四层架构(Handler → Service → Store → Model)
|
||||
2. **简化导入路径**:所有模型统一使用 `internal/model` 导入
|
||||
3. **提高代码可维护性**:开发者只需要在一个地方查找所有数据模型
|
||||
4. **符合 Go 语言惯用设计**:扁平化包结构,按功能组织
|
||||
5. **降低认知负担**:新开发者不需要猜测模型应该放在哪里
|
||||
|
||||
### 为什么不保留 `internal/iot/model`?
|
||||
|
||||
**反驳方案 1:按业务模块组织(纵向分层)**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── iot/
|
||||
│ ├── model/ # IoT 模型
|
||||
│ ├── service/ # IoT 服务
|
||||
│ └── store/ # IoT 存储
|
||||
```
|
||||
|
||||
**缺点:**
|
||||
- 违反项目架构约定(横向分层)
|
||||
- 导致模型分散,难以统一管理
|
||||
- 跨模块调用时需要引用不同的 model 包(如 `iot/model` 和 `internal/model`)
|
||||
- 不符合当前项目已有的架构实践
|
||||
|
||||
**反驳方案 2:IoT 模型使用子包(`internal/model/iot`)**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/
|
||||
│ ├── user.go
|
||||
│ ├── shop.go
|
||||
│ └── iot/ # IoT 子包
|
||||
│ └── iot_card.go
|
||||
```
|
||||
|
||||
**缺点:**
|
||||
- 引入不必要的层级嵌套
|
||||
- 导入路径变为 `internal/model/iot`,不符合项目惯例
|
||||
- 其他模型(User、Shop)没有子包,为什么 IoT 要特殊对待?
|
||||
- 增加认知负担:需要记住哪些模型有子包,哪些没有
|
||||
|
||||
### 最终方案:扁平化模型目录
|
||||
|
||||
```
|
||||
internal/
|
||||
└── model/
|
||||
├── account.go
|
||||
├── shop.go
|
||||
├── enterprise.go
|
||||
├── personal_customer.go
|
||||
├── role.go
|
||||
├── permission.go
|
||||
├── carrier.go # IoT 相关模型
|
||||
├── commission.go # IoT 相关模型
|
||||
├── data_usage.go # IoT 相关模型
|
||||
├── device.go # IoT 相关模型
|
||||
├── financial.go # IoT 相关模型
|
||||
├── iot_card.go # IoT 相关模型
|
||||
├── number_card.go # IoT 相关模型
|
||||
├── order.go # IoT 相关模型
|
||||
├── package.go # IoT 相关模型
|
||||
├── polling.go # IoT 相关模型
|
||||
└── system.go # IoT 相关模型
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- 符合项目架构约定(横向分层)
|
||||
- 所有模型统一管理,易于查找和维护
|
||||
- 导入路径统一(`internal/model`)
|
||||
- 符合 Go 语言扁平化包结构的最佳实践
|
||||
- 与项目现有架构保持一致
|
||||
|
||||
## 实施风险评估
|
||||
|
||||
### 风险等级:低
|
||||
|
||||
**理由:**
|
||||
1. **无代码引用**:经过搜索,当前项目中没有任何代码引用 `internal/iot/model`(因为 IoT 模块尚未完全实现)
|
||||
2. **纯文件移动**:只需要移动文件,不需要修改文件内容(包名已经是 `package model`)
|
||||
3. **无数据库影响**:模型位置变更不影响数据库表结构或迁移脚本
|
||||
4. **无 API 影响**:模型位置变更不影响 API 接口定义
|
||||
|
||||
### 潜在风险
|
||||
|
||||
**风险 1:并发开发冲突**
|
||||
- **描述**:如果在重构期间有其他开发者新增了对 `internal/iot/model` 的引用
|
||||
- **缓解措施**:在重构前和重构后都执行全局搜索,确保无遗漏引用
|
||||
- **恢复方案**:如果发现遗漏引用,只需要更新导入路径即可(从 `internal/iot/model` 改为 `internal/model`)
|
||||
|
||||
**风险 2:Git 历史追踪**
|
||||
- **描述**:Git 的 `git log` 或 `git blame` 可能无法自动追踪文件移动历史
|
||||
- **缓解措施**:使用 `git mv` 命令移动文件(或确保提交信息清晰说明文件移动)
|
||||
- **影响评估**:低(可以通过 `git log --follow` 追踪文件历史)
|
||||
|
||||
## 验收标准 (Acceptance Criteria)
|
||||
|
||||
1. **目录结构正确**:
|
||||
- [ ] `internal/iot/model/` 目录不存在
|
||||
- [ ] 所有 11 个 IoT 模型文件都在 `internal/model/` 目录下
|
||||
|
||||
2. **包名一致性**:
|
||||
- [ ] 所有模型文件的包名都是 `package model`
|
||||
|
||||
3. **无遗漏引用**:
|
||||
- [ ] 项目中不存在 `internal/iot/model` 的引用
|
||||
|
||||
4. **编译成功**:
|
||||
- [ ] 执行 `go build` 成功,无错误
|
||||
|
||||
5. **测试通过**:
|
||||
- [ ] 执行 `go test ./...` 成功(如果存在测试)
|
||||
|
||||
## 后续改进建议
|
||||
|
||||
虽然本次重构只涉及模型位置,但建议后续也考虑其他架构一致性改进:
|
||||
|
||||
1. **完善 IoT Handler 层**:创建 `internal/handler/iot/` 或 `internal/handler/iot_handler.go`
|
||||
2. **完善 IoT Service 层**:创建 `internal/service/iot/` 或 `internal/service/iot_service.go`
|
||||
3. **完善 IoT Store 层**:创建 `internal/store/postgres/iot_store.go`
|
||||
4. **删除空的 `internal/iot/` 目录**(如果迁移后为空)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- **项目架构规范**:`openspec/project.md`
|
||||
- **Go 包组织最佳实践**:[Effective Go - Package names](https://go.dev/doc/effective_go#package-names)
|
||||
- **Go Code Review Comments**:[Go Wiki - Package names](https://go.dev/wiki/CodeReviewComments#package-names)
|
||||
- **现有模型目录**:`internal/model/`
|
||||
- **IoT 规格文档**:`openspec/specs/iot-card/spec.md`
|
||||
@@ -0,0 +1,107 @@
|
||||
# 提案:将 IoT 模型迁移到统一的 internal/model/ 目录
|
||||
|
||||
## 动机 (Motivation)
|
||||
|
||||
当前 IoT 模块的数据模型被放置在 `internal/iot/model/` 目录下,这与项目的其他模型(如 Shop、Account、Enterprise 等)不一致。其他模型都统一放在 `internal/model/` 目录下。
|
||||
|
||||
**存在的问题:**
|
||||
|
||||
1. **目录结构不一致**:IoT 模型使用 `internal/iot/model/`,而其他模型使用 `internal/model/`,导致项目结构混乱
|
||||
2. **导入路径冗余**:引用 IoT 模型时需要写 `internal/iot/model`,而其他模型只需要 `internal/model`
|
||||
3. **违反项目约定**:根据 `openspec/project.md` 的架构规范,所有模型应该统一在 Model 层管理
|
||||
4. **可维护性下降**:新开发者容易困惑,不知道应该把模型放在哪里
|
||||
|
||||
**当前 IoT 模型文件清单(11 个文件):**
|
||||
|
||||
- `internal/iot/model/carrier.go` - 运营商模型
|
||||
- `internal/iot/model/commission.go` - 佣金模型
|
||||
- `internal/iot/model/data_usage.go` - 流量使用记录模型
|
||||
- `internal/iot/model/device.go` - 设备模型
|
||||
- `internal/iot/model/financial.go` - 财务记录模型
|
||||
- `internal/iot/model/iot_card.go` - IoT 卡模型
|
||||
- `internal/iot/model/number_card.go` - 号卡模型
|
||||
- `internal/iot/model/order.go` - 订单模型
|
||||
- `internal/iot/model/package.go` - 套餐模型
|
||||
- `internal/iot/model/polling.go` - 轮询配置模型
|
||||
- `internal/iot/model/system.go` - 系统配置模型
|
||||
|
||||
## 提案内容 (Proposed Change)
|
||||
|
||||
将所有 IoT 相关模型从 `internal/iot/model/` 迁移到 `internal/model/`,与项目的其他模型保持一致。
|
||||
|
||||
**迁移方案:**
|
||||
|
||||
1. 将 `internal/iot/model/` 目录下的所有 `.go` 文件移动到 `internal/model/`
|
||||
2. 所有文件的包名保持为 `package model`(无需修改)
|
||||
3. 删除空的 `internal/iot/model/` 目录
|
||||
4. 检查是否有其他代码引用了 `internal/iot/model`,如果有则更新导入路径(当前搜索未发现引用)
|
||||
5. 验证所有模型文件的包名一致性,确保都是 `package model`
|
||||
|
||||
**目标结构:**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/ # 统一的模型目录
|
||||
│ ├── account.go
|
||||
│ ├── shop.go
|
||||
│ ├── enterprise.go
|
||||
│ ├── personal_customer.go
|
||||
│ ├── role.go
|
||||
│ ├── permission.go
|
||||
│ ├── carrier.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── commission.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── data_usage.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── device.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── financial.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── iot_card.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── number_card.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── order.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── package.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── polling.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── system.go # ← 从 internal/iot/model/ 迁移
|
||||
│ ├── ...
|
||||
├── iot/ # IoT 业务逻辑目录
|
||||
│ ├── handler.go # (未来)IoT Handler 层
|
||||
│ ├── service.go # (未来)IoT Service 层
|
||||
│ └── store.go # (未来)IoT Store 层
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 影响范围 (Impact)
|
||||
|
||||
**好消息:** 经过代码搜索,目前 **没有发现任何代码引用 `internal/iot/model`**,因此这是一个零影响的重构。
|
||||
|
||||
**潜在风险:**
|
||||
|
||||
- 如果未来有代码在提案实施期间新增了对 `internal/iot/model` 的引用,需要同步更新
|
||||
- 数据库迁移脚本不受影响(模型位置变更不影响表结构)
|
||||
|
||||
**不影响的部分:**
|
||||
|
||||
- 数据库表结构
|
||||
- API 接口
|
||||
- 业务逻辑
|
||||
- 测试代码(如果存在)
|
||||
|
||||
## 实施计划 (Implementation Plan)
|
||||
|
||||
1. **移动文件**:将 11 个模型文件从 `internal/iot/model/` 移动到 `internal/model/`
|
||||
2. **删除空目录**:删除 `internal/iot/model/` 目录
|
||||
3. **验证包名**:确认所有模型文件的包名都是 `package model`
|
||||
4. **搜索引用**:再次搜索项目中是否有 `internal/iot/model` 的引用,如有则更新
|
||||
5. **运行测试**:执行 `go test ./...` 确保没有破坏性变更
|
||||
6. **构建验证**:执行 `go build` 确保项目可以正常编译
|
||||
|
||||
## 验收标准 (Acceptance Criteria)
|
||||
|
||||
- [x] 所有 IoT 模型文件已迁移到 `internal/model/` 目录
|
||||
- [x] `internal/iot/model/` 目录已删除
|
||||
- [x] 项目可以正常编译(`go build` 成功)
|
||||
- [x] 所有测试通过(如果存在测试)
|
||||
- [x] 项目中不存在 `internal/iot/model` 的引用(已更新文档中的引用)
|
||||
|
||||
## 参考资料 (References)
|
||||
|
||||
- 项目架构规范:`openspec/project.md`
|
||||
- 现有模型目录:`internal/model/`
|
||||
- IoT 相关规格:`openspec/specs/iot-card/spec.md`
|
||||
@@ -0,0 +1,171 @@
|
||||
# Model Organization
|
||||
|
||||
## Purpose
|
||||
|
||||
定义项目中数据模型(Model)的组织规范,确保所有模型遵循统一的目录结构和命名约定。
|
||||
|
||||
本规范支持:
|
||||
- 统一的模型目录结构(`internal/model/`)
|
||||
- 横向分层架构(Handler → Service → Store → Model)
|
||||
- 扁平化包组织(符合 Go 语言最佳实践)
|
||||
- 跨模块的模型共享和引用
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 统一的模型目录
|
||||
|
||||
系统 SHALL 将所有数据模型(GORM 模型、DTO)统一放置在 `internal/model/` 目录下,不按业务模块分散。
|
||||
|
||||
**核心原则:**
|
||||
- **横向分层**:模型层(Model)是全局统一的,不按业务模块纵向分割
|
||||
- **扁平化组织**:所有模型文件直接放在 `internal/model/` 目录下,不创建子目录(除非文件数量超过 50 个)
|
||||
- **统一导入路径**:所有代码引用模型时统一使用 `internal/model` 导入路径
|
||||
- **统一包名**:所有模型文件的包名统一为 `package model`
|
||||
|
||||
**目录结构:**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/ # 所有数据模型统一在这里
|
||||
│ ├── account.go # 账户模型
|
||||
│ ├── shop.go # 店铺模型
|
||||
│ ├── enterprise.go # 企业模型
|
||||
│ ├── personal_customer.go # 个人客户模型
|
||||
│ ├── role.go # 角色模型
|
||||
│ ├── permission.go # 权限模型
|
||||
│ ├── carrier.go # 运营商模型(IoT 相关)
|
||||
│ ├── iot_card.go # IoT 卡模型(IoT 相关)
|
||||
│ ├── device.go # 设备模型(IoT 相关)
|
||||
│ ├── order.go # 订单模型(IoT 相关)
|
||||
│ ├── package.go # 套餐模型(IoT 相关)
|
||||
│ └── ... # 其他模型
|
||||
├── handler/ # Handler 层(按功能分包)
|
||||
├── service/ # Service 层(按功能分包)
|
||||
└── store/ # Store 层(按功能分包)
|
||||
```
|
||||
|
||||
#### Scenario: 创建新的 IoT 相关模型
|
||||
|
||||
- **WHEN** 开发者需要创建新的 IoT 相关数据模型(如 `SIMCard`)
|
||||
- **THEN** 系统要求开发者在 `internal/model/sim_card.go` 创建模型,而不是在 `internal/iot/model/sim_card.go`
|
||||
|
||||
#### Scenario: 引用 IoT 模型
|
||||
|
||||
- **WHEN** Service 层或 Store 层需要引用 IoT 卡模型
|
||||
- **THEN** 系统使用统一的导入路径 `internal/model`,而不是 `internal/iot/model`
|
||||
|
||||
#### Scenario: 跨模块引用模型
|
||||
|
||||
- **WHEN** 用户模块(User)需要引用 IoT 卡模型(IotCard)进行关联查询
|
||||
- **THEN** 系统允许直接从 `internal/model` 导入 `IotCard`,因为所有模型都在同一个包中
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 模型文件命名规范
|
||||
|
||||
系统 SHALL 遵循统一的模型文件命名规范,确保文件名清晰、一致、易于查找。
|
||||
|
||||
**命名规则:**
|
||||
- 文件名使用小写下划线命名法(snake_case):`user_account.go`、`iot_card.go`、`shop_order.go`
|
||||
- 文件名应该清晰描述模型的业务含义,不使用缩写(除非是广泛认可的缩写如 `iot`、`http`)
|
||||
- 一个文件可以包含一个或多个相关模型(如 `iot_card.go` 可以包含 `IotCard` 和 `IotCardDTO`)
|
||||
- DTO 模型应该与主模型放在同一个文件中(如 `IotCard` 和 `IotCardDTO` 都在 `iot_card.go` 中)
|
||||
|
||||
**文件内容结构:**
|
||||
|
||||
```go
|
||||
package model
|
||||
|
||||
// IotCard IoT 卡模型(GORM 模型)
|
||||
type IotCard struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
ICCID string `gorm:"column:iccid;uniqueIndex" json:"iccid"`
|
||||
// ... 其他字段
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (IotCard) TableName() string {
|
||||
return "iot_cards"
|
||||
}
|
||||
|
||||
// IotCardDTO IoT 卡 DTO(数据传输对象)
|
||||
type IotCardDTO struct {
|
||||
ID uint `json:"id"`
|
||||
ICCID string `json:"iccid"`
|
||||
// ... 其他字段
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: 创建新模型时命名文件
|
||||
|
||||
- **WHEN** 开发者创建新的数据模型 `DeviceBinding`
|
||||
- **THEN** 系统要求文件名为 `device_binding.go`,而不是 `DeviceBinding.go` 或 `deviceBinding.go`
|
||||
|
||||
#### Scenario: DTO 模型放置位置
|
||||
|
||||
- **WHEN** 开发者为 `IotCard` 模型创建 DTO(`IotCardDTO`)
|
||||
- **THEN** 系统要求 DTO 定义在同一个文件 `iot_card.go` 中,而不是创建新文件 `iot_card_dto.go`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 禁止按业务模块分割模型
|
||||
|
||||
系统 SHALL 禁止按业务模块(如 `iot`、`user`、`order`)创建独立的模型子目录,所有模型必须扁平化组织在 `internal/model/` 下。
|
||||
|
||||
**禁止的目录结构:**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/
|
||||
│ ├── user/ # ❌ 禁止按业务模块分子目录
|
||||
│ │ └── user.go
|
||||
│ ├── iot/ # ❌ 禁止按业务模块分子目录
|
||||
│ │ └── iot_card.go
|
||||
│ └── order/ # ❌ 禁止按业务模块分子目录
|
||||
│ └── order.go
|
||||
```
|
||||
|
||||
```
|
||||
internal/
|
||||
├── user/ # ❌ 禁止按业务模块纵向分层
|
||||
│ ├── model.go
|
||||
│ ├── handler.go
|
||||
│ ├── service.go
|
||||
│ └── store.go
|
||||
├── iot/ # ❌ 禁止按业务模块纵向分层
|
||||
│ ├── model/
|
||||
│ │ └── iot_card.go
|
||||
│ ├── handler.go
|
||||
│ ├── service.go
|
||||
│ └── store.go
|
||||
```
|
||||
|
||||
**正确的目录结构(扁平化):**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/ # ✅ 所有模型扁平化在一个目录
|
||||
│ ├── user.go
|
||||
│ ├── iot_card.go
|
||||
│ └── order.go
|
||||
├── handler/ # ✅ 横向分层
|
||||
├── service/ # ✅ 横向分层
|
||||
└── store/ # ✅ 横向分层
|
||||
```
|
||||
|
||||
**设计理由:**
|
||||
1. **符合横向分层架构**:Handler → Service → Store → Model 是全局分层,不是模块分层
|
||||
2. **简化导入路径**:所有模型统一使用 `internal/model`,不需要记忆不同模块的路径
|
||||
3. **便于跨模块引用**:用户模块可以直接引用 IoT 模型,不需要跨包引用
|
||||
4. **符合 Go 语言惯用设计**:包应该按功能组织(model),而不是按业务模块组织(user/model, iot/model)
|
||||
|
||||
#### Scenario: 代码审查拒绝纵向分层
|
||||
|
||||
- **WHEN** 开发者提交 PR,创建了 `internal/iot/model/` 目录
|
||||
- **THEN** 系统要求代码审查拒绝该 PR,并要求开发者将模型移动到 `internal/model/`
|
||||
|
||||
#### Scenario: 重构现有纵向分层的模型
|
||||
|
||||
- **WHEN** 项目中存在 `internal/iot/model/` 目录
|
||||
- **THEN** 系统要求重构,将所有模型迁移到 `internal/model/`,删除 `internal/iot/model/` 目录
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
# 任务列表:IoT 模型位置重构
|
||||
|
||||
## 准备工作
|
||||
|
||||
- [x] **TASK-001**: 确认 `internal/model/` 目录已存在且可写
|
||||
- 验证方法:`ls -la internal/model/`
|
||||
- 预期输出:目录存在,包含现有模型文件
|
||||
- **结果**: ✅ 已确认
|
||||
|
||||
- [x] **TASK-002**: 确认 `internal/iot/model/` 目录包含 11 个模型文件
|
||||
- 验证方法:`ls internal/iot/model/ | wc -l`
|
||||
- 预期输出:11
|
||||
- **结果**: ✅ 已确认 11 个文件
|
||||
|
||||
- [x] **TASK-003**: 搜索项目中所有引用 `internal/iot/model` 的代码
|
||||
- 验证方法:`rg "internal/iot/model" internal/ --type go`
|
||||
- 预期输出:无结果(当前已验证)
|
||||
- **结果**: ✅ 无 Go 代码引用
|
||||
|
||||
## 迁移执行
|
||||
|
||||
- [x] **TASK-004**: 移动运营商模型文件
|
||||
- 命令:`git mv internal/iot/model/carrier.go internal/model/`
|
||||
- 验证:`test -f internal/model/carrier.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-005**: 移动佣金模型文件
|
||||
- 命令:`git mv internal/iot/model/commission.go internal/model/`
|
||||
- 验证:`test -f internal/model/commission.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-006**: 移动流量使用记录模型文件
|
||||
- 命令:`git mv internal/iot/model/data_usage.go internal/model/`
|
||||
- 验证:`test -f internal/model/data_usage.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-007**: 移动设备模型文件
|
||||
- 命令:`git mv internal/iot/model/device.go internal/model/`
|
||||
- 验证:`test -f internal/model/device.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-008**: 移动财务记录模型文件
|
||||
- 命令:`git mv internal/iot/model/financial.go internal/model/`
|
||||
- 验证:`test -f internal/model/financial.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-009**: 移动 IoT 卡模型文件
|
||||
- 命令:`git mv internal/iot/model/iot_card.go internal/model/`
|
||||
- 验证:`test -f internal/model/iot_card.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-010**: 移动号卡模型文件
|
||||
- 命令:`git mv internal/iot/model/number_card.go internal/model/`
|
||||
- 验证:`test -f internal/model/number_card.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-011**: 移动订单模型文件
|
||||
- 命令:`git mv internal/iot/model/order.go internal/model/`
|
||||
- 验证:`test -f internal/model/order.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-012**: 移动套餐模型文件
|
||||
- 命令:`git mv internal/iot/model/package.go internal/model/`
|
||||
- 验证:`test -f internal/model/package.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-013**: 移动轮询配置模型文件
|
||||
- 命令:`git mv internal/iot/model/polling.go internal/model/`
|
||||
- 验证:`test -f internal/model/polling.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
- [x] **TASK-014**: 移动系统配置模型文件
|
||||
- 命令:`git mv internal/iot/model/system.go internal/model/`
|
||||
- 验证:`test -f internal/model/system.go && echo "成功"`
|
||||
- **结果**: ✅ 已完成
|
||||
|
||||
## 清理工作
|
||||
|
||||
- [x] **TASK-015**: 验证 `internal/iot/model/` 目录已空
|
||||
- 验证方法:`ls internal/iot/model/`
|
||||
- 预期输出:无文件
|
||||
- **结果**: ✅ 目录为空
|
||||
|
||||
- [x] **TASK-016**: 删除空的 `internal/iot/model/` 目录
|
||||
- 命令:`rmdir internal/iot/model/`
|
||||
- 验证:`test ! -d internal/iot/model/ && echo "目录已删除"`
|
||||
- **结果**: ✅ 目录已删除
|
||||
|
||||
- [x] **TASK-017**: 检查 `internal/iot/` 目录是否还包含其他内容
|
||||
- 验证方法:`ls -la internal/iot/`
|
||||
- 如果为空,考虑删除 `internal/iot/` 目录
|
||||
- **结果**: ✅ 目录为空,但保留用于未来的 IoT Handler/Service/Store 层
|
||||
|
||||
## 验证工作
|
||||
|
||||
- [x] **TASK-018**: 再次搜索项目中所有引用 `internal/iot/model` 的代码
|
||||
- 验证方法:`rg "internal/iot/model" . --type go`
|
||||
- 预期输出:无结果
|
||||
- **结果**: ✅ 无 Go 代码引用
|
||||
|
||||
- [x] **TASK-019**: 验证所有迁移的模型文件包名正确
|
||||
- 验证方法:`grep "^package " internal/model/{carrier,commission,data_usage,device,financial,iot_card,number_card,order,package,polling,system}.go`
|
||||
- 预期输出:所有 11 个文件的包名都是 `package model`
|
||||
- **结果**: ✅ 所有文件包名统一为 `package model`
|
||||
|
||||
- [x] **TASK-020**: 运行 Go 代码格式检查
|
||||
- 命令:`go fmt ./...`
|
||||
- 预期输出:无需格式化(或格式化成功)
|
||||
- **结果**: ✅ 格式检查通过
|
||||
|
||||
- [x] **TASK-021**: 运行 Go 代码静态分析
|
||||
- 命令:`go vet ./...`
|
||||
- 预期输出:无错误
|
||||
- **结果**: ✅ 静态分析通过
|
||||
|
||||
- [x] **TASK-022**: 编译项目
|
||||
- 命令:`go build -o /tmp/junhong_cmp_fiber ./cmd/api`
|
||||
- 预期输出:编译成功,无错误
|
||||
- **结果**: ✅ 编译成功
|
||||
|
||||
- [x] **TASK-023**: 运行项目测试(如果存在)
|
||||
- 命令:`go test ./... -v`
|
||||
- 预期输出:所有测试通过(或无测试)
|
||||
- **结果**: ✅ 跳过(项目暂无测试)
|
||||
|
||||
## 文档更新
|
||||
|
||||
- [x] **TASK-024**: 检查是否需要更新项目文档
|
||||
- 验证方法:`rg "internal/iot/model" docs/ README.md CLAUDE.md 2>/dev/null`
|
||||
- 预期输出:无结果(或更新找到的文档)
|
||||
- **结果**: ✅ 已更新 `docs/iot-sim-management/表结构详细说明.md` 中的 25 处引用
|
||||
|
||||
- [x] **TASK-025**: 更新本次重构的总结文档
|
||||
- 创建 `docs/refactor-iot-model-location/` 目录
|
||||
- 编写重构总结(包含动机、影响、验证结果)
|
||||
- **结果**: ✅ 已完成,文档位于 `docs/refactor-iot-model-location/重构总结.md`
|
||||
|
||||
## 依赖关系
|
||||
|
||||
**并行任务:**
|
||||
- TASK-004 到 TASK-014 可以并行执行(移动文件操作互不依赖)
|
||||
|
||||
**串行依赖:**
|
||||
- TASK-001, TASK-002, TASK-003 必须在迁移前完成(准备工作)
|
||||
- TASK-004 到 TASK-014 必须在 TASK-015 之前完成(移动完成后才能验证)
|
||||
- TASK-015 必须在 TASK-016 之前完成(验证空目录后才能删除)
|
||||
- TASK-018 到 TASK-023 必须在 TASK-016 之后完成(清理完成后才能验证)
|
||||
|
||||
## 预计时间
|
||||
|
||||
- 准备工作:5 分钟
|
||||
- 迁移执行:5 分钟(自动化脚本)
|
||||
- 清理工作:2 分钟
|
||||
- 验证工作:5 分钟
|
||||
- 文档更新:10 分钟
|
||||
|
||||
**总计:约 30 分钟**
|
||||
162
openspec/specs/model-organization/spec.md
Normal file
162
openspec/specs/model-organization/spec.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# model-organization Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change refactor-iot-model-location. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: 统一的模型目录
|
||||
|
||||
系统 SHALL 将所有数据模型(GORM 模型、DTO)统一放置在 `internal/model/` 目录下,不按业务模块分散。
|
||||
|
||||
**核心原则:**
|
||||
- **横向分层**:模型层(Model)是全局统一的,不按业务模块纵向分割
|
||||
- **扁平化组织**:所有模型文件直接放在 `internal/model/` 目录下,不创建子目录(除非文件数量超过 50 个)
|
||||
- **统一导入路径**:所有代码引用模型时统一使用 `internal/model` 导入路径
|
||||
- **统一包名**:所有模型文件的包名统一为 `package model`
|
||||
|
||||
**目录结构:**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/ # 所有数据模型统一在这里
|
||||
│ ├── account.go # 账户模型
|
||||
│ ├── shop.go # 店铺模型
|
||||
│ ├── enterprise.go # 企业模型
|
||||
│ ├── personal_customer.go # 个人客户模型
|
||||
│ ├── role.go # 角色模型
|
||||
│ ├── permission.go # 权限模型
|
||||
│ ├── carrier.go # 运营商模型(IoT 相关)
|
||||
│ ├── iot_card.go # IoT 卡模型(IoT 相关)
|
||||
│ ├── device.go # 设备模型(IoT 相关)
|
||||
│ ├── order.go # 订单模型(IoT 相关)
|
||||
│ ├── package.go # 套餐模型(IoT 相关)
|
||||
│ └── ... # 其他模型
|
||||
├── handler/ # Handler 层(按功能分包)
|
||||
├── service/ # Service 层(按功能分包)
|
||||
└── store/ # Store 层(按功能分包)
|
||||
```
|
||||
|
||||
#### Scenario: 创建新的 IoT 相关模型
|
||||
|
||||
- **WHEN** 开发者需要创建新的 IoT 相关数据模型(如 `SIMCard`)
|
||||
- **THEN** 系统要求开发者在 `internal/model/sim_card.go` 创建模型,而不是在 `internal/iot/model/sim_card.go`
|
||||
|
||||
#### Scenario: 引用 IoT 模型
|
||||
|
||||
- **WHEN** Service 层或 Store 层需要引用 IoT 卡模型
|
||||
- **THEN** 系统使用统一的导入路径 `internal/model`,而不是 `internal/iot/model`
|
||||
|
||||
#### Scenario: 跨模块引用模型
|
||||
|
||||
- **WHEN** 用户模块(User)需要引用 IoT 卡模型(IotCard)进行关联查询
|
||||
- **THEN** 系统允许直接从 `internal/model` 导入 `IotCard`,因为所有模型都在同一个包中
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 模型文件命名规范
|
||||
|
||||
系统 SHALL 遵循统一的模型文件命名规范,确保文件名清晰、一致、易于查找。
|
||||
|
||||
**命名规则:**
|
||||
- 文件名使用小写下划线命名法(snake_case):`user_account.go`、`iot_card.go`、`shop_order.go`
|
||||
- 文件名应该清晰描述模型的业务含义,不使用缩写(除非是广泛认可的缩写如 `iot`、`http`)
|
||||
- 一个文件可以包含一个或多个相关模型(如 `iot_card.go` 可以包含 `IotCard` 和 `IotCardDTO`)
|
||||
- DTO 模型应该与主模型放在同一个文件中(如 `IotCard` 和 `IotCardDTO` 都在 `iot_card.go` 中)
|
||||
|
||||
**文件内容结构:**
|
||||
|
||||
```go
|
||||
package model
|
||||
|
||||
// IotCard IoT 卡模型(GORM 模型)
|
||||
type IotCard struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
ICCID string `gorm:"column:iccid;uniqueIndex" json:"iccid"`
|
||||
// ... 其他字段
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (IotCard) TableName() string {
|
||||
return "iot_cards"
|
||||
}
|
||||
|
||||
// IotCardDTO IoT 卡 DTO(数据传输对象)
|
||||
type IotCardDTO struct {
|
||||
ID uint `json:"id"`
|
||||
ICCID string `json:"iccid"`
|
||||
// ... 其他字段
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: 创建新模型时命名文件
|
||||
|
||||
- **WHEN** 开发者创建新的数据模型 `DeviceBinding`
|
||||
- **THEN** 系统要求文件名为 `device_binding.go`,而不是 `DeviceBinding.go` 或 `deviceBinding.go`
|
||||
|
||||
#### Scenario: DTO 模型放置位置
|
||||
|
||||
- **WHEN** 开发者为 `IotCard` 模型创建 DTO(`IotCardDTO`)
|
||||
- **THEN** 系统要求 DTO 定义在同一个文件 `iot_card.go` 中,而不是创建新文件 `iot_card_dto.go`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 禁止按业务模块分割模型
|
||||
|
||||
系统 SHALL 禁止按业务模块(如 `iot`、`user`、`order`)创建独立的模型子目录,所有模型必须扁平化组织在 `internal/model/` 下。
|
||||
|
||||
**禁止的目录结构:**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/
|
||||
│ ├── user/ # ❌ 禁止按业务模块分子目录
|
||||
│ │ └── user.go
|
||||
│ ├── iot/ # ❌ 禁止按业务模块分子目录
|
||||
│ │ └── iot_card.go
|
||||
│ └── order/ # ❌ 禁止按业务模块分子目录
|
||||
│ └── order.go
|
||||
```
|
||||
|
||||
```
|
||||
internal/
|
||||
├── user/ # ❌ 禁止按业务模块纵向分层
|
||||
│ ├── model.go
|
||||
│ ├── handler.go
|
||||
│ ├── service.go
|
||||
│ └── store.go
|
||||
├── iot/ # ❌ 禁止按业务模块纵向分层
|
||||
│ ├── model/
|
||||
│ │ └── iot_card.go
|
||||
│ ├── handler.go
|
||||
│ ├── service.go
|
||||
│ └── store.go
|
||||
```
|
||||
|
||||
**正确的目录结构(扁平化):**
|
||||
|
||||
```
|
||||
internal/
|
||||
├── model/ # ✅ 所有模型扁平化在一个目录
|
||||
│ ├── user.go
|
||||
│ ├── iot_card.go
|
||||
│ └── order.go
|
||||
├── handler/ # ✅ 横向分层
|
||||
├── service/ # ✅ 横向分层
|
||||
└── store/ # ✅ 横向分层
|
||||
```
|
||||
|
||||
**设计理由:**
|
||||
1. **符合横向分层架构**:Handler → Service → Store → Model 是全局分层,不是模块分层
|
||||
2. **简化导入路径**:所有模型统一使用 `internal/model`,不需要记忆不同模块的路径
|
||||
3. **便于跨模块引用**:用户模块可以直接引用 IoT 模型,不需要跨包引用
|
||||
4. **符合 Go 语言惯用设计**:包应该按功能组织(model),而不是按业务模块组织(user/model, iot/model)
|
||||
|
||||
#### Scenario: 代码审查拒绝纵向分层
|
||||
|
||||
- **WHEN** 开发者提交 PR,创建了 `internal/iot/model/` 目录
|
||||
- **THEN** 系统要求代码审查拒绝该 PR,并要求开发者将模型移动到 `internal/model/`
|
||||
|
||||
#### Scenario: 重构现有纵向分层的模型
|
||||
|
||||
- **WHEN** 项目中存在 `internal/iot/model/` 目录
|
||||
- **THEN** 系统要求重构,将所有模型迁移到 `internal/model/`,删除 `internal/iot/model/` 目录
|
||||
|
||||
Reference in New Issue
Block a user