package license import ( "backend/internal/dto" "backend/internal/model" "backend/utils" "errors" "time" ) func inferPlanType(lic model.License) string { if lic.IsTrial { return "trial" } if lic.Duration <= 0 || lic.EndDate.Equal(model.PermanentEndDate) || lic.EndDate.Year() >= 2099 { return "perpetual" } if lic.Duration <= 180 { return "trial" } if lic.Duration <= 31 { return "monthly" } return "yearly" } func licenseClientPayload(lic model.License) map[string]interface{} { return map[string]interface{}{ "expires_at": lic.EndDate.Format("2006-01-02 15:04:05"), "days_left": utils.CalculateDaysLeft(lic.EndDate), "max_devices": lic.MaxActivations, "plan_type": inferPlanType(lic), } } // Verify 客户端 License 验证 // Logan.Li func (s *LicenseService) Verify(req dto.LicenseVerifyRequest) (*dto.LicenseVerifyResponse, error) { var lic model.License if err := s.db.Preload("Product").Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil { return &dto.LicenseVerifyResponse{Valid: false, Message: "无效的 License"}, nil } if req.ProductID > 0 && lic.ProductID != req.ProductID { return &dto.LicenseVerifyResponse{Valid: false, Message: "License 与产品不匹配"}, nil } switch lic.Status { case model.LicenseStatusRevoked: return &dto.LicenseVerifyResponse{Valid: false, Message: "License 已被吊销"}, nil case model.LicenseStatusSuspended: return &dto.LicenseVerifyResponse{Valid: false, Message: "License 已被暂停"}, nil case model.LicenseStatusExpired: return &dto.LicenseVerifyResponse{Valid: false, Message: "License 已过期"}, nil } if utils.IsLicenseExpired(lic.EndDate) { lic.Status = model.LicenseStatusExpired s.db.Save(&lic) return &dto.LicenseVerifyResponse{Valid: false, Message: "License 已过期"}, nil } return &dto.LicenseVerifyResponse{ Valid: true, LicenseKey: lic.LicenseKey, ProductID: lic.ProductID, ProductName: lic.Product.Name, ProductCode: lic.Product.Code, Features: utils.ParseFeatures(lic.Product.Features), Status: lic.Status, ExpiresAt: lic.EndDate.Format("2006-01-02 15:04:05"), DaysLeft: utils.CalculateDaysLeft(lic.EndDate), MaxDevices: lic.MaxActivations, PlanType: inferPlanType(lic), Message: "验证成功", }, nil } // Activate 客户端激活 License(机器绑定) // Logan.Li func (s *LicenseService) Activate(req dto.LicenseActivateRequest, clientIP string) (map[string]interface{}, error) { var lic model.License if err := s.db.Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil { return nil, errors.New("无效的 License") } if lic.Status == model.LicenseStatusRevoked { return nil, errors.New("License 已被吊销") } if lic.Status == model.LicenseStatusExpired || utils.IsLicenseExpired(lic.EndDate) { return nil, errors.New("License 已过期") } var existing model.Activation if err := s.db.Where("license_id = ? AND device_id = ?", lic.ID, req.DeviceID).First(&existing).Error; err == nil { existing.LastSeenAt = time.Now() existing.IsOnline = true existing.DeviceName = req.DeviceName existing.DeviceInfo = req.DeviceInfo existing.IP = clientIP s.db.Save(&existing) return licenseClientPayload(lic), nil } if lic.CurrentActivations >= lic.MaxActivations { return nil, errors.New("已达到最大激活数量限制") } activation := model.Activation{ LicenseID: lic.ID, DeviceID: req.DeviceID, DeviceName: req.DeviceName, DeviceInfo: req.DeviceInfo, IP: clientIP, Status: "active", IsOnline: true, ActivatedAt: time.Now(), LastSeenAt: time.Now(), } if err := s.db.Create(&activation).Error; err != nil { return nil, errors.New("激活失败") } lic.CurrentActivations++ if lic.Status == model.LicenseStatusInactive { lic.Status = model.LicenseStatusActive } s.db.Save(&lic) return licenseClientPayload(lic), nil } // Deactivate 客户端停用 // Logan.Li func (s *LicenseService) Deactivate(req dto.LicenseDeactivateRequest) error { var lic model.License if err := s.db.Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil { return errors.New("无效的 License") } result := s.db.Model(&model.Activation{}). Where("license_id = ? AND device_id = ?", lic.ID, req.DeviceID). Update("status", "inactive") if result.RowsAffected > 0 && lic.CurrentActivations > 0 { lic.CurrentActivations-- s.db.Save(&lic) } return nil } // Heartbeat 客户端心跳 // Logan.Li func (s *LicenseService) Heartbeat(req dto.LicenseHeartbeatRequest) (map[string]interface{}, error) { var lic model.License if err := s.db.Where("license_key = ?", req.LicenseKey).First(&lic).Error; err != nil { return nil, errors.New("无效的 License") } if lic.Status == model.LicenseStatusRevoked { return nil, errors.New("License 已被吊销") } if lic.Status == model.LicenseStatusExpired || utils.IsLicenseExpired(lic.EndDate) { return nil, errors.New("License 已过期") } s.db.Model(&model.Activation{}). Where("license_id = ? AND device_id = ?", lic.ID, req.DeviceID). Updates(map[string]interface{}{"last_seen_at": time.Now(), "is_online": true}) return licenseClientPayload(lic), nil } // ListDevicesByKey returns activations for a license key (client self-service). // Logan.Li func (s *LicenseService) ListDevicesByKey(licenseKey string) ([]dto.ActivationResponse, int, error) { var lic model.License if err := s.db.Where("license_key = ?", licenseKey).First(&lic).Error; err != nil { return nil, 0, errors.New("无效的 License") } var activations []model.Activation if err := s.db.Where("license_id = ? AND status = ?", lic.ID, "active").Order("activated_at DESC").Find(&activations).Error; err != nil { return nil, 0, errors.New("获取设备列表失败") } results := make([]dto.ActivationResponse, 0, len(activations)) for _, a := range activations { results = append(results, toActivationResponse(a)) } return results, lic.MaxActivations, nil }