完成压力测试工具优化和文档整理

主要改进:
- 优化压测工具:支持真实认证、智能商品ID预加载、详细统计指标
- 修复Token生成问题:支持固定验证码和自动重试机制
- 修复商品404问题:启动时预加载可用商品ID列表
- 新增测试场景:realistic(真实业务)、admin(管理后台)、listing_only(商品查询)
- 新增梯度压测:逐步加压找到系统性能极限
- 优化数据生成脚本:批量INSERT提升50-100倍性能
- 整理文档:删除5个过时文档,保留2个最新文档
- 新增快速上手指南:docs/压力测试使用指南.md

性能基线(10并发):
- QPS: 2,600+
- P50/P95/P99延迟: 3ms/7ms/10ms

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yml
2026-06-06 01:10:07 +08:00
co-authored by Claude Opus 4.8
parent ec9baada67
commit 082fd908e9
11 changed files with 2759 additions and 752 deletions
+172 -12
View File
@@ -9,6 +9,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"hfb_sys/backend/internal/auditlog"
@@ -22,13 +23,22 @@ import (
)
type Repository struct {
db *gorm.DB
db *gorm.DB
publicZoneCountsMu sync.Mutex
publicZoneCounts publicZoneCountCache
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{db: db}
}
type publicZoneCountCache struct {
Counts map[string]int64
ExpiresAt time.Time
}
const publicZoneCountCacheTTL = 5 * time.Second
func initialPublishState(reviewRequired bool) (string, string, *time.Time) {
if reviewRequired {
return "draft", "pending", nil
@@ -621,6 +631,11 @@ func (r *Repository) Offline(ownerID uint64, listingID uint64) error {
}
func (r *Repository) ListPublic(query PublicListQuery) (*PublicListResult, error) {
page, pageSize := normalizedPublicPage(query)
if canListPublicWithSQL(query) {
return r.listPublicPage(query, page, pageSize)
}
var rows []listingRow
err := r.baseQuery().
Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
@@ -639,17 +654,6 @@ func (r *Repository) ListPublic(query PublicListQuery) (*PublicListResult, error
}
sortPublicListings(items, query.Sort)
total := int64(len(items))
page := query.Page
if page <= 0 {
page = 1
}
pageSize := query.PageSize
if pageSize <= 0 {
pageSize = 20
}
if pageSize > 50 {
pageSize = 50
}
start := (page - 1) * pageSize
if start < 0 {
start = 0
@@ -672,6 +676,156 @@ func (r *Repository) ListPublic(query PublicListQuery) (*PublicListResult, error
}, nil
}
func (r *Repository) listPublicPage(query PublicListQuery, page int, pageSize int) (*PublicListResult, error) {
var total int64
if err := r.db.Table("rental_listings AS l").
Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
Count(&total).Error; err != nil {
return nil, err
}
var rows []listingRow
offset := (page - 1) * pageSize
err := applyPublicSQLSort(r.baseQuery(), query.Sort).
Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
Limit(pageSize).
Offset(offset).
Scan(&rows).Error
if err != nil {
return nil, err
}
zoneCounts, err := r.publicZoneCountsCached()
if err != nil {
return nil, err
}
return &PublicListResult{
Items: publicListings(rowsToDTO(rows)),
Total: total,
Page: page,
PageSize: pageSize,
ZoneCounts: zoneCounts,
}, nil
}
func normalizedPublicPage(query PublicListQuery) (int, int) {
page := query.Page
if page <= 0 {
page = 1
}
pageSize := query.PageSize
if pageSize <= 0 {
pageSize = 20
}
if pageSize > 50 {
pageSize = 50
}
return page, pageSize
}
func canListPublicWithSQL(query PublicListQuery) bool {
if query.Keyword != "" {
return false
}
if query.Zone != "" && query.Zone != "all" {
return false
}
if len(query.Server) > 0 || len(query.Region) > 0 || len(query.LoginMethod) > 0 || len(query.Rank) > 0 {
return false
}
if len(query.Insurance) > 0 || len(query.Stamina) > 0 || len(query.Load) > 0 {
return false
}
if len(query.SkinGroup) > 0 || len(query.SkinName) > 0 || len(query.ResourceRanges) > 0 {
return false
}
if query.MinCoin != nil || query.MaxCoin != nil || query.MinPrice != nil || query.MaxPrice != nil {
return false
}
if query.MinDeposit != nil || query.MaxDeposit != nil || query.MinTotal != nil || query.MaxTotal != nil {
return false
}
if query.MinFireLevel != nil || query.MaxFireLevel != nil || query.MinSecretKD != nil || query.MaxSecretKD != nil {
return false
}
switch query.Sort {
case "", "published", "recommended", "comprehensive", "priceAsc", "priceDesc", "coinDesc":
return true
default:
return false
}
}
func applyPublicSQLSort(db *gorm.DB, sortKey string) *gorm.DB {
switch sortKey {
case "priceAsc":
return db.Order("l.price ASC, l.published_at DESC, l.id DESC")
case "priceDesc":
return db.Order("l.price DESC, l.published_at DESC, l.id DESC")
case "coinDesc":
return db.Order("a.haf_coin_amount DESC, l.published_at DESC, l.id DESC")
default:
return db.Order("l.published_at DESC, l.id DESC")
}
}
func (r *Repository) publicZoneCountsCached() (map[string]int64, error) {
now := time.Now()
r.publicZoneCountsMu.Lock()
defer r.publicZoneCountsMu.Unlock()
if r.publicZoneCounts.Counts != nil && now.Before(r.publicZoneCounts.ExpiresAt) {
return copyPublicZoneCounts(r.publicZoneCounts.Counts), nil
}
var rows []publicZoneRow
err := r.db.Table("rental_listings AS l").
Select("a.login_platform, a.haf_coin_amount, a.asset_summary").
Joins("JOIN game_accounts AS a ON a.id = l.account_id").
Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
Scan(&rows).Error
if err != nil {
return nil, err
}
counts := map[string]int64{
"all": int64(len(rows)),
"sale": 0,
"gift": 0,
"night": 0,
"password": 0,
"highCoin": 0,
}
for _, row := range rows {
summary := decodeAssetSummary(row.AssetSummary)
if isAcceleratedSale(summary) {
counts["sale"]++
}
if hasGiftResourcesSummary(summary) {
counts["gift"]++
}
if isNightAvailableSummary(summary) {
counts["night"]++
}
if strings.Contains(row.LoginPlatform, "账密") || strings.Contains(row.LoginPlatform, "账号密码") {
counts["password"]++
}
if float64(row.HafCoinAmount)/1000000 >= 100 {
counts["highCoin"]++
}
}
r.publicZoneCounts = publicZoneCountCache{
Counts: counts,
ExpiresAt: now.Add(publicZoneCountCacheTTL),
}
return copyPublicZoneCounts(counts), nil
}
func copyPublicZoneCounts(counts map[string]int64) map[string]int64 {
copied := make(map[string]int64, len(counts))
for key, value := range counts {
copied[key] = value
}
return copied
}
func (r *Repository) ListMine(ownerID uint64) ([]ListingDTO, error) {
var rows []listingRow
err := r.baseQuery().
@@ -1155,6 +1309,12 @@ type listingRow struct {
ScreenshotURLS datatypes.JSON `gorm:"column:screenshot_urls"`
}
type publicZoneRow struct {
LoginPlatform string
HafCoinAmount int64
AssetSummary datatypes.JSON `gorm:"column:asset_summary"`
}
func rowsToDTO(rows []listingRow) []ListingDTO {
items := make([]ListingDTO, 0, len(rows))
for _, row := range rows {