mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
Add classification annotations
This commit is contained in:
@@ -7,6 +7,10 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Cookie创建函数
|
||||
// ============================================================================
|
||||
|
||||
// CreateSecureCookie 创建安全的Cookie
|
||||
// name: Cookie名称
|
||||
// value: Cookie值
|
||||
@@ -67,6 +71,10 @@ func CreateExpiredCookie(name string) *http.Cookie {
|
||||
return CreateSecureCookie(name, "", -1)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 配置函数
|
||||
// ============================================================================
|
||||
|
||||
// GetDefaultCookieMaxAge 获取默认Cookie过期时间
|
||||
func GetDefaultCookieMaxAge() int {
|
||||
maxAge := viper.GetInt("security.cookie.max_age")
|
||||
@@ -74,4 +82,4 @@ func GetDefaultCookieMaxAge() int {
|
||||
return 86400 // 默认24小时
|
||||
}
|
||||
return maxAge
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 结构体定义
|
||||
// ============================================================================
|
||||
|
||||
// CryptoManager 加密管理器,提供高性能的加密解密服务
|
||||
type CryptoManager struct {
|
||||
key []byte
|
||||
@@ -23,9 +27,17 @@ type CryptoManager struct {
|
||||
inited bool
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 全局变量
|
||||
// ============================================================================
|
||||
|
||||
// 全局加密管理器实例
|
||||
var cryptoManager = &CryptoManager{}
|
||||
|
||||
// ============================================================================
|
||||
// 私有函数
|
||||
// ============================================================================
|
||||
|
||||
// initCrypto 初始化加密管理器
|
||||
// 缓存密钥和GCM实例,避免重复创建
|
||||
func (cm *CryptoManager) initCrypto() error {
|
||||
@@ -63,6 +75,10 @@ func (cm *CryptoManager) initCrypto() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 加密解密函数
|
||||
// ============================================================================
|
||||
|
||||
// EncryptString 字符串加密(AES-256-GCM)
|
||||
// 使用缓存的密钥和GCM实例,提高性能
|
||||
func EncryptString(plain string) (string, error) {
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 常量定义
|
||||
// ============================================================================
|
||||
|
||||
const (
|
||||
CSRFTokenLength = 32
|
||||
CSRFCookieName = "csrf_token"
|
||||
@@ -16,6 +20,10 @@ const (
|
||||
CSRFFormField = "csrf_token"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 私有函数
|
||||
// ============================================================================
|
||||
|
||||
// generateRandomBytes 生成指定长度的随机字节
|
||||
func generateRandomBytes(length int) ([]byte, error) {
|
||||
bytes := make([]byte, length)
|
||||
@@ -26,6 +34,10 @@ func generateRandomBytes(length int) ([]byte, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 公共函数
|
||||
// ============================================================================
|
||||
|
||||
// GenerateCSRFToken 生成CSRF令牌
|
||||
func GenerateCSRFToken() (string, error) {
|
||||
bytes, err := generateRandomBytes(CSRFTokenLength)
|
||||
@@ -190,4 +202,4 @@ func CSRFTokenHandler(c *gin.Context) {
|
||||
"csrf_token": token,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,327 +1,343 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DatabaseConfig 数据库连接池配置结构体
|
||||
// 用于配置数据库连接池的各项参数,包括连接池大小、生命周期管理和健康检查等
|
||||
type DatabaseConfig struct {
|
||||
// 连接池配置
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"` // 最大空闲连接数
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"` // 最大打开连接数
|
||||
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` // 连接最大生存时间
|
||||
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间
|
||||
|
||||
// 健康检查配置
|
||||
PingTimeout time.Duration `mapstructure:"ping_timeout"` // Ping超时时间
|
||||
HealthCheckInterval time.Duration `mapstructure:"health_check_interval"` // 健康检查间隔
|
||||
}
|
||||
|
||||
// GetDefaultDatabaseConfig 获取默认数据库配置
|
||||
// 返回一个包含合理默认值的数据库配置实例
|
||||
func GetDefaultDatabaseConfig() *DatabaseConfig {
|
||||
return &DatabaseConfig{
|
||||
MaxIdleConns: 10, // 默认最大空闲连接数
|
||||
MaxOpenConns: 100, // 默认最大打开连接数
|
||||
ConnMaxLifetime: 30 * time.Minute, // 连接最大生存时间30分钟
|
||||
ConnMaxIdleTime: 10 * time.Minute, // 连接最大空闲时间10分钟
|
||||
PingTimeout: 5 * time.Second, // Ping超时5秒
|
||||
HealthCheckInterval: 30 * time.Second, // 健康检查间隔30秒
|
||||
}
|
||||
}
|
||||
|
||||
// LoadDatabaseConfig 从配置文件加载数据库配置
|
||||
// 使用指定的前缀从viper配置中读取数据库配置,如果配置项不存在则使用默认值
|
||||
func LoadDatabaseConfig(prefix string) *DatabaseConfig {
|
||||
config := GetDefaultDatabaseConfig()
|
||||
|
||||
// 从viper读取配置,如果不存在则使用默认值
|
||||
if viper.IsSet(prefix + ".max_idle_conns") {
|
||||
config.MaxIdleConns = viper.GetInt(prefix + ".max_idle_conns")
|
||||
}
|
||||
if viper.IsSet(prefix + ".max_open_conns") {
|
||||
config.MaxOpenConns = viper.GetInt(prefix + ".max_open_conns")
|
||||
}
|
||||
if viper.IsSet(prefix + ".conn_max_lifetime") {
|
||||
config.ConnMaxLifetime = viper.GetDuration(prefix + ".conn_max_lifetime")
|
||||
}
|
||||
if viper.IsSet(prefix + ".conn_max_idle_time") {
|
||||
config.ConnMaxIdleTime = viper.GetDuration(prefix + ".conn_max_idle_time")
|
||||
}
|
||||
if viper.IsSet(prefix + ".ping_timeout") {
|
||||
config.PingTimeout = viper.GetDuration(prefix + ".ping_timeout")
|
||||
}
|
||||
if viper.IsSet(prefix + ".health_check_interval") {
|
||||
config.HealthCheckInterval = viper.GetDuration(prefix + ".health_check_interval")
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// ConfigureConnectionPool 配置数据库连接池
|
||||
// 根据提供的配置参数设置GORM数据库的连接池属性
|
||||
func ConfigureConnectionPool(db *gorm.DB, config *DatabaseConfig) error {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取底层数据库连接失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置连接池参数
|
||||
sqlDB.SetMaxIdleConns(config.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(config.MaxOpenConns)
|
||||
sqlDB.SetConnMaxLifetime(config.ConnMaxLifetime)
|
||||
sqlDB.SetConnMaxIdleTime(config.ConnMaxIdleTime)
|
||||
|
||||
// LogInfo("数据库连接池配置完成", map[string]interface{}{
|
||||
// "max_idle_conns": config.MaxIdleConns,
|
||||
// "max_open_conns": config.MaxOpenConns,
|
||||
// "conn_max_lifetime": config.ConnMaxLifetime,
|
||||
// "conn_max_idle_time": config.ConnMaxIdleTime,
|
||||
// })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PingDatabase 检查数据库连接健康状态
|
||||
// 使用指定的超时时间ping数据库以验证连接是否正常
|
||||
func PingDatabase(db *gorm.DB, timeout time.Duration) error {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取底层数据库连接失败: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
return sqlDB.PingContext(ctx)
|
||||
}
|
||||
|
||||
// GetConnectionStats 获取数据库连接池统计信息
|
||||
// 返回当前数据库连接池的详细统计数据,包括连接数、等待时间等
|
||||
func GetConnectionStats(db *gorm.DB) (*sql.DBStats, error) {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取底层数据库连接失败: %w", err)
|
||||
}
|
||||
|
||||
stats := sqlDB.Stats()
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// LogConnectionStats 记录数据库连接池统计信息
|
||||
// 获取并记录数据库连接池的统计信息到日志中,用于监控和调试
|
||||
func LogConnectionStats(db *gorm.DB) {
|
||||
stats, err := GetConnectionStats(db)
|
||||
if err != nil {
|
||||
LogError("获取数据库连接池统计信息失败", err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
LogInfo("数据库连接池统计", map[string]interface{}{
|
||||
"open_connections": stats.OpenConnections,
|
||||
"in_use": stats.InUse,
|
||||
"idle": stats.Idle,
|
||||
"wait_count": stats.WaitCount,
|
||||
"wait_duration": stats.WaitDuration,
|
||||
"max_idle_closed": stats.MaxIdleClosed,
|
||||
"max_idle_time_closed": stats.MaxIdleTimeClosed,
|
||||
"max_lifetime_closed": stats.MaxLifetimeClosed,
|
||||
})
|
||||
}
|
||||
|
||||
// StartHealthCheck 启动数据库健康检查
|
||||
// 启动一个后台goroutine定期检查数据库连接健康状态
|
||||
// 只在健康检查失败时输出错误日志,正常情况下不输出日志
|
||||
func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// 记录连接池统计信息(仅在调试模式下)
|
||||
if logrus.GetLevel() == logrus.DebugLevel {
|
||||
LogConnectionStats(db)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// LogInfo("数据库健康检查已启动", map[string]interface{}{
|
||||
// "check_interval": config.HealthCheckInterval,
|
||||
// "ping_timeout": config.PingTimeout,
|
||||
// })
|
||||
}
|
||||
|
||||
// ValidateDatabaseConfig 验证数据库配置参数
|
||||
// 检查数据库配置参数的有效性,确保所有参数都在合理范围内
|
||||
func ValidateDatabaseConfig(config *DatabaseConfig) error {
|
||||
if config.MaxIdleConns < 0 {
|
||||
return fmt.Errorf("最大空闲连接数不能为负数: %d", config.MaxIdleConns)
|
||||
}
|
||||
if config.MaxOpenConns < 0 {
|
||||
return fmt.Errorf("最大打开连接数不能为负数: %d", config.MaxOpenConns)
|
||||
}
|
||||
if config.MaxIdleConns > config.MaxOpenConns && config.MaxOpenConns > 0 {
|
||||
return fmt.Errorf("最大空闲连接数(%d)不能大于最大打开连接数(%d)", config.MaxIdleConns, config.MaxOpenConns)
|
||||
}
|
||||
if config.ConnMaxLifetime < 0 {
|
||||
return fmt.Errorf("连接最大生存时间不能为负数: %v", config.ConnMaxLifetime)
|
||||
}
|
||||
if config.ConnMaxIdleTime < 0 {
|
||||
return fmt.Errorf("连接最大空闲时间不能为负数: %v", config.ConnMaxIdleTime)
|
||||
}
|
||||
if config.PingTimeout <= 0 {
|
||||
return fmt.Errorf("Ping超时时间必须大于0: %v", config.PingTimeout)
|
||||
}
|
||||
if config.HealthCheckInterval <= 0 {
|
||||
return fmt.Errorf("健康检查间隔必须大于0: %v", config.HealthCheckInterval)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
// redisClient 全局Redis客户端
|
||||
redisClient *redis.Client
|
||||
// redisOnce 确保只初始化一次
|
||||
redisOnce sync.Once
|
||||
// redisAvailable 标记Redis是否可用
|
||||
redisAvailable bool
|
||||
)
|
||||
|
||||
// InitRedis 初始化Redis客户端(仅在配置存在时尝试连接)
|
||||
// - 从 viper 读取 security.redis.* 配置
|
||||
// - 如果连接失败,则标记为不可用,不影响主流程
|
||||
func InitRedis() {
|
||||
redisOnce.Do(func() {
|
||||
host := viper.GetString("redis.host")
|
||||
port := viper.GetInt("redis.port")
|
||||
if host == "" || port == 0 {
|
||||
logrus.Info("未配置Redis或配置不完整,跳过初始化")
|
||||
redisAvailable = false
|
||||
return
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: viper.GetString("redis.password"),
|
||||
DB: viper.GetInt("redis.db"),
|
||||
})
|
||||
// 健康检查
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if err := redisClient.Ping(ctx).Err(); err != nil {
|
||||
logrus.WithError(err).Warn("Redis初始化失败,标记为不可用")
|
||||
redisAvailable = false
|
||||
return
|
||||
}
|
||||
redisAvailable = true
|
||||
logrus.WithField("addr", addr).Info("Redis 连接已建立")
|
||||
})
|
||||
}
|
||||
|
||||
// GetRedis 获取全局Redis客户端,可能返回nil(当不可用时)
|
||||
func GetRedis() *redis.Client {
|
||||
if redisClient == nil {
|
||||
InitRedis()
|
||||
}
|
||||
if !redisAvailable {
|
||||
return nil
|
||||
}
|
||||
return redisClient
|
||||
}
|
||||
|
||||
// IsRedisAvailable 判断Redis是否可用
|
||||
func IsRedisAvailable() bool {
|
||||
if redisClient == nil {
|
||||
InitRedis()
|
||||
}
|
||||
return redisAvailable
|
||||
}
|
||||
|
||||
// RedisGetOrSet 通用Redis缓存获取或设置函数(基于JSON序列化)
|
||||
// - ctx: 上下文
|
||||
// - key: 缓存键
|
||||
// - ttl: 过期时间
|
||||
// - loader: 当缓存不存在时的加载函数(一般执行数据库查询)
|
||||
// 返回:目标对象指针和错误
|
||||
func RedisGetOrSet[T any](ctx context.Context, key string, ttl time.Duration, loader func() (*T, error)) (*T, error) {
|
||||
// 如果Redis不可用则直接调用加载函数
|
||||
if !IsRedisAvailable() {
|
||||
return loader()
|
||||
}
|
||||
client := GetRedis()
|
||||
if client == nil {
|
||||
return loader()
|
||||
}
|
||||
|
||||
// 先尝试从缓存读取
|
||||
data, err := client.Get(ctx, key).Bytes()
|
||||
if err == nil {
|
||||
var out T
|
||||
if uerr := json.Unmarshal(data, &out); uerr == nil {
|
||||
return &out, nil
|
||||
}
|
||||
// 反序列化失败时视为未命中,继续加载
|
||||
logrus.WithError(err).WithField("key", key).Warn("Redis缓存反序列化失败,回退到loader")
|
||||
} else if err != redis.Nil {
|
||||
// 非空且非不存在的错误,记录告警但不中断
|
||||
logrus.WithError(err).WithField("key", key).Warn("读取Redis缓存失败")
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
val, lerr := loader()
|
||||
if lerr != nil {
|
||||
return nil, lerr
|
||||
}
|
||||
if val == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 写回缓存(错误不影响主流程)
|
||||
if b, merr := json.Marshal(val); merr == nil {
|
||||
if serr := client.Set(ctx, key, b, ttl).Err(); serr != nil {
|
||||
logrus.WithError(serr).WithField("key", key).Warn("写入Redis缓存失败")
|
||||
}
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// RedisDel 删除一个或多个Redis键(当Redis不可用时静默返回)
|
||||
// - ctx: 上下文
|
||||
// - keys: 需要删除的键名
|
||||
func RedisDel(ctx context.Context, keys ...string) error {
|
||||
// 如果Redis不可用则直接返回
|
||||
if !IsRedisAvailable() {
|
||||
return nil
|
||||
}
|
||||
client := GetRedis()
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err := client.Del(ctx, keys...).Result(); err != nil {
|
||||
logrus.WithError(err).WithField("keys", keys).Warn("删除Redis键失败")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 结构体定义
|
||||
// ============================================================================
|
||||
|
||||
// DatabaseConfig 数据库连接池配置结构体
|
||||
// 用于配置数据库连接池的各项参数,包括连接池大小、生命周期管理和健康检查等
|
||||
type DatabaseConfig struct {
|
||||
// 连接池配置
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"` // 最大空闲连接数
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"` // 最大打开连接数
|
||||
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` // 连接最大生存时间
|
||||
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间
|
||||
|
||||
// 健康检查配置
|
||||
PingTimeout time.Duration `mapstructure:"ping_timeout"` // Ping超时时间
|
||||
HealthCheckInterval time.Duration `mapstructure:"health_check_interval"` // 健康检查间隔
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 配置函数
|
||||
// ============================================================================
|
||||
|
||||
// GetDefaultDatabaseConfig 获取默认数据库配置
|
||||
// 返回一个包含合理默认值的数据库配置实例
|
||||
func GetDefaultDatabaseConfig() *DatabaseConfig {
|
||||
return &DatabaseConfig{
|
||||
MaxIdleConns: 10, // 默认最大空闲连接数
|
||||
MaxOpenConns: 100, // 默认最大打开连接数
|
||||
ConnMaxLifetime: 30 * time.Minute, // 连接最大生存时间30分钟
|
||||
ConnMaxIdleTime: 10 * time.Minute, // 连接最大空闲时间10分钟
|
||||
PingTimeout: 5 * time.Second, // Ping超时5秒
|
||||
HealthCheckInterval: 30 * time.Second, // 健康检查间隔30秒
|
||||
}
|
||||
}
|
||||
|
||||
// LoadDatabaseConfig 从配置文件加载数据库配置
|
||||
// 使用指定的前缀从viper配置中读取数据库配置,如果配置项不存在则使用默认值
|
||||
func LoadDatabaseConfig(prefix string) *DatabaseConfig {
|
||||
config := GetDefaultDatabaseConfig()
|
||||
|
||||
// 从viper读取配置,如果不存在则使用默认值
|
||||
if viper.IsSet(prefix + ".max_idle_conns") {
|
||||
config.MaxIdleConns = viper.GetInt(prefix + ".max_idle_conns")
|
||||
}
|
||||
if viper.IsSet(prefix + ".max_open_conns") {
|
||||
config.MaxOpenConns = viper.GetInt(prefix + ".max_open_conns")
|
||||
}
|
||||
if viper.IsSet(prefix + ".conn_max_lifetime") {
|
||||
config.ConnMaxLifetime = viper.GetDuration(prefix + ".conn_max_lifetime")
|
||||
}
|
||||
if viper.IsSet(prefix + ".conn_max_idle_time") {
|
||||
config.ConnMaxIdleTime = viper.GetDuration(prefix + ".conn_max_idle_time")
|
||||
}
|
||||
if viper.IsSet(prefix + ".ping_timeout") {
|
||||
config.PingTimeout = viper.GetDuration(prefix + ".ping_timeout")
|
||||
}
|
||||
if viper.IsSet(prefix + ".health_check_interval") {
|
||||
config.HealthCheckInterval = viper.GetDuration(prefix + ".health_check_interval")
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// ConfigureConnectionPool 配置数据库连接池
|
||||
// 根据提供的配置参数设置GORM数据库的连接池属性
|
||||
func ConfigureConnectionPool(db *gorm.DB, config *DatabaseConfig) error {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取底层数据库连接失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置连接池参数
|
||||
sqlDB.SetMaxIdleConns(config.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(config.MaxOpenConns)
|
||||
sqlDB.SetConnMaxLifetime(config.ConnMaxLifetime)
|
||||
sqlDB.SetConnMaxIdleTime(config.ConnMaxIdleTime)
|
||||
|
||||
// LogInfo("数据库连接池配置完成", map[string]interface{}{
|
||||
// "max_idle_conns": config.MaxIdleConns,
|
||||
// "max_open_conns": config.MaxOpenConns,
|
||||
// "conn_max_lifetime": config.ConnMaxLifetime,
|
||||
// "conn_max_idle_time": config.ConnMaxIdleTime,
|
||||
// })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PingDatabase 检查数据库连接健康状态
|
||||
// 使用指定的超时时间ping数据库以验证连接是否正常
|
||||
func PingDatabase(db *gorm.DB, timeout time.Duration) error {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取底层数据库连接失败: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
return sqlDB.PingContext(ctx)
|
||||
}
|
||||
|
||||
// GetConnectionStats 获取数据库连接池统计信息
|
||||
// 返回当前数据库连接池的详细统计数据,包括连接数、等待时间等
|
||||
func GetConnectionStats(db *gorm.DB) (*sql.DBStats, error) {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取底层数据库连接失败: %w", err)
|
||||
}
|
||||
|
||||
stats := sqlDB.Stats()
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// LogConnectionStats 记录数据库连接池统计信息
|
||||
// 获取并记录数据库连接池的统计信息到日志中,用于监控和调试
|
||||
func LogConnectionStats(db *gorm.DB) {
|
||||
stats, err := GetConnectionStats(db)
|
||||
if err != nil {
|
||||
LogError("获取数据库连接池统计信息失败", err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
LogInfo("数据库连接池统计", map[string]interface{}{
|
||||
"open_connections": stats.OpenConnections,
|
||||
"in_use": stats.InUse,
|
||||
"idle": stats.Idle,
|
||||
"wait_count": stats.WaitCount,
|
||||
"wait_duration": stats.WaitDuration,
|
||||
"max_idle_closed": stats.MaxIdleClosed,
|
||||
"max_idle_time_closed": stats.MaxIdleTimeClosed,
|
||||
"max_lifetime_closed": stats.MaxLifetimeClosed,
|
||||
})
|
||||
}
|
||||
|
||||
// StartHealthCheck 启动数据库健康检查
|
||||
// 启动一个后台goroutine定期检查数据库连接健康状态
|
||||
// 只在健康检查失败时输出错误日志,正常情况下不输出日志
|
||||
func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// 记录连接池统计信息(仅在调试模式下)
|
||||
if logrus.GetLevel() == logrus.DebugLevel {
|
||||
LogConnectionStats(db)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// LogInfo("数据库健康检查已启动", map[string]interface{}{
|
||||
// "check_interval": config.HealthCheckInterval,
|
||||
// "ping_timeout": config.PingTimeout,
|
||||
// })
|
||||
}
|
||||
|
||||
// ValidateDatabaseConfig 验证数据库配置参数
|
||||
// 检查数据库配置参数的有效性,确保所有参数都在合理范围内
|
||||
func ValidateDatabaseConfig(config *DatabaseConfig) error {
|
||||
if config.MaxIdleConns < 0 {
|
||||
return fmt.Errorf("最大空闲连接数不能为负数: %d", config.MaxIdleConns)
|
||||
}
|
||||
if config.MaxOpenConns < 0 {
|
||||
return fmt.Errorf("最大打开连接数不能为负数: %d", config.MaxOpenConns)
|
||||
}
|
||||
if config.MaxIdleConns > config.MaxOpenConns && config.MaxOpenConns > 0 {
|
||||
return fmt.Errorf("最大空闲连接数(%d)不能大于最大打开连接数(%d)", config.MaxIdleConns, config.MaxOpenConns)
|
||||
}
|
||||
if config.ConnMaxLifetime < 0 {
|
||||
return fmt.Errorf("连接最大生存时间不能为负数: %v", config.ConnMaxLifetime)
|
||||
}
|
||||
if config.ConnMaxIdleTime < 0 {
|
||||
return fmt.Errorf("连接最大空闲时间不能为负数: %v", config.ConnMaxIdleTime)
|
||||
}
|
||||
if config.PingTimeout <= 0 {
|
||||
return fmt.Errorf("Ping超时时间必须大于0: %v", config.PingTimeout)
|
||||
}
|
||||
if config.HealthCheckInterval <= 0 {
|
||||
return fmt.Errorf("健康检查间隔必须大于0: %v", config.HealthCheckInterval)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 全局变量
|
||||
// ============================================================================
|
||||
|
||||
var (
|
||||
// redisClient 全局Redis客户端
|
||||
redisClient *redis.Client
|
||||
// redisOnce 确保只初始化一次
|
||||
redisOnce sync.Once
|
||||
// redisAvailable 标记Redis是否可用
|
||||
redisAvailable bool
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Redis函数
|
||||
// ============================================================================
|
||||
|
||||
// InitRedis 初始化Redis客户端(仅在配置存在时尝试连接)
|
||||
// - 从 viper 读取 security.redis.* 配置
|
||||
// - 如果连接失败,则标记为不可用,不影响主流程
|
||||
func InitRedis() {
|
||||
redisOnce.Do(func() {
|
||||
host := viper.GetString("redis.host")
|
||||
port := viper.GetInt("redis.port")
|
||||
if host == "" || port == 0 {
|
||||
logrus.Info("未配置Redis或配置不完整,跳过初始化")
|
||||
redisAvailable = false
|
||||
return
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: viper.GetString("redis.password"),
|
||||
DB: viper.GetInt("redis.db"),
|
||||
})
|
||||
// 健康检查
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if err := redisClient.Ping(ctx).Err(); err != nil {
|
||||
logrus.WithError(err).Warn("Redis初始化失败,标记为不可用")
|
||||
redisAvailable = false
|
||||
return
|
||||
}
|
||||
redisAvailable = true
|
||||
logrus.WithField("addr", addr).Info("Redis 连接已建立")
|
||||
})
|
||||
}
|
||||
|
||||
// GetRedis 获取全局Redis客户端,可能返回nil(当不可用时)
|
||||
func GetRedis() *redis.Client {
|
||||
if redisClient == nil {
|
||||
InitRedis()
|
||||
}
|
||||
if !redisAvailable {
|
||||
return nil
|
||||
}
|
||||
return redisClient
|
||||
}
|
||||
|
||||
// IsRedisAvailable 判断Redis是否可用
|
||||
func IsRedisAvailable() bool {
|
||||
if redisClient == nil {
|
||||
InitRedis()
|
||||
}
|
||||
return redisAvailable
|
||||
}
|
||||
|
||||
// RedisGetOrSet 通用Redis缓存获取或设置函数(基于JSON序列化)
|
||||
// - ctx: 上下文
|
||||
// - key: 缓存键
|
||||
// - ttl: 过期时间
|
||||
// - loader: 当缓存不存在时的加载函数(一般执行数据库查询)
|
||||
// 返回:目标对象指针和错误
|
||||
func RedisGetOrSet[T any](ctx context.Context, key string, ttl time.Duration, loader func() (*T, error)) (*T, error) {
|
||||
// 如果Redis不可用则直接调用加载函数
|
||||
if !IsRedisAvailable() {
|
||||
return loader()
|
||||
}
|
||||
client := GetRedis()
|
||||
if client == nil {
|
||||
return loader()
|
||||
}
|
||||
|
||||
// 先尝试从缓存读取
|
||||
data, err := client.Get(ctx, key).Bytes()
|
||||
if err == nil {
|
||||
var out T
|
||||
if uerr := json.Unmarshal(data, &out); uerr == nil {
|
||||
return &out, nil
|
||||
}
|
||||
// 反序列化失败时视为未命中,继续加载
|
||||
logrus.WithError(err).WithField("key", key).Warn("Redis缓存反序列化失败,回退到loader")
|
||||
} else if err != redis.Nil {
|
||||
// 非空且非不存在的错误,记录告警但不中断
|
||||
logrus.WithError(err).WithField("key", key).Warn("读取Redis缓存失败")
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
val, lerr := loader()
|
||||
if lerr != nil {
|
||||
return nil, lerr
|
||||
}
|
||||
if val == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 写回缓存(错误不影响主流程)
|
||||
if b, merr := json.Marshal(val); merr == nil {
|
||||
if serr := client.Set(ctx, key, b, ttl).Err(); serr != nil {
|
||||
logrus.WithError(serr).WithField("key", key).Warn("写入Redis缓存失败")
|
||||
}
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// RedisDel 删除一个或多个Redis键(当Redis不可用时静默返回)
|
||||
// - ctx: 上下文
|
||||
// - keys: 需要删除的键名
|
||||
func RedisDel(ctx context.Context, keys ...string) error {
|
||||
// 如果Redis不可用则直接返回
|
||||
if !IsRedisAvailable() {
|
||||
return nil
|
||||
}
|
||||
client := GetRedis()
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err := client.Del(ctx, keys...).Result(); err != nil {
|
||||
logrus.WithError(err).WithField("keys", keys).Warn("删除Redis键失败")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,12 +8,20 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 结构体定义
|
||||
// ============================================================================
|
||||
|
||||
// EasyEncrypt 易加密算法结构体
|
||||
type EasyEncrypt struct {
|
||||
encryptKey []int // 加密密钥
|
||||
decryptKey []int // 解密密钥
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
// ============================================================================
|
||||
|
||||
// NewEasyEncrypt 创建新的易加密实例
|
||||
func NewEasyEncrypt(encryptKey, decryptKey []int) *EasyEncrypt {
|
||||
return &EasyEncrypt{
|
||||
@@ -22,17 +30,21 @@ func NewEasyEncrypt(encryptKey, decryptKey []int) *EasyEncrypt {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 密钥生成函数
|
||||
// ============================================================================
|
||||
|
||||
// GenerateEasyKey 生成易加密密钥对
|
||||
func GenerateEasyKey() ([]int, []int, error) {
|
||||
// 使用crypto/rand生成随机长度(15-30位)
|
||||
var lengthByte [1]byte
|
||||
|
||||
|
||||
// 生成加密密钥长度
|
||||
if _, err := rand.Read(lengthByte[:]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
encryptKeyLen := 15 + int(lengthByte[0])%16 // 15-30位随机长度
|
||||
|
||||
|
||||
encryptKey := make([]int, encryptKeyLen)
|
||||
encryptBytes := make([]byte, encryptKeyLen)
|
||||
if _, err := rand.Read(encryptBytes); err != nil {
|
||||
@@ -47,7 +59,7 @@ func GenerateEasyKey() ([]int, []int, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
decryptKeyLen := 15 + int(lengthByte[0])%16 // 15-30位随机长度
|
||||
|
||||
|
||||
decryptKey := make([]int, decryptKeyLen)
|
||||
decryptBytes := make([]byte, decryptKeyLen)
|
||||
if _, err := rand.Read(decryptBytes); err != nil {
|
||||
@@ -60,6 +72,10 @@ func GenerateEasyKey() ([]int, []int, error) {
|
||||
return encryptKey, decryptKey, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 方法函数
|
||||
// ============================================================================
|
||||
|
||||
// Encrypt 加密函数 - 对应 UserLogin_encrypt_Up_42510
|
||||
func (e *EasyEncrypt) Encrypt(input string) string {
|
||||
if input == "" {
|
||||
@@ -140,6 +156,10 @@ func (e *EasyEncrypt) Decrypt(input string) string {
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 工具函数
|
||||
// ============================================================================
|
||||
|
||||
// EncryptWithKey 使用指定密钥加密
|
||||
func EncryptWithKey(input string, key []int) string {
|
||||
if input == "" || len(key) == 0 {
|
||||
|
||||
@@ -11,14 +11,18 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 结构体定义
|
||||
// ============================================================================
|
||||
|
||||
// ErrorResponse 统一的错误响应结构
|
||||
// 用于标准化API错误响应格式
|
||||
type ErrorResponse struct {
|
||||
Success bool `json:"success"` // 请求是否成功,错误响应时固定为false
|
||||
Message string `json:"message"` // 错误消息描述
|
||||
Success bool `json:"success"` // 请求是否成功,错误响应时固定为false
|
||||
Message string `json:"message"` // 错误消息描述
|
||||
ErrorCode string `json:"error_code,omitempty"` // 错误代码,用于客户端识别错误类型
|
||||
Data interface{} `json:"data"` // 附加数据,可为空
|
||||
Timestamp int64 `json:"timestamp"` // 响应时间戳
|
||||
Data interface{} `json:"data"` // 附加数据,可为空
|
||||
Timestamp int64 `json:"timestamp"` // 响应时间戳
|
||||
}
|
||||
|
||||
// SuccessResponse 统一的成功响应结构
|
||||
@@ -26,10 +30,14 @@ type ErrorResponse struct {
|
||||
type SuccessResponse struct {
|
||||
Success bool `json:"success"` // 请求是否成功,成功响应时固定为true
|
||||
Message string `json:"message"` // 成功消息描述
|
||||
Data interface{} `json:"data"` // 响应数据
|
||||
Data interface{} `json:"data"` // 响应数据
|
||||
Timestamp int64 `json:"timestamp"` // 响应时间戳
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 常量定义
|
||||
// ============================================================================
|
||||
|
||||
// ErrorCode 错误代码常量
|
||||
// 定义标准化的错误代码,用于客户端识别和处理不同类型的错误
|
||||
const (
|
||||
@@ -59,15 +67,19 @@ const (
|
||||
// LogEntry 日志条目结构
|
||||
// 包含完整的日志信息,用于结构化日志记录
|
||||
type LogEntry struct {
|
||||
Level LogLevel `json:"level"` // 日志级别
|
||||
Message string `json:"message"` // 日志消息
|
||||
Error string `json:"error,omitempty"` // 错误信息,仅在错误日志中存在
|
||||
Context interface{} `json:"context,omitempty"` // 上下文信息,额外的结构化数据
|
||||
Timestamp time.Time `json:"timestamp"` // 日志时间戳
|
||||
File string `json:"file"` // 源文件路径
|
||||
Line int `json:"line"` // 源文件行号
|
||||
Level LogLevel `json:"level"` // 日志级别
|
||||
Message string `json:"message"` // 日志消息
|
||||
Error string `json:"error,omitempty"` // 错误信息,仅在错误日志中存在
|
||||
Context interface{} `json:"context,omitempty"` // 上下文信息,额外的结构化数据
|
||||
Timestamp time.Time `json:"timestamp"` // 日志时间戳
|
||||
File string `json:"file"` // 源文件路径
|
||||
Line int `json:"line"` // 源文件行号
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 响应函数
|
||||
// ============================================================================
|
||||
|
||||
// WriteErrorResponse 写入错误响应
|
||||
// c: Gin上下文
|
||||
// statusCode: HTTP状态码
|
||||
@@ -102,6 +114,10 @@ func WriteSuccessResponse(c *gin.Context, statusCode int, message string, data i
|
||||
c.JSON(statusCode, response)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 错误处理函数
|
||||
// ============================================================================
|
||||
|
||||
// HandleDatabaseError 处理数据库错误
|
||||
// c: Gin上下文
|
||||
// err: 数据库错误
|
||||
@@ -152,6 +168,10 @@ func HandleInternalError(c *gin.Context, err error, operation string) {
|
||||
WriteErrorResponse(c, 500, "服务器内部错误", ErrCodeInternalError, nil)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 日志函数
|
||||
// ============================================================================
|
||||
|
||||
// LogInfo 记录信息日志
|
||||
// message: 日志消息
|
||||
// context: 上下文信息
|
||||
@@ -189,6 +209,10 @@ func LogDebug(message string, context interface{}) {
|
||||
printLog(logEntry)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 私有函数
|
||||
// ============================================================================
|
||||
|
||||
// createLogEntry 创建日志条目
|
||||
// level: 日志级别
|
||||
// message: 日志消息
|
||||
@@ -252,4 +276,4 @@ func getLevelString(level LogLevel) string {
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,20 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 结构体定义
|
||||
// ============================================================================
|
||||
|
||||
// Logger 日志工具结构体
|
||||
// 封装logrus.Logger,提供统一的日志接口
|
||||
type Logger struct {
|
||||
*log.Logger // 嵌入logrus.Logger,继承其所有方法
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
// ============================================================================
|
||||
|
||||
// NewLogger 创建新的日志实例,使用全局logrus配置
|
||||
// 返回: 新的Logger实例
|
||||
func NewLogger() *Logger {
|
||||
@@ -32,6 +40,10 @@ func InitLogger() *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 方法函数
|
||||
// ============================================================================
|
||||
|
||||
// WithFields 添加字段到日志条目
|
||||
// fields: 要添加的字段映射
|
||||
// 返回: 包含字段的日志条目
|
||||
@@ -89,6 +101,10 @@ func (l *Logger) LogError(err error, msg string) {
|
||||
l.WithError(err).Error(msg)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 全局变量
|
||||
// ============================================================================
|
||||
|
||||
// GlobalLogger 全局日志实例
|
||||
// 提供全局访问的日志记录器
|
||||
var GlobalLogger *Logger
|
||||
@@ -99,6 +115,10 @@ func init() {
|
||||
GlobalLogger = NewLogger()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 全局函数
|
||||
// ============================================================================
|
||||
|
||||
// GetLogger 获取全局日志实例
|
||||
// 返回: 全局Logger实例
|
||||
func GetLogger() *Logger {
|
||||
@@ -109,4 +129,4 @@ func GetLogger() *Logger {
|
||||
// logger: 要设置的Logger实例
|
||||
func SetGlobalLogger(logger *Logger) {
|
||||
GlobalLogger = logger
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,4 @@ func (l *Logger) LogServerStop() {
|
||||
// configFile: 配置文件路径
|
||||
func (l *Logger) LogConfigLoad(configFile string) {
|
||||
l.WithField("config_file", configFile).Info("配置文件加载")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,17 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 全局变量
|
||||
// ============================================================================
|
||||
|
||||
// serverStartTime 记录进程启动时间(近似服务器启动时间)
|
||||
var serverStartTime = time.Now()
|
||||
|
||||
// ============================================================================
|
||||
// 公共函数
|
||||
// ============================================================================
|
||||
|
||||
// GetServerStartTime 获取服务器启动时间
|
||||
// 返回: 服务器启动的时间戳
|
||||
func GetServerStartTime() time.Time {
|
||||
|
||||
Reference in New Issue
Block a user