测试库不再使用 SQLite 内存库,改为每个测试包独立的 MySQL 库, 表结构由 backend/migrations 的真实迁移建立。 基础设施 - 新增 internal/database/testdb.go:NewTestDB() 保持原签名(28 处调用零改动), 内部连接 MySQL 测试库,首次调用重建库并执行全部真实迁移,之后每次调用清空全表 - 库名 = 测试库基础名 + 包目录名,同包共享、跨包隔离,使 go test ./... 可并发执行 (共用一库时各包会互相 DROP/CREATE 导致 database exists) - 新增 NewDryRunDB():MySQL 方言 + DryRun,供断言 SQL 形态的 5 处测试使用 - 新增 AutoMigrateIfMissing():只对不存在的表 AutoMigrate, 避免改动迁移定义的索引与约束 - 新增 scripts/prepare-test-db.sh:建库与通配授权,可重复执行 - 33 处 sqlite.Open 全部替换,26 个测试文件改用 MySQL - 删除 46 处手写 SQLite 方言 DDL(AUTOINCREMENT / INTEGER PRIMARY KEY / TEXT), 表统一由真实迁移建立 - go.mod 移除 gorm.io/driver/sqlite 与 mattn/go-sqlite3 - 删除生产代码里 distribution/repository.go 的 sqlite 方言分支(strftime) 对齐环境后暴露并修复的真实问题 - DATE(col) 在 MySQL 返回 DATE 类型,使 Date string 字段变成 RFC3339 时间串, 大盘日期会带出时间部分;28 处改为 DATE_FORMAT(col, '%Y-%m-%d') - 系统自动创建分销结算批次时 created_by 写入 0,违反指向 admin_users 的外键, 生产会直接插入失败;改为写 NULL(新增 adminRef helper) - MySQL DATETIME 仅到秒,静默截断 Go 侧写入的时间, 导致租用时长计算与结算时点回读比对偏差; rental_orders 的 9 个业务时间列提到 DATETIME(6) - 测试数据补齐严格模式要求的 NOT NULL 列、修正超出列长的取值、补齐外键父记录 迁移 - 000065 的 ALTER TABLE rental_orders 一并完成业务时间列精度调整, feature 侧不再单开迁移文件 脚本与文档 - check.sh / check-backend-coverage.sh 去掉 -p 1 串行与 SQLite 相关注释 - 更新 docs/Repository层测试补充总结.md 的测试运行说明
272 lines
10 KiB
Go
272 lines
10 KiB
Go
package listing
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/auditlog"
|
|
)
|
|
|
|
type ListingDTO struct {
|
|
ID uint64 `json:"id"`
|
|
ListingNo string `json:"listing_no"`
|
|
AccountID uint64 `json:"account_id"`
|
|
OwnerID uint64 `json:"owner_id"`
|
|
OwnerPhone string `json:"owner_phone,omitempty"`
|
|
OwnerNickname string `json:"owner_nickname,omitempty"`
|
|
SourceChannel string `json:"source_channel,omitempty"`
|
|
IsExternalUpload bool `json:"is_external_upload"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
GameName string `json:"game_name"`
|
|
ServerRegion string `json:"server_region"`
|
|
LoginPlatform string `json:"login_platform"`
|
|
RankLevel string `json:"rank_level"`
|
|
HafCoinAmount int64 `json:"haf_coin_amount"`
|
|
AssetSummary map[string]any `json:"asset_summary,omitempty"`
|
|
ScreenshotURLS []string `json:"screenshot_urls"`
|
|
CoverURL string `json:"cover_url"`
|
|
PriceCent int64 `json:"price_cent"`
|
|
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
|
IsAccelerated bool `json:"is_accelerated_sale"`
|
|
InTransaction bool `json:"in_transaction"`
|
|
Status string `json:"status"`
|
|
ReviewStatus string `json:"review_status"`
|
|
HandoffMode string `json:"handoff_mode"`
|
|
SettlementMode string `json:"settlement_mode"`
|
|
ManagedAdminID *uint64 `json:"managed_admin_id,omitempty"`
|
|
ReviewReason string `json:"review_reason"`
|
|
PublishedAt *time.Time `json:"published_at"`
|
|
DistributionSiteID *uint64 `json:"distribution_site_id,omitempty"`
|
|
DistributionSiteCode string `json:"distribution_site_code,omitempty"`
|
|
DistributionSiteName string `json:"distribution_site_name,omitempty"`
|
|
ListingGroupConversationID uint64 `json:"listing_group_conversation_id,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type CreateRequest struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Description string `json:"description"`
|
|
ServerRegion string `json:"server_region" binding:"required"`
|
|
LoginPlatform string `json:"login_platform"`
|
|
RankLevel string `json:"rank_level"`
|
|
HafCoinAmount int64 `json:"haf_coin_amount"`
|
|
AssetSummary map[string]any `json:"asset_summary"`
|
|
ScreenshotURLS []string `json:"screenshot_urls"`
|
|
PriceCent int64 `json:"price_cent"`
|
|
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
|
|
|
AgreedVirtualAssetSale bool `json:"agreed_virtual_asset_sale"`
|
|
AgreedSellerAgreement bool `json:"agreed_seller_agreement"`
|
|
}
|
|
|
|
type UpdateRequest = CreateRequest
|
|
|
|
type ReviewRequest struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
type AdminPriceAdjustRequest struct {
|
|
BuyerRatio float64 `json:"buyer_ratio"`
|
|
BuyerTotalPriceCent int64 `json:"buyer_total_price_cent"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
const (
|
|
ExternalAdjustModeDiscountAmount = "discount_amount"
|
|
ExternalAdjustModeSaleRatio = "sale_ratio"
|
|
)
|
|
|
|
type AdminExternalListingAdjustRequest struct {
|
|
HafCoinAmount int64 `json:"haf_coin_amount" binding:"required,min=1"`
|
|
AWMBullets int `json:"awm_bullets" binding:"min=0,max=1000000"`
|
|
Level6Helmets int `json:"level6_helmets" binding:"min=0,max=1000000"`
|
|
Level6Armor int `json:"level6_armor" binding:"min=0,max=1000000"`
|
|
DepositAmountCent int64 `json:"deposit_amount_cent" binding:"min=0"`
|
|
AdjustMode string `json:"adjust_mode" binding:"required,oneof=discount_amount sale_ratio"`
|
|
DiscountAmountCent int64 `json:"discount_amount_cent" binding:"min=0"`
|
|
SaleRatio float64 `json:"sale_ratio" binding:"min=0"`
|
|
Reason string `json:"reason" binding:"required,max=255"`
|
|
}
|
|
|
|
type TransferOwnerRequest struct {
|
|
TargetUserID uint64 `json:"target_user_id" binding:"required"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
type AdminUpdateScreenshotsRequest struct {
|
|
ScreenshotURLs []string `json:"screenshot_urls" binding:"required,min=1"`
|
|
ScreenshotGroups map[string]any `json:"screenshot_groups"`
|
|
}
|
|
|
|
type AdminListQuery struct {
|
|
OwnerID uint64
|
|
Keyword string
|
|
Status string
|
|
ReviewStatus string
|
|
Insurance []string
|
|
Stamina []string
|
|
Load []string
|
|
SkinGroup []string
|
|
SkinName []string
|
|
MinCoin *float64
|
|
MaxCoin *float64
|
|
MinDeposit *float64
|
|
MaxDeposit *float64
|
|
Limit int
|
|
Page int
|
|
PageSize int
|
|
}
|
|
|
|
type AdminListResult struct {
|
|
Items []ListingDTO `json:"items"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
}
|
|
|
|
type PublicListQuery struct {
|
|
Page int
|
|
PageSize int
|
|
Keyword string
|
|
Sort string
|
|
Zone string
|
|
Server []string
|
|
Region []string
|
|
LoginMethod []string
|
|
Rank []string
|
|
Insurance []string
|
|
Stamina []string
|
|
Load []string
|
|
SkinGroup []string
|
|
SkinName []string
|
|
MinCoin *float64
|
|
MaxCoin *float64
|
|
MinRatio *float64
|
|
MaxRatio *float64
|
|
MinPrice *float64
|
|
MaxPrice *float64
|
|
MinDeposit *float64
|
|
MaxDeposit *float64
|
|
MinTotal *float64
|
|
MaxTotal *float64
|
|
MinFireLevel *float64
|
|
MaxFireLevel *float64
|
|
MinDailyLoss *float64
|
|
MaxDailyLoss *float64
|
|
MinSecretKD *float64
|
|
MaxSecretKD *float64
|
|
ResourceRanges map[string]NumberRange
|
|
DistributionSiteCode string
|
|
}
|
|
|
|
type NumberRange struct {
|
|
Min *float64
|
|
Max *float64
|
|
}
|
|
|
|
type PublicListResult struct {
|
|
Items []ListingDTO `json:"items"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
ZoneCounts map[string]int64 `json:"zone_counts"`
|
|
}
|
|
|
|
type AdminActionRequest struct {
|
|
Reason string `json:"reason" binding:"required"`
|
|
}
|
|
|
|
type AuditMeta = auditlog.Meta
|
|
|
|
type ExternalUploadRequest struct {
|
|
UploadTime int64 `json:"uploadTime"`
|
|
UploaderName string `json:"uploaderName"`
|
|
Uploaderame string `json:"uploaderame"`
|
|
SourceChannel string `json:"sourceChannel"`
|
|
SourceChannelSnake string `json:"source_channel"`
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
|
|
func (r ExternalUploadRequest) normalizedUploaderName() string {
|
|
if name := strings.TrimSpace(r.UploaderName); name != "" {
|
|
return name
|
|
}
|
|
return strings.TrimSpace(r.Uploaderame)
|
|
}
|
|
|
|
func (r ExternalUploadRequest) normalizedSourceChannel() string {
|
|
if channel := normalizeExternalSourceChannel(r.SourceChannel); channel != "" {
|
|
return channel
|
|
}
|
|
return normalizeExternalSourceChannel(r.SourceChannelSnake)
|
|
}
|
|
|
|
type ExternalAccountData struct {
|
|
LoginMethod string `json:"loginMethod"`
|
|
Rank string `json:"rank"`
|
|
Level int `json:"level"`
|
|
SafeSlots int `json:"safeSlots"`
|
|
SecretKD float64 `json:"secretKD"`
|
|
DailyLossM float64 `json:"dailyLossM"`
|
|
Deposit float64 `json:"deposit"`
|
|
BanRecord string `json:"banRecord"`
|
|
CommonRegion string `json:"commonRegion"`
|
|
ContactPhone string `json:"contactPhone"`
|
|
OwnerOnlineTime string `json:"ownerOnlineTime"`
|
|
Remark string `json:"remark"`
|
|
Currency ExternalUploadCurrency `json:"currency"`
|
|
DailyConsumption ExternalDailyConsumption `json:"dailyConsumption"`
|
|
Inventory ExternalUploadInventory `json:"inventory"`
|
|
}
|
|
|
|
type ExternalUploadCurrency struct {
|
|
HafuCoin float64 `json:"hafuCoin"`
|
|
RecycleRatio float64 `json:"recycleRatio"`
|
|
RecycleRent float64 `json:"recycleRent"`
|
|
}
|
|
|
|
type ExternalDailyConsumption struct {
|
|
Stamina int `json:"stamina"`
|
|
Weight int `json:"weight"`
|
|
}
|
|
|
|
type ExternalUploadInventory struct {
|
|
AWMBullets int `json:"awmBullets"`
|
|
Level6Helmets int `json:"level6Helmets"`
|
|
Level6Armor int `json:"level6Armor"`
|
|
Skins []string `json:"skins"`
|
|
}
|
|
|
|
type ExternalUploadMeta struct {
|
|
IP string
|
|
UserAgent string
|
|
RawPayload []byte
|
|
}
|
|
|
|
type ExternalUploadResult struct {
|
|
Index int `json:"index"`
|
|
ListingID uint64 `json:"listing_id,omitempty"`
|
|
ListingNo string `json:"listing_no,omitempty"`
|
|
AccountID uint64 `json:"account_id,omitempty"`
|
|
ListingGroupConversationID uint64 `json:"listing_group_conversation_id,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
ReviewStatus string `json:"review_status,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type ExternalUploadResponse struct {
|
|
ListingID uint64 `json:"listing_id,omitempty"`
|
|
ListingNo string `json:"listing_no,omitempty"`
|
|
AccountID uint64 `json:"account_id,omitempty"`
|
|
ListingGroupConversationID uint64 `json:"listing_group_conversation_id,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
ReviewStatus string `json:"review_status,omitempty"`
|
|
Total int `json:"total"`
|
|
Success int `json:"success"`
|
|
Failed int `json:"failed"`
|
|
Items []ExternalUploadResult `json:"items,omitempty"`
|
|
}
|