package services import ( "crypto/rand" "encoding/base64" "encoding/hex" "errors" "fmt" "os" "strings" "time" "github.com/google/uuid" qr "github.com/yeqown/go-qrcode/v2" "github.com/yeqown/go-qrcode/writer/standard" "git.beifan.cn/trace-system/backend-go/database" "git.beifan.cn/trace-system/backend-go/models" ) // SerialsService 序列号服务 type SerialsService struct{} // Generate 生成序列号 func (s *SerialsService) Generate( companyName string, quantity int, validDays int, userId uint, prefix ...string, ) ([]models.Serial, error) { var serials []models.Serial validUntil := time.Now().AddDate(0, 0, validDays) // 检查公司是否存在,不存在则创建 var company models.Company result := database.DB.Where("company_name = ?", companyName).First(&company) if result.Error != nil { company = models.Company{ CompanyName: companyName, IsActive: true, } result = database.DB.Create(&company) if result.Error != nil { return nil, fmt.Errorf("创建公司失败: %w", result.Error) } } // 生成序列号前缀 var serialPrefix string if len(prefix) > 0 && prefix[0] != "" { serialPrefix = strings.ToUpper(strings.ReplaceAll(prefix[0], "[^A-Z0-9]", "")) } else { serialPrefix = fmt.Sprintf("BF%d", time.Now().Year()%100) } // 预生成所有序列号 serialNumbers := make(map[string]bool) for i := 0; i < quantity; { randomBytes := make([]byte, 3) if _, err := rand.Read(randomBytes); err != nil { return nil, fmt.Errorf("生成随机数失败: %w", err) } randomPart := hex.EncodeToString(randomBytes)[:6] serialNumber := fmt.Sprintf("%s%s", serialPrefix, randomPart) if serialNumbers[serialNumber] { continue } var existingSerial models.Serial checkResult := database.DB.Where("serial_number = ?", serialNumber).First(&existingSerial) if checkResult.Error != nil { serialNumbers[serialNumber] = true i++ } } for serialNumber := range serialNumbers { serial := models.Serial{ SerialNumber: strings.ToUpper(serialNumber), CompanyName: companyName, ValidUntil: &validUntil, CreatedBy: &userId, IsActive: true, } serials = append(serials, serial) } // 保存到数据库 result = database.DB.Create(&serials) if result.Error != nil { return nil, fmt.Errorf("保存序列号失败: %w", result.Error) } return serials, nil } // GenerateQRCode 生成二维码 func (s *SerialsService) GenerateQRCode( serialNumber string, baseUrl string, requestHost string, protocol string, ) (string, string, error) { var serial models.Serial result := database.DB.Preload("User").Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial) if result.Error != nil { return "", "", fmt.Errorf("查询序列号失败: %w", errors.New("序列号不存在")) } if !serial.IsActive { return "", "", fmt.Errorf("序列号状态无效: %w", errors.New("序列号已被禁用")) } if serial.ValidUntil != nil && serial.ValidUntil.Before(time.Now()) { return "", "", fmt.Errorf("序列号已过期") } // 确定查询 URL if baseUrl == "" { baseUrl = fmt.Sprintf("%s://%s/query.html", protocol, requestHost) } var queryUrl string if strings.Contains(baseUrl, "?") { queryUrl = fmt.Sprintf("%s&serial=%s", baseUrl, serial.SerialNumber) } else { queryUrl = fmt.Sprintf("%s?serial=%s", baseUrl, serial.SerialNumber) } // 生成二维码到临时文件 filePath := fmt.Sprintf("temp_qr_%s.png", uuid.New().String()) writer, err := standard.New(filePath, standard.WithQRWidth(6)) if err != nil { return "", "", fmt.Errorf("二维码写入器创建失败: %w", err) } qrc, errCode := qr.New(queryUrl) if errCode != nil { os.Remove(filePath) return "", "", fmt.Errorf("二维码创建失败: %w", errCode) } if errSave := qrc.Save(writer); errSave != nil { os.Remove(filePath) return "", "", fmt.Errorf("二维码保存失败: %w", errSave) } // 读取文件内容 fileContent, errRead := os.ReadFile(filePath) if errRead != nil { os.Remove(filePath) return "", "", fmt.Errorf("二维码文件读取失败: %w", errRead) } // 删除临时文件 os.Remove(filePath) // 转换为 base64 qrCodeBase64 := fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(fileContent)) return qrCodeBase64, queryUrl, nil } // Query 查询序列号信息 func (s *SerialsService) Query(serialNumber string) (*models.Serial, error) { var serial models.Serial result := database.DB.Preload("User").Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial) if result.Error != nil { return nil, fmt.Errorf("查询序列号失败: %w", errors.New("序列号不存在")) } if serial.ValidUntil != nil && serial.ValidUntil.Before(time.Now()) { return nil, fmt.Errorf("序列号已过期") } return &serial, nil } // FindAll 获取序列号列表 func (s *SerialsService) FindAll(page int, limit int, search string) ([]models.Serial, int, int, error) { var serials []models.Serial var total int64 offset := (page - 1) * limit db := database.DB.Preload("User") // 搜索条件 if search != "" { db = db.Where("serial_number LIKE ? OR company_name LIKE ?", "%"+search+"%", "%"+search+"%") } // 获取总数 countQuery := db.Model(&models.Serial{}) if search != "" { countQuery = countQuery.Where("serial_number LIKE ? OR company_name LIKE ?", "%"+search+"%", "%"+search+"%") } countQuery.Count(&total) // 分页查询 result := db.Model(&models.Serial{}).Order("created_at DESC").Offset(offset).Limit(limit).Find(&serials) if result.Error != nil { return nil, 0, 0, fmt.Errorf("查询序列号列表失败: %w", result.Error) } totalPages := (int(total) + limit - 1) / limit return serials, int(total), totalPages, nil } // Update 更新序列号信息 func (s *SerialsService) Update(serialNumber string, updateData models.UpdateSerialDTO) (*models.Serial, error) { var serial models.Serial result := database.DB.Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial) if result.Error != nil { return nil, fmt.Errorf("查询序列号失败: %w", errors.New("序列号不存在")) } if updateData.CompanyName != "" { // 检查公司是否存在 var company models.Company companyResult := database.DB.Where("company_name = ?", updateData.CompanyName).First(&company) if companyResult.Error != nil { company = models.Company{ CompanyName: updateData.CompanyName, IsActive: true, } database.DB.Create(&company) } serial.CompanyName = updateData.CompanyName } if updateData.ValidUntil != nil { serial.ValidUntil = updateData.ValidUntil } if updateData.IsActive != nil { serial.IsActive = *updateData.IsActive } result = database.DB.Save(&serial) if result.Error != nil { return nil, fmt.Errorf("更新序列号失败: %w", result.Error) } _ = database.DB.Preload("User").Where("serial_number = ?", serial.SerialNumber).First(&serial) return &serial, nil } // Revoke 吊销序列号 func (s *SerialsService) Revoke(serialNumber string) error { var serial models.Serial result := database.DB.Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial) if result.Error != nil { return fmt.Errorf("查询序列号失败: %w", errors.New("序列号不存在")) } if !serial.IsActive { return fmt.Errorf("序列号状态无效: %w", errors.New("序列号已被吊销")) } serial.IsActive = false result = database.DB.Save(&serial) if result.Error != nil { return fmt.Errorf("吊销序列号失败: %w", result.Error) } return nil }