feat: 支持服务器主机名排序

- 服务器列表默认按主机名升序并支持表头切换

- 后端按白名单排序并区分分页缓存键
This commit is contained in:
Logan.Li committed 2026-07-26 23:21:10 +08:00
1 parent 6b5fc6a5c3
commit 09e6271bb4
5 files changed
+83 -7

No files matched your search

+14 -2
View File
@@ -74,8 +74,9 @@ func (h *ServerHandler) ListServers(c *gin.Context) {
status := c.Query("status")
regionID := c.Query("region_id")
// 构建缓存键(按查询参数区分)
// 构建缓存键(按查询参数区分) // Logan.Li
cacheKey := "servers:list:" + query.Keyword + ":" + status + ":" + regionID +
":" + query.Sort + ":" + query.Order +
":" + strconv.Itoa(query.Page) + ":" + strconv.Itoa(query.PageSize)
const cacheTTL = 10 * time.Second
@@ -114,7 +115,7 @@ func (h *ServerHandler) ListServers(c *gin.Context) {
var servers []model.Server
db.Preload("Region").Preload("Cloud").
Offset(query.GetOffset()).Limit(query.GetLimit()).
Order("created_at DESC").
Order(serverOrderClause(query.Sort, query.Order)).
Find(&servers)
// 填充 has_auth 字段(密码和SSH密钥在JSON中隐藏,但前端需知道是否有凭据) // Logan.Li
@@ -142,6 +143,17 @@ func (h *ServerHandler) ListServers(c *gin.Context) {
c.JSON(http.StatusOK, resp)
}
// serverOrderClause 返回服务器列表允许使用的排序表达式,避免直接拼接请求参数。 // Logan.Li
func serverOrderClause(sortField, order string) string {
if sortField != "hostname" {
return "created_at DESC"
}
if order == "desc" {
return "LOWER(hostname) DESC, id DESC"
}
return "LOWER(hostname) ASC, id ASC"
}
// GetServer 获取服务器详情
// @Summary 获取服务器详情
// @Description 获取指定服务器的详细信息
+27
View File
@@ -0,0 +1,27 @@
package controller
import "testing"
// TestServerOrderClause 验证服务器排序字段仅使用后端白名单表达式。 // Logan.Li
func TestServerOrderClause(t *testing.T) {
tests := []struct {
name string
sortField string
order string
want string
}{
{name: "默认排序", want: "created_at DESC"},
{name: "主机名升序", sortField: "hostname", order: "asc", want: "LOWER(hostname) ASC, id ASC"},
{name: "主机名降序", sortField: "hostname", order: "desc", want: "LOWER(hostname) DESC, id DESC"},
{name: "非法方向回退升序", sortField: "hostname", order: "invalid", want: "LOWER(hostname) ASC, id ASC"},
{name: "非法字段回退默认", sortField: "hostname; DROP TABLE sys_servers", order: "asc", want: "created_at DESC"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := serverOrderClause(tt.sortField, tt.order); got != tt.want {
t.Fatalf("serverOrderClause(%q, %q) = %q, want %q", tt.sortField, tt.order, got, tt.want)
}
})
}
}