174 lines
5.5 KiB
Go
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
|
|
}
|