package models import ( "encoding/json" "time" ) // License 许可证模型 type License struct { ID uint `gorm:"primarykey" json:"id"` LicenseKey string `gorm:"column:license_key;uniqueIndex;not null" json:"key"` BoundDevices string `gorm:"type:text" json:"bound_devices"` // JSON 数组字符串,例如 '["uuid-1", "uuid-2"]' DeviceActivations string `gorm:"type:text" json:"device_activations"` // JSON 对象字符串,记录首次激活时间,例如 '{"uuid-1": "2024-01-01T00:00:00Z", "uuid-2": "2024-01-02T00:00:00Z"}' DeviceHeartbeats string `gorm:"type:text" json:"device_heartbeats"` // JSON 对象字符串,记录心跳时间(最后验证时间),例如 '{"uuid-1": "2024-01-01T12:00:00Z", "uuid-2": "2024-01-02T12:00:00Z"}' MaxDevices int `gorm:"default:2" json:"max_devices"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // GetBoundDeviceList 获取已绑定设备ID列表 func (l *License) GetBoundDeviceList() ([]string, error) { if l.BoundDevices == "" { return []string{}, nil } var devices []string err := json.Unmarshal([]byte(l.BoundDevices), &devices) return devices, err } // SetBoundDeviceList 设置已绑定设备ID列表 func (l *License) SetBoundDeviceList(devices []string) error { data, err := json.Marshal(devices) if err != nil { return err } l.BoundDevices = string(data) return nil } // IsDeviceBound 检查设备是否已绑定 func (l *License) IsDeviceBound(deviceID string) (bool, error) { devices, err := l.GetBoundDeviceList() if err != nil { return false, err } for _, d := range devices { if d == deviceID { return true, nil } } return false, nil } // AddDevice 添加设备到绑定列表 func (l *License) AddDevice(deviceID string) error { devices, err := l.GetBoundDeviceList() if err != nil { return err } devices = append(devices, deviceID) if err := l.SetBoundDeviceList(devices); err != nil { return err } // 记录设备激活时间 return l.RecordDeviceActivation(deviceID) } // GetDeviceActivations 获取设备激活时间映射 func (l *License) GetDeviceActivations() (map[string]time.Time, error) { if l.DeviceActivations == "" { return make(map[string]time.Time), nil } var activations map[string]string if err := json.Unmarshal([]byte(l.DeviceActivations), &activations); err != nil { return nil, err } result := make(map[string]time.Time) for deviceID, timeStr := range activations { t, err := time.Parse(time.RFC3339, timeStr) if err != nil { return nil, err } result[deviceID] = t } return result, nil } // RecordDeviceActivation 记录设备激活时间(仅在首次激活时记录) func (l *License) RecordDeviceActivation(deviceID string) error { activations, err := l.GetDeviceActivations() if err != nil { activations = make(map[string]time.Time) } // 如果设备已存在,不更新激活时间;如果不存在,记录当前时间 if _, exists := activations[deviceID]; !exists { activations[deviceID] = time.Now() // 转换为字符串格式存储 activationsStr := make(map[string]string) for id, t := range activations { activationsStr[id] = t.Format(time.RFC3339) } data, err := json.Marshal(activationsStr) if err != nil { return err } l.DeviceActivations = string(data) } return nil } // GetDeviceHeartbeats 获取设备心跳时间映射 func (l *License) GetDeviceHeartbeats() (map[string]time.Time, error) { if l.DeviceHeartbeats == "" { return make(map[string]time.Time), nil } var heartbeats map[string]string if err := json.Unmarshal([]byte(l.DeviceHeartbeats), &heartbeats); err != nil { return nil, err } result := make(map[string]time.Time) for deviceID, timeStr := range heartbeats { t, err := time.Parse(time.RFC3339, timeStr) if err != nil { return nil, err } result[deviceID] = t } return result, nil } // UpdateDeviceHeartbeat 更新设备心跳时间(每次验证时更新) func (l *License) UpdateDeviceHeartbeat(deviceID string) error { heartbeats, err := l.GetDeviceHeartbeats() if err != nil { heartbeats = make(map[string]time.Time) } // 更新心跳时间为当前时间 heartbeats[deviceID] = time.Now() // 转换为字符串格式存储 heartbeatsStr := make(map[string]string) for id, t := range heartbeats { heartbeatsStr[id] = t.Format(time.RFC3339) } data, err := json.Marshal(heartbeatsStr) if err != nil { return err } l.DeviceHeartbeats = string(data) return nil } // CanBindMoreDevices 检查是否可以绑定更多设备 func (l *License) CanBindMoreDevices() (bool, error) { devices, err := l.GetBoundDeviceList() if err != nil { return false, err } return len(devices) < l.MaxDevices, nil }