diff --git a/cmd/root.go b/cmd/root.go index 2f49fe8..2923677 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -75,8 +75,14 @@ func setupLogrusForNonHTTP() { // 初始化HTTP日志处理器 logger.InitLogger() - // 记录配置加载完成 - logrus.WithField("file", viper.ConfigFileUsed()).Info("配置文件加载完成") + // 记录配置加载完成,使用相对路径或文件名保持一致性 + configFile := viper.ConfigFileUsed() + if configFile != "" { + fileName := filepath.Base(configFile) + logrus.WithField("file", fileName).Info("配置文件加载完成") + } else { + logrus.Info("配置加载完成(内存默认配置)") + } } // initConfig 读取配置文件和环境变量 diff --git a/config/config.go b/config/config.go index c7f6065..511fc7a 100644 --- a/config/config.go +++ b/config/config.go @@ -170,20 +170,18 @@ func Init(cfgFilePath string) { }, ).Fatal("配置文件解析错误") } - } - - // 只显示配置文件名,不显示完整路径 - configFile := viper.ConfigFileUsed() - if configFile != "" { - // 提取文件名 - fileName := filepath.Base(configFile) - log.WithFields( - log.Fields{ - "file": fileName, - }, - ).Info("使用配置文件") } else { - log.Info("使用默认配置") + // 只显示配置文件名,不显示完整路径 + configFile := viper.ConfigFileUsed() + if configFile != "" { + // 统一使用 filepath.Clean 和 filepath.Base 处理路径展示 + cleanPath := filepath.Clean(configFile) + log.WithFields( + log.Fields{ + "file": cleanPath, + }, + ).Info("使用配置文件") + } } // 验证配置 diff --git a/controllers/admin/app.go b/controllers/admin/app.go index 2010d4f..ca915d4 100644 --- a/controllers/admin/app.go +++ b/controllers/admin/app.go @@ -3,6 +3,7 @@ package admin import ( "NetworkAuth/controllers" "NetworkAuth/models" + "NetworkAuth/services" "crypto/rand" "encoding/base64" "encoding/hex" @@ -385,6 +386,20 @@ func AppCreateHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + services.RecordOperationLog( + "创建应用", + operator, + operatorUUID, + "创建了应用: "+app.Name, + ) + logrus.WithField("app_uuid", app.UUID).Debug("Successfully created app with default APIs") c.JSON(http.StatusOK, gin.H{ @@ -555,6 +570,20 @@ func AppDeleteHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + services.RecordOperationLog( + "删除应用", + operator, + operatorUUID, + "删除了应用: "+app.Name, + ) + logrus.WithFields(logrus.Fields{ "app_id": app.ID, "app_uuid": app.UUID, diff --git a/controllers/admin/handlers.go b/controllers/admin/dashboard.go similarity index 93% rename from controllers/admin/handlers.go rename to controllers/admin/dashboard.go index c09f584..e9bf60d 100644 --- a/controllers/admin/handlers.go +++ b/controllers/admin/dashboard.go @@ -211,9 +211,20 @@ func DashboardLoginLogsHandler(c *gin.Context) { } offset := (page - 1) * limit + // 获取当前管理员信息(可能是 username 或 admin_username,具体取决于认证中间件设置的 key) + username := c.GetString("admin_username") + if username == "" { + // 尝试获取其他可能的键名 + username = c.GetString("username") + } + var total int64 - // 当前模型的 LoginLog 本身就是专用于 admin 的登录日志模型(没有 type 字段),所以直接查询全部即可 - query := db.Model(&models.LoginLog{}) + query := db.Model(&models.LoginLog{}).Where("type = ?", "admin") + + // 如果有用户名,则仅过滤该用户的日志 + if username != "" { + query = query.Where("username = ?", username) + } if err := query.Count(&total).Error; err != nil { handlersBaseController.HandleInternalError(c, "获取登录日志总数失败", err) @@ -232,3 +243,4 @@ func DashboardLoginLogsHandler(c *gin.Context) { } handlersBaseController.HandleSuccess(c, "获取登录日志成功", data) } + diff --git a/controllers/admin/function.go b/controllers/admin/function.go index 8246db8..a5db9f6 100644 --- a/controllers/admin/function.go +++ b/controllers/admin/function.go @@ -3,6 +3,7 @@ package admin import ( "NetworkAuth/controllers" "NetworkAuth/models" + "NetworkAuth/services" "net/http" "regexp" "strconv" @@ -203,6 +204,20 @@ func FunctionCreateHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + services.RecordOperationLog( + "创建函数", + operator, + operatorUUID, + "创建了函数: "+function.Alias, + ) + functionBaseController.HandleSuccess(c, "创建成功", function) } @@ -292,6 +307,10 @@ func FunctionDeleteHandler(c *gin.Context) { return } + // 查找函数以记录日志 + var function models.Function + db.First(&function, req.ID) + // 删除函数 if err := db.Delete(&models.Function{}, req.ID).Error; err != nil { logrus.WithError(err).Error("Failed to delete function") @@ -299,6 +318,25 @@ func FunctionDeleteHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + details := "删除了函数ID: " + strconv.Itoa(int(req.ID)) + if function.ID != 0 { + details = "删除了函数: " + function.Alias + } + + services.RecordOperationLog( + "删除函数", + operator, + operatorUUID, + details, + ) + logrus.WithField("function_id", req.ID).Debug("Successfully deleted function") functionBaseController.HandleSuccess(c, "删除成功", nil) @@ -331,6 +369,26 @@ func FunctionsBatchDeleteHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + var idStrs []string + for _, id := range req.IDs { + idStrs = append(idStrs, strconv.Itoa(int(id))) + } + details := "批量删除了函数ID: " + strings.Join(idStrs, ",") + + services.RecordOperationLog( + "删除函数", + operator, + operatorUUID, + details, + ) + logrus.WithField("function_ids", req.IDs).Debug("Successfully batch deleted functions") functionBaseController.HandleSuccess(c, "批量删除成功", nil) diff --git a/controllers/admin/login_log.go b/controllers/admin/login_log.go index 13d3f71..36b8f60 100644 --- a/controllers/admin/login_log.go +++ b/controllers/admin/login_log.go @@ -162,9 +162,6 @@ func LoginLogsClearHandler(c *gin.Context) { OperationType: "清空登录日志", Operator: operator, OperatorUUID: "", // NetworkAuth 中暂时无法获取 UUID - AppName: "-", - ProductName: "-", - TransactionID: "-", Details: "管理员清空了所有登录日志", CreatedAt: time.Now(), } diff --git a/controllers/admin/operation_log.go b/controllers/admin/operation_log.go index 2a89b3e..9b8917b 100644 --- a/controllers/admin/operation_log.go +++ b/controllers/admin/operation_log.go @@ -51,7 +51,6 @@ func LogsListHandler(c *gin.Context) { endTimeStr := strings.TrimSpace(c.Query("end_time")) operationType := strings.TrimSpace(c.Query("operation_type")) operator := strings.TrimSpace(c.Query("operator")) - transactionID := strings.TrimSpace(c.Query("transaction_id")) // 构建查询 db, ok := logBaseController.GetDB(c) @@ -72,10 +71,6 @@ func LogsListHandler(c *gin.Context) { // 支持按 UUID 或 用户名 筛选 query = query.Where("operator_uuid = ? OR operator = ?", operator, operator) } - if transactionID != "" { - // 优化:使用精确匹配提升查询性能 - query = query.Where("transaction_id = ?", transactionID) - } if startTimeStr != "" { if t, err := time.ParseInLocation("2006-01-02", startTimeStr, time.Local); err == nil { query = query.Where("created_at >= ?", t) @@ -140,9 +135,6 @@ func LogsClearHandler(c *gin.Context) { OperationType: "清空日志", Operator: operator, OperatorUUID: "", - AppName: "-", - ProductName: "-", - TransactionID: "-", Details: "管理员清空了所有操作日志", CreatedAt: time.Now(), } diff --git a/controllers/admin/profile.go b/controllers/admin/profile.go index a308b81..6cdd611 100644 --- a/controllers/admin/profile.go +++ b/controllers/admin/profile.go @@ -144,6 +144,20 @@ func ProfilePasswordUpdateHandler(c *gin.Context) { cookie := utils.CreateSecureCookie("admin_session", token, maxAge, domain, secure, sameSite) c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly) + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + services.RecordOperationLog( + "修改密码", + operator, + operatorUUID, + "管理员修改了登录密码", + ) + authBaseController.HandleSuccess(c, "密码修改成功", nil) } @@ -217,6 +231,24 @@ func ProfileUpdateHandler(c *gin.Context) { return } + // 刷新缓存 + settingsService.RefreshCache() + _ = utils.RedisDel(c.Request.Context(), "setting:admin_username") + + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + services.RecordOperationLog( + "修改账号", + operator, + operatorUUID, + "管理员修改了用户名为: "+username, + ) + // 重新签发JWT并写入Cookie token, err := generateJWTTokenForAdmin(username, currentHash) if err != nil { @@ -228,10 +260,6 @@ func ProfileUpdateHandler(c *gin.Context) { cookie := utils.CreateSecureCookie("admin_session", token, maxAge, domain, secure, sameSite) c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly) - // 刷新缓存 - settingsService.RefreshCache() - _ = utils.RedisDel(c.Request.Context(), "setting:admin_username") - authBaseController.HandleSuccess(c, "用户名修改成功", map[string]interface{}{ "username": username, }) diff --git a/controllers/admin/variable.go b/controllers/admin/variable.go index 983cfd5..f0d99af 100644 --- a/controllers/admin/variable.go +++ b/controllers/admin/variable.go @@ -3,6 +3,7 @@ package admin import ( "NetworkAuth/controllers" "NetworkAuth/models" + "NetworkAuth/services" "net/http" "regexp" "strconv" @@ -223,6 +224,20 @@ func VariableCreateHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + services.RecordOperationLog( + "创建变量", + operator, + operatorUUID, + "创建了变量: "+variable.Alias, + ) + variableBaseController.HandleSuccess(c, "创建成功", variable) } @@ -312,6 +327,10 @@ func VariableDeleteHandler(c *gin.Context) { return } + // 查找变量以记录日志 + var variable models.Variable + db.First(&variable, req.ID) + // 删除变量 if err := db.Delete(&models.Variable{}, req.ID).Error; err != nil { logrus.WithError(err).Error("Failed to delete variable") @@ -319,6 +338,25 @@ func VariableDeleteHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + details := "删除了变量ID: " + strconv.Itoa(int(req.ID)) + if variable.ID != 0 { + details = "删除了变量: " + variable.Alias + } + + services.RecordOperationLog( + "删除变量", + operator, + operatorUUID, + details, + ) + logrus.WithField("variable_id", req.ID).Debug("Successfully deleted variable") variableBaseController.HandleSuccess(c, "删除成功", nil) @@ -351,6 +389,26 @@ func VariablesBatchDeleteHandler(c *gin.Context) { return } + // 记录操作日志 + operator := c.GetString("admin_username") + if operator == "" { + operator = "unknown" + } + operatorUUID := c.GetString("admin_uuid") + + var idStrs []string + for _, id := range req.IDs { + idStrs = append(idStrs, strconv.Itoa(int(id))) + } + details := "批量删除了变量ID: " + strings.Join(idStrs, ",") + + services.RecordOperationLog( + "删除变量", + operator, + operatorUUID, + details, + ) + logrus.WithField("variable_ids", req.IDs).Debug("Successfully batch deleted variables") variableBaseController.HandleSuccess(c, "批量删除成功", nil) diff --git a/database/database.go b/database/database.go index 8989503..4aeb45a 100644 --- a/database/database.go +++ b/database/database.go @@ -2,6 +2,7 @@ package database import ( "NetworkAuth/utils" + "context" "fmt" "log" "os" @@ -23,6 +24,8 @@ import ( var ( // dbInstance 全局 *gorm.DB 实例,使用单例确保全局复用 dbInstance *gorm.DB + // healthCheckCancel 数据库健康检查取消函数 + healthCheckCancel context.CancelFunc // once 确保初始化只执行一次 once sync.Once ) @@ -56,6 +59,10 @@ func GetDB() (*gorm.DB, error) { func ReInit() (*gorm.DB, error) { // 如果已有连接,尝试关闭它 if dbInstance != nil { + if healthCheckCancel != nil { + healthCheckCancel() + healthCheckCancel = nil + } if sqlDB, err := dbInstance.DB(); err == nil { sqlDB.Close() } @@ -124,7 +131,7 @@ func performInit() error { } // 启动健康检查 - utils.StartHealthCheck(dbInstance, dbConfig) + healthCheckCancel = utils.StartHealthCheck(dbInstance, dbConfig) } return initErr } diff --git a/models/operation_log.go b/models/operation_log.go index b8241d3..c6dd46b 100644 --- a/models/operation_log.go +++ b/models/operation_log.go @@ -6,19 +6,16 @@ import ( // OperationLog 操作日志模型 type OperationLog struct { - ID uint `gorm:"primarykey" json:"id"` - CreatedAt time.Time `gorm:"index;comment:创建时间" json:"created_at"` + ID uint `gorm:"primarykey" json:"id"` // 操作信息 - OperationType string `gorm:"type:varchar(50);index;comment:操作方式" json:"operation_type"` // 如:入库成功、凭证分配等 + OperationType string `gorm:"type:varchar(50);index;comment:操作方式" json:"operation_type"` // 操作人信息 Operator string `gorm:"type:varchar(100);index;comment:操作账号" json:"operator"` OperatorUUID string `gorm:"type:varchar(36);index;comment:操作账号UUID" json:"operator_uuid"` - // 关联对象信息 (快照,防止关联对象被删除后无法查询) - TransactionID string `gorm:"type:varchar(100);index;comment:交易ID" json:"transaction_id"` - AppName string `gorm:"type:varchar(100);comment:应用名称" json:"app_name"` - ProductName string `gorm:"type:varchar(100);comment:商品名称" json:"product_name"` - Details string `gorm:"type:text;comment:操作详情" json:"details"` -} \ No newline at end of file + Details string `gorm:"type:text;comment:操作详情" json:"details"` + + CreatedAt time.Time `gorm:"index;comment:创建时间" json:"created_at"` +} diff --git a/services/operation_log.go b/services/operation_log.go index 34f4c02..d5dd9ec 100644 --- a/services/operation_log.go +++ b/services/operation_log.go @@ -9,7 +9,7 @@ import ( ) // RecordOperationLog 记录操作日志 -func RecordOperationLog(operationType, operator, operatorUUID, transactionID, appName, productName, details string) { +func RecordOperationLog(operationType, operator, operatorUUID, details string) { db, err := database.GetDB() if err != nil { logrus.WithError(err).Error("获取数据库连接失败,无法记录操作日志") @@ -20,9 +20,6 @@ func RecordOperationLog(operationType, operator, operatorUUID, transactionID, ap OperationType: operationType, Operator: operator, OperatorUUID: operatorUUID, - TransactionID: transactionID, - AppName: appName, - ProductName: productName, Details: details, CreatedAt: time.Now(), } diff --git a/utils/database.go b/utils/database.go index cc605f8..859e8dc 100644 --- a/utils/database.go +++ b/utils/database.go @@ -301,22 +301,29 @@ func LogConnectionStats(db *gorm.DB) { // StartHealthCheck 启动数据库健康检查 // 启动一个后台goroutine定期检查数据库连接健康状态 // 只在健康检查失败时输出错误日志,正常情况下不输出日志 -func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) { +// 返回一个 CancelFunc 用于停止健康检查 +func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) context.CancelFunc { + ctx, cancel := context.WithCancel(context.Background()) go func() { ticker := time.NewTicker(config.HealthCheckInterval) defer ticker.Stop() - for range ticker.C { - if err := PingDatabase(db, config.PingTimeout); err != nil { - // 只在健康检查失败时输出错误日志 - LogError("数据库健康检查失败", err, map[string]interface{}{ - "ping_timeout": config.PingTimeout, - }) - } + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if err := PingDatabase(db, config.PingTimeout); err != nil { + // 只在健康检查失败时输出错误日志 + LogError("数据库健康检查失败", err, map[string]interface{}{ + "ping_timeout": config.PingTimeout, + }) + } - // 记录连接池统计信息(仅在调试模式下) - if logrus.GetLevel() == logrus.DebugLevel { - LogConnectionStats(db) + // 记录连接池统计信息(仅在调试模式下) + if logrus.GetLevel() == logrus.DebugLevel { + LogConnectionStats(db) + } } } }() @@ -325,6 +332,8 @@ func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) { // "check_interval": config.HealthCheckInterval, // "ping_timeout": config.PingTimeout, // }) + + return cancel } // ValidateDatabaseConfig 验证数据库配置参数 diff --git a/web/template/admin/operation_logs.html b/web/template/admin/operation_logs.html index c060292..7943d9f 100644 --- a/web/template/admin/operation_logs.html +++ b/web/template/admin/operation_logs.html @@ -26,12 +26,6 @@ -