From 3490bc7e57552389b865ae69bb938e2785bf94a9 Mon Sep 17 00:00:00 2001 From: timerzz Date: Sat, 15 Jun 2024 16:54:04 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E4=BC=98=E6=83=A0=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=E5=8F=AF=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 2 + model/product/model.go | 237 +++++++++++++++++++++--------------- model/product/model_test.go | 35 ++++-- 4 files changed, 169 insertions(+), 106 deletions(-) diff --git a/go.mod b/go.mod index 598e16b..e966f3b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/bytedance/sonic v1.11.6 github.com/cloudwego/hertz v0.9.1 github.com/corpix/uarand v0.2.0 + github.com/expr-lang/expr v1.16.9 github.com/go-resty/resty/v2 v2.13.1 github.com/golang/glog v1.2.1 github.com/metacubex/mihomo v1.18.4 diff --git a/go.sum b/go.sum index 5a78c70..46f6a38 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= +github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= +github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= diff --git a/model/product/model.go b/model/product/model.go index 308679f..cd55bab 100644 --- a/model/product/model.go +++ b/model/product/model.go @@ -2,6 +2,7 @@ package productv1 import ( "fmt" + "github.com/expr-lang/expr" "gorm.io/gorm" "math" "strings" @@ -35,24 +36,23 @@ type Product struct { DeletedAt gorm.DeletedAt `gorm:"index"` Name string `gorm:"index" json:"name"` - Pid string `gorm:"unique;not null" json:"pid"` + Pid string `gorm:"index:,unique,composite:product_pid_website_idx;not null" json:"pid"` Color string `json:"color"` Link string `json:"link"` Image string `json:"image"` Orderable bool `json:"orderable"` - USPrice float64 `json:"usPrice"` - Website Website `json:"website" gorm:"index"` - DiscPercent int `json:"discPercent" gorm:"index"` //折扣力度 - CNYPrice float64 `json:"cnyPrice"` - CalMark string `json:"calMark"` //计算价格的过程 - DWPrice float64 `json:"dwPrice"` - Freight float64 `json:"freight"` //运费 - ExchangeRate float64 `json:"exchangeRate" gorm:"-"` //汇率 - Discount int `json:"discount" gorm:"-"` //折扣 - Rate float64 `json:"rate" gorm:"index"` //利润率 - PriceStatus PriceStatus `json:"priceStatus"` //价格状态 - Remark string `json:"remark"` //备注 + OriginalPrice float64 `json:"originalPrice"` + Website Website `json:"website" gorm:"index:,unique,composite:product_pid_website_idx"` + DiscPercent int `json:"discPercent" gorm:"index"` //折扣力度 + CNYPrice float64 `json:"cnyPrice"` // 最终的价格 + CalMark string `json:"calMark"` //计算价格的过程 + DWPrice float64 `json:"dwPrice"` + Freight float64 `json:"freight"` //运费 + ExchangeRate float64 `json:"exchangeRate" gorm:"-"` //汇率 + Rate float64 `json:"rate" gorm:"index"` //利润率 + PriceStatus PriceStatus `json:"priceStatus"` //价格状态 + Remark string `json:"remark"` //备注 HistoryPrices []HistoryPrice `gorm:"foreignKey:Pid;references:Pid" json:"historyPrices"` DWHistoryPrices []DWHistoryPrice `gorm:"foreignKey:Pid;references:Pid" json:"dwHistoryPrices"` @@ -62,29 +62,29 @@ func (p *Product) TableName() string { return "products" } +func (p *Product) Env() map[string]any { + return map[string]any{ + "originalPrice": p.OriginalPrice, + "dwPrice": p.DWPrice, + "freight": p.Freight, + "exchangeRate": p.ExchangeRate, + } +} + func (p *Product) BeforeSave(tx *gorm.DB) (err error) { - p.CalRate() p.fillPriceStatus(tx) + p.CalRate() return } func (p *Product) fillPriceStatus(tx *gorm.DB) { - switch p.Website { - case WebSite_US_Coach_Outlet: - p.fillUSCoachPriceStatus(tx) - case WebSite_CN_Coach: - p.fillCNCoachPriceStatus(tx) - } -} - -func (p *Product) fillUSCoachPriceStatus(tx *gorm.DB) { var lastPrice float64 - tx.Model(&HistoryPrice{}).Where("pid = ?", p.Pid).Order("created_at desc").Limit(1).Pluck("us_price", &lastPrice) - if p.USPrice != lastPrice { + tx.Model(&HistoryPrice{}).Where("pid = ?", p.Pid).Order("created_at desc").Limit(1).Pluck("original_price", &lastPrice) + if p.OriginalPrice != lastPrice { p.HistoryPrices = append(p.HistoryPrices, HistoryPrice{ - USPrice: p.USPrice, + OriginalPrice: p.OriginalPrice, }) - if p.USPrice > lastPrice { + if p.OriginalPrice > lastPrice { p.PriceStatus = PriceStatus_UP } else { p.PriceStatus = PriceStatus_DOWN @@ -92,81 +92,83 @@ func (p *Product) fillUSCoachPriceStatus(tx *gorm.DB) { } } -func (p *Product) fillCNCoachPriceStatus(tx *gorm.DB) { - var lastPrice float64 - tx.Model(&HistoryPrice{}).Where("pid = ?", p.Pid).Order("created_at desc").Limit(1).Pluck("us_price", &lastPrice) - if p.CNYPrice != lastPrice { - p.HistoryPrices = append(p.HistoryPrices, HistoryPrice{ - USPrice: p.CNYPrice, - }) - if p.CNYPrice > lastPrice { - p.PriceStatus = PriceStatus_UP - } else { - p.PriceStatus = PriceStatus_DOWN +func (p *Product) CalCNY(calculateProcess []CalculateProcess) { + env := p.Env() + var calculateStrings = make([]string, 0, len(calculateProcess)) + for _, c := range calculateProcess { + process, price := c.Calculate(env) + if process != "" { + calculateStrings = append(calculateStrings, process) + p.CNYPrice = min(p.CNYPrice, price) } } + p.CalMark = strings.Join(calculateStrings, "\n") } +//func (p *Product) CalRate() { +// switch p.Website { +// case WebSite_US_Coach_Outlet: +// p.CalRateUSCoach() +// case WebSite_CN_Coach: +// p.CalRateCNCoach() +// } +//} + +//func (p *Product) CalRateUSCoach() { +// calculationProcess := make([]string, 0, 2) //存放计算过程 +// if p.ExchangeRate == 0 { +// p.ExchangeRate = defaultExchangeRate +// } +// if p.Freight == 0 { +// p.Freight = defaultFreight +// } +// if p.Discount == 0 { +// p.Discount = 100 +// } +// +// //先计算打九折的价格 +// tmpPrice := p.USPrice * 0.9 // 这是打九折的价格 +// p.CNYPrice = tmpPrice*p.ExchangeRate + p.Freight +// calculationProcess = append(calculationProcess, fmt.Sprintf("【九折】%.2f * 0.9 * %.2f + %.2f = %.2f", p.USPrice, p.ExchangeRate, p.Freight, p.CNYPrice)) +// var discountPrice = p.USPrice +// if 0 < p.Discount && p.Discount < 100 { +// discountPrice = p.USPrice * float64(p.Discount) / 100 +// } +// if discountPrice >= 150 { +// calculationProcess = append(calculationProcess, fmt.Sprintf("【150 -20】(%.2f * %d%% - 20) * %.2f + %.2f = %.2f", p.USPrice, p.Discount, p.ExchangeRate, p.Freight, (discountPrice-20)*p.ExchangeRate+p.Freight)) +// if discountPrice-20 < tmpPrice { +// // 符合满150-20,而且比九折便宜 +// tmpPrice = discountPrice - 20 +// p.CNYPrice = tmpPrice*p.ExchangeRate + p.Freight +// } +// } else if discountPrice >= 100 { +// calculationProcess = append(calculationProcess, fmt.Sprintf("【100 -10】(%.2f * %d%% - 10) * %.2f + %.2f = %.2f", p.USPrice, p.Discount, p.ExchangeRate, p.Freight, (discountPrice-10)*p.ExchangeRate+p.Freight)) +// if discountPrice-10 < tmpPrice { +// // 符合满100 -10,而且比九折便宜 +// tmpPrice = discountPrice - 10 +// p.CNYPrice = tmpPrice*p.ExchangeRate + p.Freight +// } +// } else if p.Discount < 90 { +// calculationProcess = append(calculationProcess, fmt.Sprintf("【打折扣】%.2f * %d%% * %.2f + %.2f = %.2f", p.USPrice, p.Discount, p.ExchangeRate, p.Freight, p.USPrice*float64(p.Discount)/100*p.ExchangeRate+p.Freight)) +// p.CNYPrice = p.USPrice*float64(p.Discount)/100*p.ExchangeRate + p.Freight +// } +// p.CalMark = strings.Join(calculationProcess, "\n") +// if p.DWPrice > 0 { +// // 如果有得物价格,就计算收益率 +// p.Rate = Decimal((p.DWPrice - p.CNYPrice) / p.CNYPrice * 100) +// } +// +// p.CNYPrice = Decimal(p.CNYPrice) +//} + +// CalRate 计算收益率 func (p *Product) CalRate() { - switch p.Website { - case WebSite_US_Coach_Outlet: - p.CalRateUSCoach() - case WebSite_CN_Coach: - p.CalRateCNCoach() - } -} - -func (p *Product) CalRateUSCoach() { - calculationProcess := make([]string, 0, 2) //存放计算过程 - if p.ExchangeRate == 0 { - p.ExchangeRate = defaultExchangeRate - } - if p.Freight == 0 { - p.Freight = defaultFreight - } - if p.Discount == 0 { - p.Discount = 100 - } - - //先计算打九折的价格 - tmpPrice := p.USPrice * 0.9 // 这是打九折的价格 - p.CNYPrice = tmpPrice*p.ExchangeRate + p.Freight - calculationProcess = append(calculationProcess, fmt.Sprintf("【九折】%.2f * 0.9 * %.2f + %.2f = %.2f", p.USPrice, p.ExchangeRate, p.Freight, p.CNYPrice)) - var discountPrice = p.USPrice - if 0 < p.Discount && p.Discount < 100 { - discountPrice = p.USPrice * float64(p.Discount) / 100 - } - if discountPrice >= 150 { - calculationProcess = append(calculationProcess, fmt.Sprintf("【150 -20】(%.2f * %d%% - 20) * %.2f + %.2f = %.2f", p.USPrice, p.Discount, p.ExchangeRate, p.Freight, (discountPrice-20)*p.ExchangeRate+p.Freight)) - if discountPrice-20 < tmpPrice { - // 符合满150-20,而且比九折便宜 - tmpPrice = discountPrice - 20 - p.CNYPrice = tmpPrice*p.ExchangeRate + p.Freight - } - } else if discountPrice >= 100 { - calculationProcess = append(calculationProcess, fmt.Sprintf("【100 -10】(%.2f * %d%% - 10) * %.2f + %.2f = %.2f", p.USPrice, p.Discount, p.ExchangeRate, p.Freight, (discountPrice-10)*p.ExchangeRate+p.Freight)) - if discountPrice-10 < tmpPrice { - // 符合满100 -10,而且比九折便宜 - tmpPrice = discountPrice - 10 - p.CNYPrice = tmpPrice*p.ExchangeRate + p.Freight - } - } else if p.Discount < 90 { - calculationProcess = append(calculationProcess, fmt.Sprintf("【打折扣】%.2f * %d%% * %.2f + %.2f = %.2f", p.USPrice, p.Discount, p.ExchangeRate, p.Freight, p.USPrice*float64(p.Discount)/100*p.ExchangeRate+p.Freight)) - p.CNYPrice = p.USPrice*float64(p.Discount)/100*p.ExchangeRate + p.Freight - } - p.CalMark = strings.Join(calculationProcess, "\n") - if p.DWPrice > 0 { - // 如果有得物价格,就计算收益率 - p.Rate = Decimal((p.DWPrice - p.CNYPrice) / p.CNYPrice * 100) - } - - p.CNYPrice = Decimal(p.CNYPrice) -} - -func (p *Product) CalRateCNCoach() { if p.DWPrice > 0 { // 如果有得物价格,就计算收益率 p.Rate = Decimal((p.DWPrice - p.CNYPrice) / p.CNYPrice * 100) + } else if p.Rate > 0 { + // 如果得物价格为0, 收益率应该设置为0 + p.Rate = 0 } p.CNYPrice = Decimal(p.CNYPrice) @@ -181,8 +183,8 @@ type HistoryPrice struct { ID uint `gorm:"primary_key" json:"id"` CreatedAt time.Time `json:"createdAt"` - Pid string `gorm:"index" json:"pid"` - USPrice float64 `json:"usPrice"` + Pid string `gorm:"index" json:"pid"` + OriginalPrice float64 `json:"originalPrice"` } type DWHistoryPrice struct { @@ -192,3 +194,46 @@ type DWHistoryPrice struct { Pid string `gorm:"index" json:"pid"` DWPrice float64 `json:"dwPrice"` } + +// CalculateProcess 计算过程 +type CalculateProcess struct { + ID uint `gorm:"primary_key" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index"` + + Pid string + Website Website + Condition string + Process string + Name string +} + +func (c *CalculateProcess) Calculate(env map[string]any) (string, float64) { + if c.Condition != "" { + condition, err := expr.Compile(c.Condition, expr.AsBool()) + if err != nil { + return fmt.Sprintf("【%s】 condition compile error: %v", c.Name, err), 0 + } + if ok, err := expr.Run(condition, env); err != nil { + return fmt.Sprintf("【%s】 condition run error: %v", c.Name, err), 0 + } else if !ok.(bool) { + return "", 0 + } + } + program, err := expr.Compile(c.Process, expr.AsFloat64()) + if err != nil { + return fmt.Sprintf("【%s】 process compile error: %v", c.Name, err), 0 + } + + output, err := expr.Run(program, env) + if err != nil { + return fmt.Sprintf("【%s】 process run error: %v", c.Name, err), 0 + } + var replaceList = make([]string, 0, 2*len(env)) + for k, v := range env { + replaceList = append(replaceList, k, fmt.Sprintf("%v", v)) + } + replacer := strings.NewReplacer(replaceList...) + return replacer.Replace(fmt.Sprintf("【%s】 %s = %.2f", c.Name, c.Process, output.(float64))), output.(float64) +} diff --git a/model/product/model_test.go b/model/product/model_test.go index 73d407f..c9afb0d 100644 --- a/model/product/model_test.go +++ b/model/product/model_test.go @@ -26,15 +26,15 @@ func TestUpsert(t *testing.T) { DoUpdates: clause.AssignmentColumns([]string{"name", "color", "link", "orderable", "us_price", "cny_price", "cal_mark", "rate"}), }).Create([]Product{ { - Name: "test", - Pid: "test-3", - Color: "green", - Link: "www.baidu.com", - Image: "image", - Orderable: false, - USPrice: 123, - DWPrice: 3000, - Freight: 130, + Name: "test", + Pid: "test-3", + Color: "green", + Link: "www.baidu.com", + Image: "image", + Orderable: false, + OriginalPrice: 123, + DWPrice: 3000, + Freight: 130, }, }) } @@ -50,7 +50,7 @@ func TestUpdate(t *testing.T) { if err != nil { t.Fatal(err) } - var p = Product{Pid: "CJ575-QBO4G", DWPrice: 3000, Freight: 150, Remark: "test", USPrice: 209.3, ExchangeRate: 7.3} + var p = Product{Pid: "CJ575-QBO4G", DWPrice: 3000, Freight: 150, Remark: "test", OriginalPrice: 209.3, ExchangeRate: 7.3} err = db.Model(&p).Where("pid = ?", p.Pid).Select("dw_price", "freight", "remark", "rate", "cal_mark", "cny_price").Updates(&p).Error if err != nil { t.Fatal(err) @@ -72,3 +72,18 @@ func TestGetConfig(t *testing.T) { db.Table("options").Select("exchange_rate").Where("id = ?", 1).Scan(&exchangeRate) t.Log(exchangeRate) } + +func TestCalculateProcess_Calculate(t *testing.T) { + p := Product{ + OriginalPrice: 160.9, + Freight: 100, + ExchangeRate: 7.3, + PriceStatus: 0, + } + cal := CalculateProcess{ + Condition: "usPrice >= 150", + Process: "(usPrice - 20) * exchangeRate + freight", + Name: "满150 - 20", + } + t.Log(cal.Calculate(p.Env())) +}