feat: 排行榜改为单表(延迟/下载/上传同列),点击列头动态排序,测速后自动高亮我的成绩(含真实名次)

This commit is contained in:
dsh
2026-08-17 11:42:41 -04:00
parent 3c691c6a84
commit 56f918f33d
4 changed files with 176 additions and 75 deletions
+61 -9
View File
@@ -234,6 +234,8 @@ func (h *Handler) Result(c *gin.Context) {
// ClientIP 打码展示;ServerIP 为来源 IP(内网 IP 完整返回,供站长调试区分设备)
type RankItem struct {
ID uint `json:"id"`
Rank int `json:"rank"` // 名次(含附加记录的真实名次)
IsMine bool `json:"is_mine"` // 是否请求方指定的记录(前端高亮"我的成绩")
ClientIP string `json:"client_ip"`
ServerIP string `json:"server_ip"`
IsPrivateIP bool `json:"is_private_ip"` // ClientIP 是否为内网(探测失败回退场景)
@@ -278,18 +280,68 @@ func toRankItems(rows []db.SpeedTestResult) []RankItem {
return items
}
// Rankings 排行榜:下载榜 / 上传榜 / 延迟榜 + 总测试次数
// Rankings 排行榜:单表多指标,按 sort/order 动态排序;
// include_id 指定"我的记录"——不在榜内时附加到末尾(带真实名次与 is_mine 标记),供前端高亮
func (h *Handler) Rankings(c *gin.Context) {
download, _ := h.stores.Results.TopDownload(rankLimit)
upload, _ := h.stores.Results.TopUpload(rankLimit)
latency, _ := h.stores.Results.TopLatency(rankLimit)
total, _ := h.stores.Results.Count()
sortField := c.DefaultQuery("sort", "download")
order := c.DefaultQuery("order", "desc")
// 列名白名单,防注入
var col string
switch sortField {
case "upload":
col = "upload_mbps"
case "latency":
col = "latency_ms"
default:
sortField, col = "download", "download_mbps"
}
asc := order == "asc"
rows, _ := h.stores.Results.TopBy(col, asc, rankLimit)
items := toRankItems(rows)
for i := range items {
items[i].Rank = i + 1
}
// 附加"我的记录"(不在榜内时)
if idStr := strings.TrimSpace(c.Query("include_id")); idStr != "" {
if id, err := strconv.ParseUint(idStr, 10, 64); err == nil && id > 0 {
inList := false
for _, it := range items {
if uint64(it.ID) == id {
inList = true
break
}
}
if !inList {
if rec, err := h.stores.Results.GetByID(uint(id)); err == nil {
item := toRankItems([]db.SpeedTestResult{*rec})[0]
var val float64
switch col {
case "upload_mbps":
val = rec.UploadMbps
case "latency_ms":
val = rec.LatencyMs
default:
val = rec.DownloadMbps
}
if better, err := h.stores.Results.CountBetter(col, asc, val); err == nil {
item.Rank = int(better) + 1 // 真实名次
}
item.IsMine = true
items = append(items, item)
}
}
}
}
total, _ := h.stores.Results.Count()
c.Header("Cache-Control", "no-store")
c.JSON(http.StatusOK, gin.H{
"total": total,
"download": toRankItems(download),
"upload": toRankItems(upload),
"latency": toRankItems(latency),
"total": total,
"sort": sortField,
"order": order,
"items": items,
})
}