Skip to content

Commit 4433ab5

Browse files
committed
[功能] 添加应用日志分页查询和清理功能,优化日志管理逻辑
1 parent 0ab0145 commit 4433ab5

File tree

9 files changed

+790
-370
lines changed

9 files changed

+790
-370
lines changed

openflare_server/controller/agent.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package controller
33
import (
44
"openflare/model"
55
"openflare/service"
6+
"strconv"
67

78
"github.com/gin-gonic/gin"
89
)
@@ -144,10 +145,45 @@ func GetNodes(c *gin.Context) {
144145
// @Success 200 {object} map[string]interface{}
145146
// @Router /api/apply-logs/ [get]
146147
func GetApplyLogs(c *gin.Context) {
147-
logs, err := service.ListApplyLogs(c.Query("node_id"))
148+
logs, err := service.ListApplyLogsPage(service.ApplyLogListQuery{
149+
NodeID: c.Query("node_id"),
150+
PageNo: readIntQueryFallback(c, "pageNo", "page_no"),
151+
PageSize: readIntQueryFallback(c, "pageSize", "page_size"),
152+
})
148153
if err != nil {
149154
respondFailure(c, err.Error())
150155
return
151156
}
152157
respondSuccess(c, logs)
153158
}
159+
160+
// CleanupApplyLogs godoc
161+
// @Summary Cleanup apply logs
162+
// @Tags ApplyLogs
163+
// @Accept json
164+
// @Produce json
165+
// @Security BearerAuth
166+
// @Success 200 {object} map[string]interface{}
167+
// @Router /api/apply-logs/cleanup [post]
168+
func CleanupApplyLogs(c *gin.Context) {
169+
var input service.ApplyLogCleanupInput
170+
if err := c.ShouldBindJSON(&input); err != nil {
171+
respondBadRequest(c, "")
172+
return
173+
}
174+
result, err := service.CleanupApplyLogs(input)
175+
if err != nil {
176+
respondFailure(c, err.Error())
177+
return
178+
}
179+
respondSuccess(c, result)
180+
}
181+
182+
func readIntQueryFallback(c *gin.Context, primary string, secondary string) int {
183+
value := c.Query(primary)
184+
if value == "" {
185+
value = c.Query(secondary)
186+
}
187+
parsed, _ := strconv.Atoi(value)
188+
return parsed
189+
}

openflare_server/model/apply_log.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
package model
22

3-
import "time"
3+
import (
4+
"time"
5+
6+
"gorm.io/gorm"
7+
)
8+
9+
type ApplyLogQuery struct {
10+
NodeID string
11+
PageNo int
12+
PageSize int
13+
}
414

515
type ApplyLog struct {
616
ID uint `json:"id" gorm:"primaryKey"`
@@ -15,13 +25,29 @@ type ApplyLog struct {
1525
CreatedAt time.Time `json:"created_at"`
1626
}
1727

18-
func ListApplyLogs(nodeID string) (logs []*ApplyLog, err error) {
19-
query := DB.Order("id desc")
28+
func ListApplyLogs(query ApplyLogQuery) (logs []*ApplyLog, err error) {
29+
db := DB.Order("id desc")
30+
if query.NodeID != "" {
31+
db = db.Where("node_id = ?", query.NodeID)
32+
}
33+
if query.PageSize > 0 {
34+
offset := 0
35+
if query.PageNo > 1 {
36+
offset = (query.PageNo - 1) * query.PageSize
37+
}
38+
db = db.Limit(query.PageSize).Offset(offset)
39+
}
40+
err = db.Find(&logs).Error
41+
return logs, err
42+
}
43+
44+
func CountApplyLogs(nodeID string) (total int64, err error) {
45+
query := DB.Model(&ApplyLog{})
2046
if nodeID != "" {
2147
query = query.Where("node_id = ?", nodeID)
2248
}
23-
err = query.Find(&logs).Error
24-
return logs, err
49+
err = query.Count(&total).Error
50+
return total, err
2551
}
2652

2753
func GetLatestApplyLog(nodeID string) (*ApplyLog, error) {
@@ -49,3 +75,13 @@ func GetLatestApplyLogsByNodeIDs(nodeIDs []string) (map[string]*ApplyLog, error)
4975
}
5076
return result, nil
5177
}
78+
79+
func DeleteAllApplyLogs() (deleted int64, err error) {
80+
result := DB.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&ApplyLog{})
81+
return result.RowsAffected, result.Error
82+
}
83+
84+
func DeleteApplyLogsBefore(before time.Time) (deleted int64, err error) {
85+
result := DB.Where("created_at < ?", before).Delete(&ApplyLog{})
86+
return result.RowsAffected, result.Error
87+
}

openflare_server/router/api-router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func SetApiRouter(router *gin.Engine) {
134134
applyLogRoute.Use(middleware.AdminAuth())
135135
{
136136
applyLogRoute.GET("/", controller.GetApplyLogs)
137+
applyLogRoute.POST("/cleanup", controller.CleanupApplyLogs)
137138
}
138139
accessLogRoute := apiRouter.Group("/access-logs")
139140
accessLogRoute.Use(middleware.AdminAuth())

openflare_server/router/api_phase2_test.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,49 @@ func TestPhase2AgentLifecycle(t *testing.T) {
323323
t.Fatal("expected heartbeat response to include active config summary")
324324
}
325325

326-
logsResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/apply-logs/?node_id="+createdNode.NodeID, nil)
327-
var logs []model.ApplyLog
326+
logsResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/apply-logs/?node_id="+createdNode.NodeID+"&pageNo=1&pageSize=1", nil)
327+
var logs service.ApplyLogListResult
328328
decodeResponseData(t, logsResp, &logs)
329-
if len(logs) != 2 {
330-
t.Fatalf("expected 2 apply logs, got %d", len(logs))
329+
if logs.Current != 1 || logs.Total != 2 || logs.TotalPage != 2 {
330+
t.Fatalf("unexpected paged apply logs result: %+v", logs)
331+
}
332+
if len(logs.Rows) != 1 {
333+
t.Fatalf("expected 1 apply log row on page 1, got %d", len(logs.Rows))
334+
}
335+
if logs.Rows[0].Result != service.ApplyResultFailed {
336+
t.Fatalf("expected newest apply log first, got %s", logs.Rows[0].Result)
337+
}
338+
oldApplyLogTime := time.Now().Add(-48 * time.Hour)
339+
if err := model.DB.Model(&model.ApplyLog{}).Where("id = ?", successApplyLog.ID).Update("created_at", oldApplyLogTime).Error; err != nil {
340+
t.Fatalf("failed to backdate apply log: %v", err)
341+
}
342+
cleanupResp := performJSONRequest(t, engine, adminToken, http.MethodPost, "/api/apply-logs/cleanup", map[string]any{
343+
"retention_days": 1,
344+
})
345+
var cleanupResult service.ApplyLogCleanupResult
346+
decodeResponseData(t, cleanupResp, &cleanupResult)
347+
if cleanupResult.DeleteAll {
348+
t.Fatal("expected retention cleanup instead of delete-all cleanup")
349+
}
350+
if cleanupResult.RetentionDays != 1 || cleanupResult.DeletedCount != 1 {
351+
t.Fatalf("unexpected cleanup result: %+v", cleanupResult)
352+
}
353+
postCleanupResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/apply-logs/?node_id="+createdNode.NodeID, nil)
354+
decodeResponseData(t, postCleanupResp, &logs)
355+
if logs.Total != 1 || len(logs.Rows) != 1 {
356+
t.Fatalf("expected one apply log after retention cleanup, got %+v", logs)
357+
}
358+
deleteAllResp := performJSONRequest(t, engine, adminToken, http.MethodPost, "/api/apply-logs/cleanup", map[string]any{
359+
"delete_all": true,
360+
})
361+
decodeResponseData(t, deleteAllResp, &cleanupResult)
362+
if !cleanupResult.DeleteAll || cleanupResult.DeletedCount != 1 {
363+
t.Fatalf("unexpected delete-all cleanup result: %+v", cleanupResult)
364+
}
365+
emptyLogsResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/apply-logs/?node_id="+createdNode.NodeID, nil)
366+
decodeResponseData(t, emptyLogsResp, &logs)
367+
if logs.Total != 0 || len(logs.Rows) != 0 || logs.Current != 1 || logs.TotalPage != 0 {
368+
t.Fatalf("expected empty apply log page after delete-all cleanup, got %+v", logs)
331369
}
332370

333371
updatedNodeResp := performJSONRequest(t, engine, adminToken, http.MethodPost, "/api/nodes/"+toString(createdNode.ID)+"/update", map[string]any{

openflare_server/service/agent.go

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,31 @@ type ApplyLogPayload struct {
5353
SupportFileCount int `json:"support_file_count"`
5454
}
5555

56+
type ApplyLogListQuery struct {
57+
NodeID string `json:"node_id"`
58+
PageNo int `json:"pageNo"`
59+
PageSize int `json:"pageSize"`
60+
}
61+
62+
type ApplyLogListResult struct {
63+
Rows []*model.ApplyLog `json:"rows"`
64+
Current int `json:"current"`
65+
Total int `json:"total"`
66+
TotalPage int `json:"totalPage"`
67+
}
68+
69+
type ApplyLogCleanupInput struct {
70+
DeleteAll bool `json:"delete_all"`
71+
RetentionDays int `json:"retention_days"`
72+
}
73+
74+
type ApplyLogCleanupResult struct {
75+
DeleteAll bool `json:"delete_all"`
76+
RetentionDays int `json:"retention_days"`
77+
DeletedCount int64 `json:"deleted_count"`
78+
Cutoff *time.Time `json:"cutoff,omitempty"`
79+
}
80+
5681
type AgentConfigResponse struct {
5782
Version string `json:"version"`
5883
Checksum string `json:"checksum"`
@@ -314,8 +339,81 @@ func ListNodeViews() ([]*NodeView, error) {
314339
return views, nil
315340
}
316341

317-
func ListApplyLogs(nodeID string) ([]*model.ApplyLog, error) {
318-
return model.ListApplyLogs(strings.TrimSpace(nodeID))
342+
const (
343+
defaultApplyLogPageSize = 20
344+
maxApplyLogPageSize = 200
345+
maxApplyLogRetentionDays = 3650
346+
)
347+
348+
func ListApplyLogsPage(input ApplyLogListQuery) (*ApplyLogListResult, error) {
349+
pageNo := normalizeApplyLogPageNo(input.PageNo)
350+
pageSize := normalizeApplyLogPageSize(input.PageSize)
351+
nodeID := strings.TrimSpace(input.NodeID)
352+
rows, err := model.ListApplyLogs(model.ApplyLogQuery{
353+
NodeID: nodeID,
354+
PageNo: pageNo,
355+
PageSize: pageSize,
356+
})
357+
if err != nil {
358+
return nil, err
359+
}
360+
total, err := model.CountApplyLogs(nodeID)
361+
if err != nil {
362+
return nil, err
363+
}
364+
totalPage := 0
365+
if total > 0 {
366+
totalPage = int((total + int64(pageSize) - 1) / int64(pageSize))
367+
}
368+
return &ApplyLogListResult{
369+
Rows: rows,
370+
Current: pageNo,
371+
Total: int(total),
372+
TotalPage: totalPage,
373+
}, nil
374+
}
375+
376+
func CleanupApplyLogs(input ApplyLogCleanupInput) (*ApplyLogCleanupResult, error) {
377+
if input.DeleteAll {
378+
deleted, err := model.DeleteAllApplyLogs()
379+
if err != nil {
380+
return nil, err
381+
}
382+
return &ApplyLogCleanupResult{
383+
DeleteAll: true,
384+
DeletedCount: deleted,
385+
}, nil
386+
}
387+
if input.RetentionDays <= 0 || input.RetentionDays > maxApplyLogRetentionDays {
388+
return nil, errors.New("retention_days 必须在 1 到 3650 之间")
389+
}
390+
cutoff := time.Now().UTC().Add(-time.Duration(input.RetentionDays) * 24 * time.Hour)
391+
deleted, err := model.DeleteApplyLogsBefore(cutoff)
392+
if err != nil {
393+
return nil, err
394+
}
395+
return &ApplyLogCleanupResult{
396+
RetentionDays: input.RetentionDays,
397+
DeletedCount: deleted,
398+
Cutoff: &cutoff,
399+
}, nil
400+
}
401+
402+
func normalizeApplyLogPageNo(pageNo int) int {
403+
if pageNo <= 0 {
404+
return 1
405+
}
406+
return pageNo
407+
}
408+
409+
func normalizeApplyLogPageSize(pageSize int) int {
410+
if pageSize <= 0 {
411+
return defaultApplyLogPageSize
412+
}
413+
if pageSize > maxApplyLogPageSize {
414+
return maxApplyLogPageSize
415+
}
416+
return pageSize
319417
}
320418

321419
func upsertNode(payload AgentNodePayload) (*model.Node, error) {
Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
11
import { apiRequest } from '@/lib/api/client';
22

3-
import type { ApplyLogItem } from '@/features/apply-logs/types';
3+
import type {
4+
ApplyLogCleanupPayload,
5+
ApplyLogCleanupResult,
6+
ApplyLogList,
7+
ApplyLogListQuery,
8+
} from '@/features/apply-logs/types';
49

5-
export function getApplyLogs(nodeId?: string) {
6-
const normalizedNodeId = nodeId?.trim();
7-
const query = normalizedNodeId ? `?node_id=${encodeURIComponent(normalizedNodeId)}` : '';
8-
return apiRequest<ApplyLogItem[]>(`/apply-logs/${query}`);
10+
export function getApplyLogs(query: ApplyLogListQuery = {}) {
11+
const params = new URLSearchParams();
12+
const normalizedNodeId = query.node_id?.trim();
13+
if (normalizedNodeId) {
14+
params.set('node_id', normalizedNodeId);
15+
}
16+
if (query.pageNo) {
17+
params.set('pageNo', String(query.pageNo));
18+
}
19+
if (query.pageSize) {
20+
params.set('pageSize', String(query.pageSize));
21+
}
22+
const suffix = params.size > 0 ? `?${params.toString()}` : '';
23+
return apiRequest<ApplyLogList>(`/apply-logs/${suffix}`);
24+
}
25+
26+
export function cleanupApplyLogs(payload: ApplyLogCleanupPayload) {
27+
return apiRequest<ApplyLogCleanupResult>('/apply-logs/cleanup', {
28+
method: 'POST',
29+
body: JSON.stringify(payload),
30+
});
931
}

0 commit comments

Comments
 (0)