diff --git a/apis/hole/apis.go b/apis/hole/apis.go index 2bae997a..3360f37e 100644 --- a/apis/hole/apis.go +++ b/apis/hole/apis.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "math/rand" "net/http" "slices" "strconv" @@ -992,12 +993,12 @@ func GenerateSummary(c *fiber.Ctx) error { }) } if forceRefresh { - err := DeleteCache(fmt.Sprintf("AISummary_%d_v%d", id, hole.Reply-hole.Reply%config.Config.SummarySteps)) + err := DeleteCache(hole.SummaryCacheName()) if err != nil { log.Err(err).Msg("AISummary: delete cache err") } } - if GetCache(fmt.Sprintf("AISummary_%d_v%d", id, hole.Reply-hole.Reply%config.Config.SummarySteps), &cachedData) { + if GetCache(hole.SummaryCacheName(), &cachedData) { switch cachedData.Code { case 1000: @@ -1053,7 +1054,7 @@ func GenerateSummary(c *fiber.Ctx) error { Msg("AISummary: hole id error") cachedData.Data.HoleID = id } - err = SetCache(fmt.Sprintf("AISummary_%d_v%d", id, hole.Reply-hole.Reply%config.Config.SummarySteps), cachedData, 24*time.Hour) + err = SetCache(hole.SummaryCacheName(), cachedData, 24*time.Hour) if err != nil { log.Err(err).Msg("AISummary: set cache err") } @@ -1061,14 +1062,14 @@ func GenerateSummary(c *fiber.Ctx) error { return c.Status(200).JSON(cachedData) } else { - err := DeleteCache(fmt.Sprintf("AISummary_%d_v%d", id, hole.Reply-hole.Reply%config.Config.SummarySteps)) + err := DeleteCache(hole.SummaryCacheName()) if err != nil { log.Err(err).Msg("AISummary: delete cache err") } } default: - err := DeleteCache("AISummary" + strconv.Itoa(id)) + err := DeleteCache(hole.SummaryCacheName()) if err != nil { log.Err(err).Msg("AISummary: delete cache err") } @@ -1087,6 +1088,54 @@ func GenerateSummary(c *fiber.Ctx) error { if err != nil { return err } + + if len(floors) > config.Config.MaxSummaryFloors { + // 1. 按 UpdatedAt 升序排序 + slices.SortFunc(floors, func(a, b *Floor) int { + if a.UpdatedAt.Equal(b.UpdatedAt) { + return 0 + } + if a.UpdatedAt.Before(b.UpdatedAt) { + return -1 + } + return 1 + }) + + // 2. 提取头尾 + earliestFloors := config.Config.MaxSummaryFloors / 5 + newestFloors := config.Config.MaxSummaryFloors / 5 * 2 + earliest := floors[:earliestFloors] + newest := floors[len(floors)-newestFloors:] + + // 3. 提取中间并按 Like 降序筛选 + middle := make(Floors, len(floors)-earliestFloors-newestFloors) + copy(middle, floors[earliestFloors:len(floors)-newestFloors]) // copy 防止影响原切片顺序 + + rand.Shuffle(len(middle), func(i, j int) { + middle[i], middle[j] = middle[j], middle[i] + }) + + slices.SortFunc(middle, func(a, b *Floor) int { + return b.Like - a.Like + }) + + // 4.即使中间不足20条也能安全截取 (len(floors)>50 保证了 middle 长度至少为 21) + topLikes := middle[:config.Config.MaxSummaryFloors-earliestFloors-newestFloors] + + // 5. 合并 + newFloors := make(Floors, 0, config.Config.MaxSummaryFloors) + newFloors = append(newFloors, earliest...) + newFloors = append(newFloors, topLikes...) + newFloors = append(newFloors, newest...) + + floors = newFloors + + // 6. 按 ID 重新排序以恢复对话流上下文 + slices.SortFunc(floors, func(a, b *Floor) int { + return a.Ranking - b.Ranking + }) + } + content := "" if hole.HoleFloor.FirstFloor != nil { content = hole.HoleFloor.FirstFloor.Content @@ -1105,7 +1154,6 @@ func GenerateSummary(c *fiber.Ctx) error { FloorID: floor.FloorID, } } - requestBody := map[string]any{ "floors": summaryFloors, "content": content, @@ -1185,7 +1233,7 @@ func GenerateSummary(c *fiber.Ctx) error { case 1000, 1001: var cache Summary cache.Code = 1001 - err := SetCache(fmt.Sprintf("AISummary_%d_v%d", id, hole.Reply-hole.Reply%config.Config.SummarySteps), cache, 24*time.Hour) + err := SetCache(hole.SummaryCacheName(), cache, 24*time.Hour) if err != nil { log.Err(err).Msg("AISummary: set cache err") } diff --git a/config/config.go b/config/config.go index 224cd07b..1de28637 100644 --- a/config/config.go +++ b/config/config.go @@ -59,8 +59,12 @@ var Config struct { SummaryFloorLimit int `env:"SUMMARY_FLOOR_LIMIT" envDefault:"15"` SummaryContentLimit int64 `env:"SUMMARY_CONTENT_LIMIT" envDefault:"500"` SummarySteps int `env:"SUMMARY_STEPS" envDefault:"5"` + SummaryReplyBoundary1 int `env:"SUMMARY_REPLY_BOUNDARY_1" envDefault:"50"` + SummaryReplyBoundary2 int `env:"SUMMARY_REPLY_BOUNDARY_1" envDefault:"200"` + SummaryReplyBoundary3 int `env:"SUMMARY_REPLY_BOUNDARY_1" envDefault:"1000"` SummaryLogLimit int `env:"SUMMARY_LOG_LIMIT" envDefault:"1000"` WhiteListUserIds []int `env:"WHITE_LIST_USER_IDS"` + MaxSummaryFloors int `env:"MAX_FLOORS_PER_HOLE" envDefault:"50"` } var DynamicConfig struct { diff --git a/models/hole.go b/models/hole.go index 9992b593..e43654cf 100644 --- a/models/hole.go +++ b/models/hole.go @@ -98,6 +98,20 @@ func (hole *Hole) CacheName() string { return fmt.Sprintf("hole_%d", hole.ID) } +func (hole *Hole) SummaryCacheName() string { + var step int + if hole.Reply <= config.Config.SummaryReplyBoundary1 { + step = config.Config.SummarySteps + } else if hole.Reply > config.Config.SummaryReplyBoundary1 && hole.Reply <= config.Config.SummaryReplyBoundary2 { + step = config.Config.SummarySteps * 2 + } else if hole.Reply > config.Config.SummaryReplyBoundary2 && hole.Reply <= config.Config.SummaryReplyBoundary3 { + step = config.Config.SummarySteps * 4 + } else { + step = config.Config.SummarySteps * 10 + } + return fmt.Sprintf("AISummary_%d_v%d", hole.ID, hole.Reply-hole.Reply%step) +} + type Holes []*Hole func IsHolesExist(tx *gorm.DB, holeID []int) bool {