Files
opsx-licence/backend/internal/service/sync/sync_apply.go
T

174 lines
5.5 KiB
Go

//! 同步载荷应用:按业务键 upsert,仅当载荷新于本地时更新(last-write-wins)。 // Logan.Li
package sync
import (
"fmt"
"strings"
"time"
"backend/internal/dto"
"backend/internal/model"
)
// upsertProduct 按产品 code 定位 // Logan.Li
func (s *Service) upsertProduct(p dto.SyncProductPayload) bool {
if strings.TrimSpace(p.Code) == "" {
return false
}
var local model.Product
err := s.db.Where("code = ?", p.Code).First(&local).Error
if err != nil {
local = model.Product{
Code: p.Code, Name: p.Name, Version: p.Version, Description: p.Description,
Features: p.Features, Price: p.Price, PriceModel: p.PriceModel,
Status: p.Status, CreatedAt: p.UpdatedAt, UpdatedAt: p.UpdatedAt,
}
return s.db.Create(&local).Error == nil
}
if !p.UpdatedAt.After(local.UpdatedAt) {
return false // 本地已是最新的,跳过 // Logan.Li
}
local.Name = p.Name
local.Version = p.Version
local.Description = p.Description
local.Features = p.Features
local.Price = p.Price
local.PriceModel = p.PriceModel
local.Status = p.Status
return s.db.Save(&local).Error == nil
}
// upsertPlan 按 产品code+plan_type+duration 定位 // Logan.Li
func (s *Service) upsertPlan(pl dto.SyncPlanPayload) bool {
if pl.ProductCode == "" {
return false
}
var product model.Product
if err := s.db.Where("code = ?", pl.ProductCode).First(&product).Error; err != nil {
return false // 产品依赖未就绪,等下一轮 // Logan.Li
}
var local model.Plan
err := s.db.Where("product_id = ? AND plan_type = ? AND duration = ?", product.ID, pl.PlanType, pl.Duration).First(&local).Error
if err != nil {
local = model.Plan{
ProductID: product.ID, Name: pl.Name, PlanType: pl.PlanType,
Price: pl.Price, Currency: pl.Currency, Duration: pl.Duration,
MaxDevices: pl.MaxDevices, Features: pl.Features, SortOrder: pl.SortOrder,
Status: pl.Status, CreatedAt: pl.UpdatedAt, UpdatedAt: pl.UpdatedAt,
}
return s.db.Create(&local).Error == nil
}
if !pl.UpdatedAt.After(local.UpdatedAt) {
return false
}
local.Name = pl.Name
local.Price = pl.Price
local.Currency = pl.Currency
local.MaxDevices = pl.MaxDevices
local.Features = pl.Features
local.SortOrder = pl.SortOrder
local.Status = pl.Status
return s.db.Save(&local).Error == nil
}
// upsertCustomer 按 email 定位(空/占位邮箱回退 name),合规最小字段 // Logan.Li
func (s *Service) upsertCustomer(c dto.SyncCustomerPayload) bool {
email := strings.TrimSpace(c.Email)
name := strings.TrimSpace(c.Name)
var local model.Customer
var err error
if email != "" && !strings.HasSuffix(email, "@local.invalid") {
err = s.db.Where("email = ?", email).First(&local).Error
} else {
if name == "" {
return false
}
err = s.db.Where("name = ?", name).Order("id ASC").First(&local).Error
}
if err != nil {
if email == "" {
email = fmt.Sprintf("sync+%d@local.invalid", time.Now().UnixNano())
}
local = model.Customer{
Name: name, Company: c.Company, Email: email,
Status: c.Status, CreatedAt: c.UpdatedAt, UpdatedAt: c.UpdatedAt,
}
return s.db.Create(&local).Error == nil
}
if !c.UpdatedAt.After(local.UpdatedAt) {
return false
}
local.Name = name
local.Company = c.Company
if email != "" && !strings.HasSuffix(email, "@local.invalid") {
local.Email = email
}
local.Status = c.Status
return s.db.Save(&local).Error == nil
}
// upsertLicense 按 license_key 定位,跨表解析产品与客户 // Logan.Li
func (s *Service) upsertLicense(l dto.SyncLicensePayload) bool {
if strings.TrimSpace(l.LicenseKey) == "" || l.ProductCode == "" {
return false
}
var product model.Product
if err := s.db.Where("code = ?", l.ProductCode).First(&product).Error; err != nil {
return false
}
customerID, err := s.resolveCustomer(l.CustomerEmail, l.CustomerName)
if err != nil {
return false
}
var local model.License
err = s.db.Where("license_key = ?", l.LicenseKey).First(&local).Error
if err != nil {
local = model.License{
LicenseKey: l.LicenseKey, ProductID: product.ID, CustomerID: customerID,
Status: l.Status, StartDate: l.StartDate, EndDate: l.EndDate,
Duration: l.Duration, MaxActivations: l.MaxActivations,
CurrentActivations: l.CurrentActivations,
IsTrial: l.IsTrial, TrialDays: l.TrialDays,
CreatedAt: l.UpdatedAt, UpdatedAt: l.UpdatedAt,
}
return s.db.Create(&local).Error == nil
}
if !l.UpdatedAt.After(local.UpdatedAt) {
return false
}
local.ProductID = product.ID
local.CustomerID = customerID
local.Status = l.Status
local.StartDate = l.StartDate
local.EndDate = l.EndDate
local.Duration = l.Duration
local.MaxActivations = l.MaxActivations
local.CurrentActivations = l.CurrentActivations
local.IsTrial = l.IsTrial
local.TrialDays = l.TrialDays
return s.db.Save(&local).Error == nil
}
// resolveCustomer 按邮箱解析或创建客户(离线占位客户按名字复用) // Logan.Li
func (s *Service) resolveCustomer(email, name string) (uint, error) {
email = strings.TrimSpace(email)
name = strings.TrimSpace(name)
if email == "" {
email = fmt.Sprintf("sync+%d@local.invalid", time.Now().UnixNano())
}
var customer model.Customer
if err := s.db.Where("email = ?", email).First(&customer).Error; err == nil {
return customer.ID, nil
}
customer = model.Customer{Name: name, Email: email, Status: 1}
if err := s.db.Create(&customer).Error; err != nil {
// 并发下可能已存在,再查一次 // Logan.Li
if err2 := s.db.Where("email = ?", email).First(&customer).Error; err2 == nil {
return customer.ID, nil
}
return 0, err
}
return customer.ID, nil
}