Private
Public Access
1
0
Files
u-desk/internal/filesystem/audit_log.go

286 lines
6.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package filesystem
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
// AuditOperation 审计操作类型
type AuditOperation string
const (
OperationRead AuditOperation = "read" // 读取文件
OperationWrite AuditOperation = "write" // 写入文件
OperationDelete AuditOperation = "delete" // 删除文件
OperationCreate AuditOperation = "create" // 创建目录
OperationRename AuditOperation = "rename" // 重命名
OperationMove AuditOperation = "move" // 移动
OperationList AuditOperation = "list" // 列出目录
OperationDownload AuditOperation = "download" // 下载
)
// AuditLogEntry 审计日志条目
type AuditLogEntry struct {
Timestamp time.Time `json:"timestamp"` // 操作时间
Operation AuditOperation `json:"operation"` // 操作类型
Path string `json:"path"` // 文件路径
OldPath string `json:"old_path,omitempty"` // 原路径(重命名/移动)
Size int64 `json:"size,omitempty"` // 文件大小
IsDirectory bool `json:"is_directory"` // 是否为目录
Success bool `json:"success"` // 操作是否成功
Error string `json:"error,omitempty"` // 错误信息
UserAgent string `json:"user_agent,omitempty"` // 用户代理
IPAddress string `json:"ip_address,omitempty"` // IP地址
}
// AuditLogger 审计日志记录器
type AuditLogger struct {
logFile *os.File
logPath string
mu sync.Mutex
buffer []AuditLogEntry
bufferSize int
stopChan chan struct{}
}
// NewAuditLogger 创建审计日志记录器
func NewAuditLogger(logDir string) (*AuditLogger, error) {
// 创建日志目录
if err := os.MkdirAll(logDir, 0755); err != nil {
return nil, fmt.Errorf("创建日志目录失败: %v", err)
}
// 生成日志文件名(按日期)
timestamp := time.Now().Format("2006-01-02")
logPath := filepath.Join(logDir, fmt.Sprintf("audit_%s.log", timestamp))
// 打开日志文件(追加模式)
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, fmt.Errorf("打开日志文件失败: %v", err)
}
logger := &AuditLogger{
logFile: logFile,
logPath: logPath,
buffer: make([]AuditLogEntry, 0, 100),
bufferSize: 100, // 缓冲100条记录后批量写入
stopChan: make(chan struct{}),
}
// 启动后台协程,定期刷新缓冲区
go logger.backgroundFlush()
return logger, nil
}
// Log 记录操作日志
func (a *AuditLogger) Log(entry AuditLogEntry) error {
// 设置时间戳
if entry.Timestamp.IsZero() {
entry.Timestamp = time.Now()
}
a.mu.Lock()
defer a.mu.Unlock()
// 添加到缓冲区
a.buffer = append(a.buffer, entry)
// 如果缓冲区满了,立即写入
if len(a.buffer) >= a.bufferSize {
if err := a.flush(); err != nil {
return err
}
}
return nil
}
// LogDelete 记录删除操作(便捷方法)
func (a *AuditLogger) LogDelete(path string, isDir bool, size int64, err error) {
entry := AuditLogEntry{
Timestamp: time.Now(),
Operation: OperationDelete,
Path: path,
Size: size,
IsDirectory: isDir,
Success: err == nil,
}
if err != nil {
entry.Error = err.Error()
}
_ = a.Log(entry)
}
// LogWrite 记录写入操作(便捷方法)
func (a *AuditLogger) LogWrite(path string, size int64, err error) {
entry := AuditLogEntry{
Timestamp: time.Now(),
Operation: OperationWrite,
Path: path,
Size: size,
IsDirectory: false,
Success: err == nil,
}
if err != nil {
entry.Error = err.Error()
}
_ = a.Log(entry)
}
// LogRead 记录读取操作(便捷方法)
func (a *AuditLogger) LogRead(path string, size int64, err error) {
entry := AuditLogEntry{
Timestamp: time.Now(),
Operation: OperationRead,
Path: path,
Size: size,
IsDirectory: false,
Success: err == nil,
}
if err != nil {
entry.Error = err.Error()
}
_ = a.Log(entry)
}
// flush 将缓冲区写入文件
func (a *AuditLogger) flush() error {
if len(a.buffer) == 0 {
return nil
}
// 序列化所有条目为JSON每行一个
for _, entry := range a.buffer {
data, err := json.Marshal(entry)
if err != nil {
continue // 序列化失败,跳过该条目
}
if _, err := a.logFile.Write(append(data, '\n')); err != nil {
return err
}
}
// 刷新到磁盘
if err := a.logFile.Sync(); err != nil {
return err
}
// 清空缓冲区
a.buffer = a.buffer[:0]
return nil
}
// backgroundFlush 后台协程,定期刷新缓冲区
func (a *AuditLogger) backgroundFlush() {
ticker := time.NewTicker(5 * time.Second) // 每5秒刷新一次
defer ticker.Stop()
for {
select {
case <-ticker.C:
a.mu.Lock()
_ = a.flush()
a.mu.Unlock()
case <-a.stopChan:
// 停止前刷新一次
a.mu.Lock()
_ = a.flush()
a.mu.Unlock()
return
}
}
}
// Close 关闭审计日志记录器
func (a *AuditLogger) Close() error {
close(a.stopChan)
a.mu.Lock()
defer a.mu.Unlock()
// 刷新剩余缓冲区
if err := a.flush(); err != nil {
return err
}
// 关闭文件
return a.logFile.Close()
}
// GetRecentLogs 获取最近的审计日志
func GetRecentLogs(logDir string, limit int) ([]AuditLogEntry, error) {
// 读取今天的日志文件
timestamp := time.Now().Format("2006-01-02")
logPath := filepath.Join(logDir, fmt.Sprintf("audit_%s.log", timestamp))
data, err := os.ReadFile(logPath)
if err != nil {
return nil, err
}
// 解析JSON每行一个条目
var entries []AuditLogEntry
lines := parseLines(string(data))
// 从后往前读取(最新的在前)
start := len(lines) - limit
if start < 0 {
start = 0
}
for i := len(lines) - 1; i >= start; i-- {
var entry AuditLogEntry
if err := json.Unmarshal([]byte(lines[i]), &entry); err == nil {
entries = append(entries, entry)
}
}
return entries, nil
}
// parseLines 解析文本为行
func parseLines(text string) []string {
lines := make([]string, 0)
current := ""
for _, ch := range text {
if ch == '\n' {
if current != "" {
lines = append(lines, current)
current = ""
}
} else {
current += string(ch)
}
}
if current != "" {
lines = append(lines, current)
}
return lines
}
// 全局审计日志记录器
var globalAuditLogger *AuditLogger
var auditLoggerOnce sync.Once
// GetAuditLogger 获取全局审计日志记录器
func GetAuditLogger() *AuditLogger {
return globalAuditLogger
}