彻底优化数据库字段
This commit is contained in:
@@ -19,9 +19,6 @@ type ListingDTO struct {
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
Price float64 `json:"price"`
|
||||
PriceHourly float64 `json:"price_hourly"`
|
||||
PriceDaily float64 `json:"price_daily"`
|
||||
PriceWeekly float64 `json:"price_weekly"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
IsAccelerated bool `json:"is_accelerated_sale"`
|
||||
InTransaction bool `json:"in_transaction"`
|
||||
@@ -43,9 +40,6 @@ type CreateRequest struct {
|
||||
AssetSummary map[string]any `json:"asset_summary"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
Price float64 `json:"price"`
|
||||
PriceHourly float64 `json:"price_hourly"`
|
||||
PriceDaily float64 `json:"price_daily"`
|
||||
PriceWeekly float64 `json:"price_weekly"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
}
|
||||
|
||||
|
||||
@@ -364,6 +364,8 @@ func writeListingError(c *gin.Context, err error) {
|
||||
response.BadRequest(c, "发布价格不正确")
|
||||
case errors.Is(err, ErrInvalidDeposit):
|
||||
response.BadRequest(c, "押金不能小于 0")
|
||||
case errors.Is(err, ErrDepositTooLow):
|
||||
response.BadRequest(c, "押金必须大于额外消耗品总价值")
|
||||
case errors.Is(err, ErrInvalidHafCoin):
|
||||
response.BadRequest(c, "哈夫币数量不正确")
|
||||
case errors.Is(err, ErrMissingScreenshot):
|
||||
|
||||
@@ -65,9 +65,6 @@ func (r *Repository) Create(ownerID uint64, req CreateRequest, reviewRequired bo
|
||||
AccountID: account.ID,
|
||||
OwnerID: ownerID,
|
||||
Price: price,
|
||||
PriceHourly: listingHourlyPrice(req, price),
|
||||
PriceDaily: listingDailyPrice(req, price),
|
||||
PriceWeekly: listingWeeklyPrice(req, price),
|
||||
DepositAmount: req.DepositAmount,
|
||||
Status: listingStatus,
|
||||
ReviewStatus: reviewStatus,
|
||||
@@ -112,9 +109,6 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest,
|
||||
|
||||
price := normalizedListingPrice(req)
|
||||
listing.Price = price
|
||||
listing.PriceHourly = listingHourlyPrice(req, price)
|
||||
listing.PriceDaily = listingDailyPrice(req, price)
|
||||
listing.PriceWeekly = listingWeeklyPrice(req, price)
|
||||
listing.DepositAmount = req.DepositAmount
|
||||
listingStatus, reviewStatus, publishedAt := initialPublishState(reviewRequired)
|
||||
listing.Status = listingStatus
|
||||
@@ -489,63 +483,7 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
|
||||
}
|
||||
|
||||
func normalizedListingPrice(req CreateRequest) float64 {
|
||||
if req.Price > 0 {
|
||||
return req.Price
|
||||
}
|
||||
if req.PriceDaily > 0 {
|
||||
return req.PriceDaily
|
||||
}
|
||||
if req.PriceHourly > 0 {
|
||||
return req.PriceHourly * 24
|
||||
}
|
||||
if req.PriceWeekly > 0 {
|
||||
return req.PriceWeekly / 7
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func listingHourlyPrice(req CreateRequest, price float64) float64 {
|
||||
if req.PriceHourly > 0 {
|
||||
return req.PriceHourly
|
||||
}
|
||||
return price / 24
|
||||
}
|
||||
|
||||
func listingDailyPrice(req CreateRequest, price float64) float64 {
|
||||
if req.PriceDaily > 0 {
|
||||
return req.PriceDaily
|
||||
}
|
||||
return price
|
||||
}
|
||||
|
||||
func listingWeeklyPrice(req CreateRequest, price float64) float64 {
|
||||
if req.PriceWeekly > 0 {
|
||||
return req.PriceWeekly
|
||||
}
|
||||
return price * 7
|
||||
}
|
||||
|
||||
func listingDisplayPrices(price float64, hourly float64, daily float64, weekly float64) (float64, float64, float64, float64) {
|
||||
if price <= 0 {
|
||||
switch {
|
||||
case daily > 0:
|
||||
price = daily
|
||||
case hourly > 0:
|
||||
price = hourly * 24
|
||||
case weekly > 0:
|
||||
price = weekly / 7
|
||||
}
|
||||
}
|
||||
if daily <= 0 {
|
||||
daily = price
|
||||
}
|
||||
if hourly <= 0 && price > 0 {
|
||||
hourly = price / 24
|
||||
}
|
||||
if weekly <= 0 && price > 0 {
|
||||
weekly = price * 7
|
||||
}
|
||||
return price, hourly, daily, weekly
|
||||
return req.Price
|
||||
}
|
||||
|
||||
func publicListings(items []ListingDTO) []ListingDTO {
|
||||
@@ -565,7 +503,6 @@ func applyPublicListingURLs(item *ListingDTO) {
|
||||
func (row listingRow) toDTO() ListingDTO {
|
||||
assetSummary := decodeAssetSummary(row.AssetSummary)
|
||||
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(row.ScreenshotURLS))
|
||||
price, priceHourly, priceDaily, priceWeekly := listingDisplayPrices(row.Price, row.PriceHourly, row.PriceDaily, row.PriceWeekly)
|
||||
return ListingDTO{
|
||||
ID: row.ID,
|
||||
AccountID: row.AccountID,
|
||||
@@ -582,10 +519,7 @@ func (row listingRow) toDTO() ListingDTO {
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: screenshotURLS,
|
||||
CoverURL: publicCoverURL(row.ID, screenshotURLS, row.Status, row.ReviewStatus),
|
||||
Price: price,
|
||||
PriceHourly: priceHourly,
|
||||
PriceDaily: priceDaily,
|
||||
PriceWeekly: priceWeekly,
|
||||
Price: row.Price,
|
||||
DepositAmount: row.DepositAmount,
|
||||
IsAccelerated: isAcceleratedSale(assetSummary),
|
||||
InTransaction: row.InTransaction,
|
||||
@@ -601,7 +535,6 @@ func (row listingRow) toDTO() ListingDTO {
|
||||
func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
||||
assetSummary := decodeAssetSummary(account.AssetSummary)
|
||||
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(account.ScreenshotURLS))
|
||||
price, priceHourly, priceDaily, priceWeekly := listingDisplayPrices(listing.Price, listing.PriceHourly, listing.PriceDaily, listing.PriceWeekly)
|
||||
return &ListingDTO{
|
||||
ID: listing.ID,
|
||||
AccountID: account.ID,
|
||||
@@ -616,10 +549,7 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: screenshotURLS,
|
||||
CoverURL: publicCoverURL(listing.ID, screenshotURLS, listing.Status, listing.ReviewStatus),
|
||||
Price: price,
|
||||
PriceHourly: priceHourly,
|
||||
PriceDaily: priceDaily,
|
||||
PriceWeekly: priceWeekly,
|
||||
Price: listing.Price,
|
||||
DepositAmount: listing.DepositAmount,
|
||||
IsAccelerated: isAcceleratedSale(assetSummary),
|
||||
InTransaction: listing.InTransaction,
|
||||
|
||||
@@ -3,6 +3,8 @@ package listing
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -15,6 +17,7 @@ var (
|
||||
ErrMissingServerRegion = errors.New("missing server region")
|
||||
ErrInvalidPrice = errors.New("invalid listing price")
|
||||
ErrInvalidDeposit = errors.New("invalid listing deposit")
|
||||
ErrDepositTooLow = errors.New("listing deposit too low")
|
||||
ErrInvalidHafCoin = errors.New("invalid haf coin amount")
|
||||
ErrMissingScreenshot = errors.New("missing screenshot")
|
||||
)
|
||||
@@ -34,6 +37,8 @@ const (
|
||||
defaultFireLevelMin = 38
|
||||
)
|
||||
|
||||
var priceNumberPattern = regexp.MustCompile(`(\d+(?:\.\d+)?)`)
|
||||
|
||||
type FireLevelTooLowError struct {
|
||||
Min int
|
||||
}
|
||||
@@ -217,6 +222,9 @@ func validateRequest(req CreateRequest, rules publishRules) error {
|
||||
if req.DepositAmount < 0 {
|
||||
return ErrInvalidDeposit
|
||||
}
|
||||
if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmount <= consumables {
|
||||
return ErrDepositTooLow
|
||||
}
|
||||
if req.HafCoinAmount < 0 {
|
||||
return ErrInvalidHafCoin
|
||||
}
|
||||
@@ -229,6 +237,85 @@ func validateRequest(req CreateRequest, rules publishRules) error {
|
||||
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 roundMoney(value float64) float64 {
|
||||
return math.Round(value*100) / 100
|
||||
}
|
||||
|
||||
func readFireLevel(summary map[string]any) (int, bool) {
|
||||
if summary == nil {
|
||||
return 0, false
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package listing
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestConsumableValueOnlyCountsChargedResources(t *testing.T) {
|
||||
value := consumableValue(map[string]any{
|
||||
"resources": []any{
|
||||
map[string]any{"mode": "收费", "quantity": float64(5), "price": "0.1元/个"},
|
||||
map[string]any{"mode": "赠送", "quantity": float64(3), "price": "0.6元/个"},
|
||||
map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/2个"},
|
||||
},
|
||||
})
|
||||
|
||||
if value != 1.5 {
|
||||
t.Fatalf("expected 1.5, got %.2f", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
req := CreateRequest{
|
||||
Title: "测试账号",
|
||||
ServerRegion: "烽火地带",
|
||||
Price: 100,
|
||||
DepositAmount: 1.5,
|
||||
HafCoinAmount: 1000000,
|
||||
ScreenshotURLS: []string{"https://example.com/a.png"},
|
||||
AssetSummary: map[string]any{
|
||||
"resources": []any{
|
||||
map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/个"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := validateRequest(req, publishRules{}); err != ErrDepositTooLow {
|
||||
t.Fatalf("expected ErrDepositTooLow, got %v", err)
|
||||
}
|
||||
|
||||
req.DepositAmount = 2.01
|
||||
if err := validateRequest(req, publishRules{}); err != nil {
|
||||
t.Fatalf("expected valid request, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user