package service import ( "crypto/hmac" "crypto/sha256" "encoding/base32" "errors" "fmt" "strings" "time" "gorm.io/gorm" "github.com/wangjia/jiu/backend/config" "github.com/wangjia/jiu/backend/internal/model" ) var ( ErrLicenseNotFound = errors.New("license not found") ErrLicenseInactive = errors.New("license is inactive") ErrLicenseExpired = errors.New("license has expired") ErrDeviceMismatch = errors.New("license is bound to another device") ) type LicenseService struct { db *gorm.DB } func NewLicenseService(db *gorm.DB) *LicenseService { return &LicenseService{db: db} } // GenerateKey 生成许可证激活码 // 格式:HMAC-SHA256(shopID+licenseType+expiry, secret) → base32, 每5字符加'-' func GenerateKey(shopID uint64, licenseType string, expiresAt *time.Time) string { payload := fmt.Sprintf("%d:%s", shopID, licenseType) if expiresAt != nil { payload += ":" + expiresAt.Format("20060102") } mac := hmac.New(sha256.New, []byte(config.C.License.HMACSecret)) mac.Write([]byte(payload)) raw := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(mac.Sum(nil)) // 截取前20字符,分4段,每段5字符 raw = strings.ToUpper(raw)[:20] return fmt.Sprintf("%s-%s-%s-%s", raw[0:5], raw[5:10], raw[10:15], raw[15:20]) } // Activate 激活许可证(绑定设备) func (s *LicenseService) Activate(licenseKey, deviceID string) (*model.License, error) { var lic model.License if err := s.db.Where("license_key = ?", licenseKey).First(&lic).Error; err != nil { return nil, ErrLicenseNotFound } if !lic.IsActive { return nil, ErrLicenseInactive } if lic.ExpiresAt != nil && time.Now().After(*lic.ExpiresAt) { return nil, ErrLicenseExpired } // 若已绑定设备,校验是否一致 if lic.DeviceID != "" && lic.DeviceID != deviceID { return nil, ErrDeviceMismatch } now := time.Now() lic.DeviceID = deviceID lic.ActivatedAt = &now s.db.Save(&lic) return &lic, nil } // Verify 验证(客户端启动时调用) func (s *LicenseService) Verify(shopID uint64, deviceID string) (*model.License, error) { var lic model.License if err := s.db.Where("shop_id = ? AND device_id = ? AND is_active = 1", shopID, deviceID). First(&lic).Error; err != nil { return nil, ErrLicenseNotFound } if lic.ExpiresAt != nil && time.Now().After(*lic.ExpiresAt) { return nil, ErrLicenseExpired } return &lic, nil } // ShopInfo 返回门店当前授权信息(取最新一条有效许可证) func (s *LicenseService) ShopInfo(shopID uint64) (*model.License, error) { var lic model.License if err := s.db.Where("shop_id = ? AND is_active = 1", shopID). Order("id DESC").First(&lic).Error; err != nil { return nil, ErrLicenseNotFound } return &lic, nil } // Deactivate 解绑设备(换机时使用) func (s *LicenseService) Deactivate(shopID uint64, deviceID string) error { return s.db.Model(&model.License{}). Where("shop_id = ? AND device_id = ?", shopID, deviceID). Updates(map[string]interface{}{"device_id": "", "activated_at": nil}).Error }