初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"oci-portal/internal/model"
|
||||
)
|
||||
|
||||
// 系统日志保留策略与异步写入超时。
|
||||
const (
|
||||
systemLogRetention = 90 * 24 * time.Hour // 过期删除阈值
|
||||
systemLogMaxRows = 50000 // 总量兜底上限,超出删最旧
|
||||
systemLogCleanupTick = 24 * time.Hour // 周期清理间隔
|
||||
systemLogWriteWait = 3 * time.Second // 单条异步写入超时
|
||||
)
|
||||
|
||||
// 系统日志查询分页默认值与上限。
|
||||
const (
|
||||
systemLogDefaultPageSize = 20
|
||||
systemLogMaxPageSize = 100
|
||||
)
|
||||
|
||||
// SystemLogService 记录并查询面板自身的操作日志(区别于 OCI 云端审计事件)。
|
||||
type SystemLogService struct {
|
||||
db *gorm.DB
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewSystemLogService 组装依赖;调用 StartCleanup 后开始周期清理。
|
||||
func NewSystemLogService(db *gorm.DB) *SystemLogService {
|
||||
return &SystemLogService{db: db}
|
||||
}
|
||||
|
||||
// Record 异步落一条日志,不阻塞业务请求;写入失败只记日志。
|
||||
func (s *SystemLogService) Record(entry model.SystemLog) {
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), systemLogWriteWait)
|
||||
defer cancel()
|
||||
if err := s.record(ctx, entry); err != nil {
|
||||
log.Printf("system log record: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// record 同步落库,供 Record 内部与测试直调。
|
||||
func (s *SystemLogService) record(ctx context.Context, entry model.SystemLog) error {
|
||||
if entry.CreatedAt.IsZero() {
|
||||
entry.CreatedAt = time.Now()
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Create(&entry).Error; err != nil {
|
||||
return fmt.Errorf("create system log: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait 等待在途异步写入与清理 goroutine 退出,供进程收尾与测试同步。
|
||||
func (s *SystemLogService) Wait() {
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
// SystemLogQuery 是日志查询参数;Keyword 对路径与用户名模糊匹配。
|
||||
type SystemLogQuery struct {
|
||||
Page int
|
||||
PageSize int
|
||||
Keyword string
|
||||
}
|
||||
|
||||
// normalize 补分页默认值并钳制上限。
|
||||
func (q SystemLogQuery) normalize() SystemLogQuery {
|
||||
if q.Page < 1 {
|
||||
q.Page = 1
|
||||
}
|
||||
if q.PageSize < 1 {
|
||||
q.PageSize = systemLogDefaultPageSize
|
||||
}
|
||||
if q.PageSize > systemLogMaxPageSize {
|
||||
q.PageSize = systemLogMaxPageSize
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// List 按时间倒序分页查询日志,返回当前页与总数。
|
||||
func (s *SystemLogService) List(ctx context.Context, q SystemLogQuery) ([]model.SystemLog, int64, error) {
|
||||
q = q.normalize()
|
||||
tx := s.db.WithContext(ctx).Model(&model.SystemLog{})
|
||||
if q.Keyword != "" {
|
||||
kw := "%" + q.Keyword + "%"
|
||||
tx = tx.Where("path LIKE ? OR username LIKE ?", kw, kw)
|
||||
}
|
||||
var total int64
|
||||
if err := tx.Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("count system logs: %w", err)
|
||||
}
|
||||
items := make([]model.SystemLog, 0, q.PageSize)
|
||||
err := tx.Order("created_at DESC, id DESC").
|
||||
Offset((q.Page - 1) * q.PageSize).Limit(q.PageSize).
|
||||
Find(&items).Error
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("list system logs: %w", err)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// StartCleanup 启动周期清理:启动即清一次,之后每 24h 一次,随 ctx 取消退出。
|
||||
func (s *SystemLogService) StartCleanup(ctx context.Context) {
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
s.cleanupOnce(ctx)
|
||||
ticker := time.NewTicker(systemLogCleanupTick)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
s.cleanupOnce(ctx)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// cleanupOnce 执行一轮清理,失败只记日志、不中断周期调度。
|
||||
func (s *SystemLogService) cleanupOnce(ctx context.Context) {
|
||||
if err := s.cleanup(ctx, systemLogRetention, systemLogMaxRows); err != nil {
|
||||
log.Printf("system log cleanup: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup 先删过期记录,再对超量部分删最旧;阈值参数化便于测试。
|
||||
func (s *SystemLogService) cleanup(ctx context.Context, retention time.Duration, maxRows int) error {
|
||||
cutoff := time.Now().Add(-retention)
|
||||
if err := s.db.WithContext(ctx).Where("created_at < ?", cutoff).Delete(&model.SystemLog{}).Error; err != nil {
|
||||
return fmt.Errorf("delete expired system logs: %w", err)
|
||||
}
|
||||
var total int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.SystemLog{}).Count(&total).Error; err != nil {
|
||||
return fmt.Errorf("count system logs: %w", err)
|
||||
}
|
||||
overflow := int(total) - maxRows
|
||||
if overflow <= 0 {
|
||||
return nil
|
||||
}
|
||||
oldest := s.db.Model(&model.SystemLog{}).Select("id").
|
||||
Order("created_at ASC, id ASC").Limit(overflow)
|
||||
if err := s.db.WithContext(ctx).Where("id IN (?)", oldest).Delete(&model.SystemLog{}).Error; err != nil {
|
||||
return fmt.Errorf("trim system logs over cap: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user