拆分商品模块服务和处理器职责
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package listing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var priceNumberPattern = regexp.MustCompile(`(\d+(?:\.\d+)?)`)
|
||||
|
||||
type publishRules struct {
|
||||
FireLevelMin int
|
||||
}
|
||||
|
||||
func validateRequest(req CreateRequest, rules publishRules) error {
|
||||
if strings.TrimSpace(req.Title) == "" {
|
||||
return ErrMissingTitle
|
||||
}
|
||||
if strings.TrimSpace(req.ServerRegion) == "" {
|
||||
return ErrMissingServerRegion
|
||||
}
|
||||
if normalizedListingPriceCent(req) <= 0 {
|
||||
return ErrInvalidPrice
|
||||
}
|
||||
if req.DepositAmountCent < 0 {
|
||||
return ErrInvalidDeposit
|
||||
}
|
||||
if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmountCent <= yuanToCent(consumables) {
|
||||
return ErrDepositTooLow
|
||||
}
|
||||
if req.HafCoinAmount < 0 {
|
||||
return ErrInvalidHafCoin
|
||||
}
|
||||
if !hasScreenshotURL(req.ScreenshotURLS) {
|
||||
return ErrMissingScreenshot
|
||||
}
|
||||
if fireLevel, ok := readFireLevel(req.AssetSummary); ok && fireLevel < rules.FireLevelMin {
|
||||
return FireLevelTooLowError{Min: rules.FireLevelMin}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func consumableValue(summary map[string]any) float64 {
|
||||
if summary == nil {
|
||||
return 0
|
||||
}
|
||||
rawResources, ok := summary["resources"]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
resources, ok := rawResources.([]any)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
total := 0.0
|
||||
for _, raw := range resources {
|
||||
resource, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mode, _ := resource["mode"].(string)
|
||||
if strings.TrimSpace(mode) != "收费" {
|
||||
continue
|
||||
}
|
||||
quantity := readSummaryFloat(resource["quantity"])
|
||||
if quantity <= 0 {
|
||||
continue
|
||||
}
|
||||
priceText, _ := resource["price"].(string)
|
||||
total += quantity * readUnitPrice(priceText)
|
||||
}
|
||||
return roundMoney(total)
|
||||
}
|
||||
|
||||
func readSummaryFloat(value any) float64 {
|
||||
switch current := value.(type) {
|
||||
case float64:
|
||||
return current
|
||||
case int:
|
||||
return float64(current)
|
||||
case int64:
|
||||
return float64(current)
|
||||
case json.Number:
|
||||
parsed, err := current.Float64()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
case string:
|
||||
parsed, err := strconv.ParseFloat(strings.TrimSpace(current), 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func readUnitPrice(priceText string) float64 {
|
||||
numbers := priceNumberPattern.FindAllString(priceText, -1)
|
||||
if len(numbers) == 0 {
|
||||
return 0
|
||||
}
|
||||
amount, err := strconv.ParseFloat(numbers[0], 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
if len(numbers) >= 2 {
|
||||
count, err := strconv.ParseFloat(numbers[1], 64)
|
||||
if err == nil && count > 0 {
|
||||
return amount / count
|
||||
}
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
func readFireLevel(summary map[string]any) (int, bool) {
|
||||
if summary == nil {
|
||||
return 0, false
|
||||
}
|
||||
value, ok := summary["fire_level"]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
switch current := value.(type) {
|
||||
case float64:
|
||||
return int(current), true
|
||||
case int:
|
||||
return current, true
|
||||
case string:
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(current))
|
||||
return parsed, err == nil
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func hasScreenshotURL(urls []string) bool {
|
||||
for _, url := range urls {
|
||||
if strings.TrimSpace(url) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user