初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+158
View File
@@ -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
}