Files
junhong_cmp_fiber/.planning/phases/03-device-system/03-01-PLAN.md

404 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 03-device-system
plan: "01"
type: execute
wave: 1
depends_on: []
files_modified:
- migrations/000090_device_fields_extension.up.sql
- migrations/000090_device_fields_extension.down.sql
- migrations/000091_device_sim_binding_is_current.up.sql
- migrations/000091_device_sim_binding_is_current.down.sql
- internal/model/device.go
- internal/model/device_sim_binding.go
- internal/gateway/models.go
- internal/gateway/device.go
- internal/model/dto/device_dto.go
- internal/model/dto/asset_dto.go
autonomous: true
requirements:
- DEVICE-01
- DEVICE-02
- DEVICE-03
must_haves:
truths:
- "tb_device 表含 online_status / last_online_time / software_version / switch_mode / last_gateway_sync_at 字段"
- "tb_device_sim_binding 表含 is_current 字段"
- "Gateway Client 具备 SyncDeviceInfo() 方法"
- "DeviceResponse DTO 新增 5 个字段BoundCardInfo + DeviceCardBindingResponse 新增 is_current"
- "make migrate-up 执行成功go build ./... 编译通过"
artifacts:
- path: "migrations/000090_device_fields_extension.up.sql"
provides: "tb_device 字段扩展迁移"
- path: "migrations/000091_device_sim_binding_is_current.up.sql"
provides: "tb_device_sim_binding.is_current 迁移"
- path: "internal/model/device.go"
provides: "Device 模型新增 5 字段"
- path: "internal/model/device_sim_binding.go"
provides: "DeviceSimBinding 模型新增 is_current"
- path: "internal/gateway/models.go"
provides: "SyncDeviceInfoReq / SyncDeviceInfoResp 结构定义"
- path: "internal/gateway/device.go"
provides: "SyncDeviceInfo() 方法"
- path: "internal/model/dto/device_dto.go"
provides: "DeviceResponse + DeviceCardBindingResponse 字段扩展"
- path: "internal/model/dto/asset_dto.go"
provides: "BoundCardInfo.IsCurrent 字段扩展"
key_links:
- from: "internal/gateway/device.go:SyncDeviceInfo"
to: "internal/gateway/client.go:doRequestWithResponse"
via: "泛型调用"
pattern: "doRequestWithResponse\\[SyncDeviceInfoResp\\]"
- from: "internal/model/device.go:Device"
to: "migrations/000090"
via: "字段对应迁移"
pattern: "OnlineStatus.*online_status"
---
<objective>
建立设备体系的数据基础层:扩展 DB 字段DEVICE-01、新增 is_current 绑定标识DEVICE-03、对接 Gateway sync-info 接口DEVICE-02
Purpose: Plan 2 的 Refresh 接入依赖本 Plan 的全部产出(模型字段 + Gateway 方法 + DTO 定义)
Output: 2 个迁移对、Device/DeviceSimBinding 模型更新、Gateway SyncDeviceInfo 方法、DTO 扩展
</objective>
<execution_context>
@$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md
@$HOME/.config/opencode/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-device-system/03-CONTEXT.md
<interfaces>
<!-- 现有代码契约executor 直接使用,无需探索代码库 -->
**internal/gateway/device.go现有方法**
```go
func (c *Client) GetDeviceInfo(ctx context.Context, req *DeviceInfoReq) (*DeviceInfoResp, error)
func (c *Client) GetSlotInfo(ctx context.Context, req *DeviceInfoReq) (*SlotInfoResp, error)
// 其他方法略
```
**internal/gateway/client.go泛型请求方法**
```go
// 签名:
func doRequestWithResponse[T any](c *Client, ctx context.Context, path string, params interface{}) (*T, error)
// 用法(参考 GetDeviceInfo
return doRequestWithResponse[DeviceInfoResp](c, ctx, "/device/info", req)
```
**internal/gateway/models.go 中已有的设备 DTO参考风格**
```go
type DeviceInfoReq struct {
CardNo string `json:"cardNo,omitempty" description:"..."`
DeviceID string `json:"deviceId,omitempty" description:"..."`
}
type DeviceInfoResp struct {
IMEI string `json:"imei" description:"..."`
OnlineStatus int `json:"onlineStatus" description:"..."`
// ...
}
```
**internal/model/device.go 现有字段(末尾追加新字段):**
```go
type Device struct {
gorm.Model
BaseModel `gorm:"embedded"`
VirtualNo string `gorm:"column:virtual_no;..."`
IMEI string `gorm:"column:imei;type:varchar(20);..."`
SN string `gorm:"column:sn;type:varchar(100);..."`
// ... 其余字段 ...
Generation int `gorm:"column:generation;type:int;not null;default:1;..."`
// 在 Generation 之后追加 5 个新字段
}
```
**internal/model/device_sim_binding.go 现有字段(末尾追加 is_current**
```go
type DeviceSimBinding struct {
gorm.Model
BaseModel `gorm:"embedded"`
DeviceID uint `gorm:"column:device_id;..."`
IotCardID uint `gorm:"column:iot_card_id;..."`
SlotPosition int `gorm:"column:slot_position;..."`
BindStatus int `gorm:"column:bind_status;..."`
BindTime *time.Time `gorm:"column:bind_time;..."`
UnbindTime *time.Time `gorm:"column:unbind_time;..."`
// 追加 IsCurrent
}
```
**internal/model/dto/device_dto.go 中 DeviceResponse 和 DeviceCardBindingResponse**
```go
type DeviceResponse struct {
// ... 现有字段 ...
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
// 在 UpdatedAt 之后追加 5 个新字段
}
type DeviceCardBindingResponse struct {
// ... 现有字段(末尾是 BindTime...
BindTime *time.Time `json:"bind_time,omitempty" description:"绑定时间"`
// 追加 IsCurrent
}
```
**internal/model/dto/asset_dto.go 中 BoundCardInfo**
```go
type BoundCardInfo struct {
CardID uint `json:"card_id" description:"卡ID"`
ICCID string `json:"iccid" description:"ICCID"`
MSISDN string `json:"msisdn,omitempty" description:"手机号"`
NetworkStatus int `json:"network_status" description:"网络状态0停机 1开机"`
RealNameStatus int `json:"real_name_status" description:"实名状态"`
SlotPosition int `json:"slot_position,omitempty" description:"插槽位置"`
// 追加 IsCurrent
}
```
**Gateway sync-info 接口:**
- URL`POST /v1/iot/openApi/device/sync-info`(业务路径参数传 `/device/sync-info`
- cardNoICCID>15位/ IMEI=15位/ SN=11位
- 关键响应字段current_iccid, software_version, switch_mode, online_status, last_online_timeISO 8601 string, last_update_time
**最新迁移编号**`000089_realname_expiry_base`,新迁移从 `000090` 开始
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: DB 迁移 + 模型字段扩展D-0 + D-2 DB 层)</name>
<files>
migrations/000090_device_fields_extension.up.sql,
migrations/000090_device_fields_extension.down.sql,
migrations/000091_device_sim_binding_is_current.up.sql,
migrations/000091_device_sim_binding_is_current.down.sql,
internal/model/device.go,
internal/model/device_sim_binding.go
</files>
<action>
## 迁移文件 000090tb_device 字段扩展)
创建 `migrations/000090_device_fields_extension.up.sql`
```sql
-- 扩展 tb_device 设备表,新增 Gateway sync-info 同步的存储字段
-- 这些字段有"离线意义"设备下线后仍需保留最后状态非实时字段rssi/battery 等)不入库
ALTER TABLE tb_device
ADD COLUMN online_status INT NOT NULL DEFAULT 0,
ADD COLUMN last_online_time TIMESTAMP NULL,
ADD COLUMN software_version VARCHAR(100) NOT NULL DEFAULT '',
ADD COLUMN switch_mode VARCHAR(10) NOT NULL DEFAULT '0',
ADD COLUMN last_gateway_sync_at TIMESTAMP NULL;
COMMENT ON COLUMN tb_device.online_status IS '在线状态0未知1在线2离线';
COMMENT ON COLUMN tb_device.last_online_time IS '最后在线时间(来自 Gateway sync-info';
COMMENT ON COLUMN tb_device.software_version IS '固件版本号';
COMMENT ON COLUMN tb_device.switch_mode IS '切卡模式0=自动1=手动';
COMMENT ON COLUMN tb_device.last_gateway_sync_at IS '最后一次 sync-info 同步时间';
```
创建 `migrations/000090_device_fields_extension.down.sql`
```sql
ALTER TABLE tb_device
DROP COLUMN IF EXISTS online_status,
DROP COLUMN IF EXISTS last_online_time,
DROP COLUMN IF EXISTS software_version,
DROP COLUMN IF EXISTS switch_mode,
DROP COLUMN IF EXISTS last_gateway_sync_at;
```
## 迁移文件 000091tb_device_sim_binding 新增 is_current
创建 `migrations/000091_device_sim_binding_is_current.up.sql`
```sql
-- 新增 is_current 字段,标识设备当前正在使用的卡(由 sync-info current_iccid 字段决定)
ALTER TABLE tb_device_sim_binding
ADD COLUMN is_current BOOLEAN NOT NULL DEFAULT FALSE;
COMMENT ON COLUMN tb_device_sim_binding.is_current IS '是否为当前使用的卡(同一设备同一时刻最多一条为 true';
```
创建 `migrations/000091_device_sim_binding_is_current.down.sql`
```sql
ALTER TABLE tb_device_sim_binding
DROP COLUMN IF EXISTS is_current;
```
## 执行迁移
执行 `make migrate-up` 并确认:
- `make migrate-version` 输出版本为 91dirty=false
- 使用 PostgreSQL MCP 验证 tb_device 新增 5 列、tb_device_sim_binding 新增 is_current
## 更新 internal/model/device.go
`Generation` 字段之后追加 5 个字段(保持与其他字段相同的标签格式):
```go
// 以下字段由 Gateway sync-info 接口同步,有离线存储意义
OnlineStatus int `gorm:"column:online_status;type:int;not null;default:0;comment:在线状态0未知 1在线 2离线" json:"online_status"`
LastOnlineTime *time.Time `gorm:"column:last_online_time;comment:最后在线时间(来自 Gateway sync-info" json:"last_online_time,omitempty"`
SoftwareVersion string `gorm:"column:software_version;type:varchar(100);not null;default:'';comment:固件版本号" json:"software_version"`
SwitchMode string `gorm:"column:switch_mode;type:varchar(10);not null;default:'0';comment:切卡模式0=自动 1=手动" json:"switch_mode"`
LastGatewaySyncAt *time.Time `gorm:"column:last_gateway_sync_at;comment:最后一次 sync-info 同步时间" json:"last_gateway_sync_at,omitempty"`
```
## 更新 internal/model/device_sim_binding.go
`UnbindTime` 字段之后追加:
```go
IsCurrent bool `gorm:"column:is_current;type:boolean;not null;default:false;comment:是否为当前使用的卡" json:"is_current"`
```
</action>
<verify>
<automated>make migrate-up && make migrate-version && go build ./...</automated>
</verify>
<done>
- migrate-version 显示版本 91dirty=false
- go build ./... 编译无新增错误
- tb_device 含 5 个新字段tb_device_sim_binding 含 is_current可用 MCP dbhub_search_objects 验证)
</done>
</task>
<task type="auto">
<name>Task 2: Gateway SyncDeviceInfo 方法 + DTO 字段扩展D-1 + D-2 DTO 层)</name>
<files>
internal/gateway/models.go,
internal/gateway/device.go,
internal/model/dto/device_dto.go,
internal/model/dto/asset_dto.go
</files>
<action>
## internal/gateway/models.go — 新增 SyncDeviceInfo 请求/响应结构
在文件末尾(`// ============ 结束 ============` 之前或文件末)追加,放在「设备相关 DTO」分组下
```go
// SyncDeviceInfoReq sync-info 同步查询设备信息请求
// cardNo 规则:>15 位用 ICCID=15 位用 IMEI=11 位用 SN
type SyncDeviceInfoReq struct {
CardNo string `json:"card_no" description:"设备标识ICCID/IMEI/SN"`
}
// SyncDeviceInfoResp sync-info 同步查询设备信息响应(对应 Gateway data 字段)
// 注意:所有字段均可能为 null/零值,表示暂无数据
type SyncDeviceInfoResp struct {
DeviceID string `json:"device_id" description:"设备IDIMEI/SN"`
DeviceName string `json:"device_name" description:"设备名称"`
IMEI string `json:"imei" description:"IMEI号"`
CurrentIccid string `json:"current_iccid" description:"当前使用的ICCID"`
DeviceType string `json:"device_type" description:"设备类型1=诺行2=玺龙3=迎势达"`
SoftwareVersion string `json:"software_version" description:"软件版本号"`
MacAddress string `json:"mac_address" description:"MAC地址"`
SSID string `json:"ssid" description:"WiFi热点名称"`
WifiEnabled bool `json:"wifi_enabled" description:"WiFi开关状态"`
SwitchMode string `json:"switch_mode" description:"切卡模式0=自动1=手动"`
RSSI string `json:"rssi" description:"接收信号强度"`
BatteryLevel *int `json:"battery_level" description:"电池电量百分比无电池时为null"`
OnlineStatus int `json:"online_status" description:"在线状态1=在线2=离线"`
LastUpdateTime string `json:"last_update_time" description:"设备信息最后更新时间ISO 8601"`
LastOnlineTime string `json:"last_online_time" description:"设备最后在线时间ISO 8601"`
RunTime string `json:"run_time" description:"本次开机运行时间(秒)"`
DailyUsage string `json:"daily_usage" description:"日使用流量(字节)"`
}
```
## internal/gateway/device.go — 新增 SyncDeviceInfo 方法
在文件末尾追加:
```go
// SyncDeviceInfo 同步查询设备信息(直接返回,无需回调)
// 与异步 /device/info 的区别:直接返回全量数据,无需 callbackUrl
// POST /device/sync-info
func (c *Client) SyncDeviceInfo(ctx context.Context, req *SyncDeviceInfoReq) (*SyncDeviceInfoResp, error) {
if req.CardNo == "" {
return nil, errors.New(errors.CodeInvalidParam, "cardNo 不能为空")
}
return doRequestWithResponse[SyncDeviceInfoResp](c, ctx, "/device/sync-info", req)
}
```
更新文件顶部包注释:「提供设备相关的 8 个 API 方法封装」(从 7 改为 8
## internal/model/dto/device_dto.go — DeviceResponse 和 DeviceCardBindingResponse 字段扩展
**DeviceResponse** — 在 `UpdatedAt` 字段之后追加per D-0
```go
// 设备实时同步字段(由 sync-info 接口写入)
OnlineStatus int `json:"online_status" description:"在线状态0未知 1在线 2离线"`
LastOnlineTime *time.Time `json:"last_online_time,omitempty" description:"最后在线时间"`
SoftwareVersion string `json:"software_version" description:"固件版本号"`
SwitchMode string `json:"switch_mode" description:"切卡模式0=自动1=手动"`
LastGatewaySyncAt *time.Time `json:"last_gateway_sync_at,omitempty" description:"最后 sync-info 同步时间"`
```
**DeviceCardBindingResponse** — 在 `BindTime` 字段之后追加per D-2
```go
IsCurrent bool `json:"is_current" description:"是否为当前使用的卡"`
```
## internal/model/dto/asset_dto.go — BoundCardInfo 字段扩展
`SlotPosition` 字段之后追加per D-2D-11 决策):
```go
IsCurrent bool `json:"is_current" description:"是否为当前使用的卡"`
```
## 验证
执行 `go build ./...`,确认无编译错误。
</action>
<verify>
<automated>go build ./...</automated>
</verify>
<done>
- SyncDeviceInfoReq / SyncDeviceInfoResp 在 internal/gateway/models.go 中定义
- SyncDeviceInfo() 方法在 internal/gateway/device.go 中实现
- DeviceResponse 含 5 个新字段DeviceCardBindingResponse 含 is_current
- BoundCardInfo 含 is_current
- go build ./... 零新增错误
</done>
</task>
</tasks>
<verification>
```bash
# 1. 迁移正常
make migrate-version
# 期望version=91, dirty=false
# 2. 编译通过
go build ./...
# 3. 字段验证(通过 PostgreSQL MCP
# dbhub_search_objects: object_type=column, table=tb_device
# 确认online_status / last_online_time / software_version / switch_mode / last_gateway_sync_at
# dbhub_search_objects: object_type=column, table=tb_device_sim_binding
# 确认is_current
# 4. Gateway 方法搜索
grep -r "SyncDeviceInfo" internal/gateway/
```
</verification>
<success_criteria>
- [ ] `make migrate-version` 输出 version=91, dirty=false
- [ ] `go build ./...` 零新增编译错误
- [ ] `internal/gateway/device.go``SyncDeviceInfo()` 方法
- [ ] `internal/gateway/models.go``SyncDeviceInfoReq` / `SyncDeviceInfoResp`
- [ ] `DeviceResponse` 含 5 个新字段,`DeviceCardBindingResponse``is_current`
- [ ] `BoundCardInfo``is_current`
</success_criteria>
<output>
完成后创建 `.planning/phases/03-device-system/03-01-SUMMARY.md`
</output>