mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
New warehouse
This commit is contained in:
80
utils/common.go
Normal file
80
utils/common.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"networkDev/web"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JsonResponse 通用JSON响应函数
|
||||
// 将 success 转换为 code:true -> 0, false -> 1,并输出 data
|
||||
func JsonResponse(w http.ResponseWriter, status int, success bool, message string, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
|
||||
// 将success转换为code格式:true -> 0, false -> 1
|
||||
code := 1
|
||||
if success {
|
||||
code = 0
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": code,
|
||||
"msg": message,
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
// RenderTemplate 通用模板渲染函数
|
||||
// templateName: 模板文件名
|
||||
// data: 模板数据
|
||||
// w: HTTP响应写入器
|
||||
func RenderTemplate(w http.ResponseWriter, templateName string, data map[string]interface{}) error {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
tmpl, err := web.ParseTemplates()
|
||||
if err != nil {
|
||||
http.Error(w, "模板解析失败", http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, templateName, data); err != nil {
|
||||
http.Error(w, "模板渲染失败", http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefaultTemplateData 获取默认模板数据
|
||||
// 返回包含系统基础信息的数据映射
|
||||
func GetDefaultTemplateData() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"SystemName": "网络验证系统",
|
||||
"FooterText": "© 2025 凌动技术 保留所有权利",
|
||||
}
|
||||
}
|
||||
|
||||
// GetClientIP 获取客户端IP地址
|
||||
// 优先从 X-Forwarded-For 和 X-Real-IP 头部获取,否则使用 RemoteAddr
|
||||
func GetClientIP(r *http.Request) string {
|
||||
// 检查 X-Forwarded-For 头部
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
// X-Forwarded-For 可能包含多个IP,取第一个
|
||||
if idx := strings.Index(xff, ","); idx != -1 {
|
||||
return strings.TrimSpace(xff[:idx])
|
||||
}
|
||||
return strings.TrimSpace(xff)
|
||||
}
|
||||
|
||||
// 检查 X-Real-IP 头部
|
||||
if xri := r.Header.Get("X-Real-IP"); xri != "" {
|
||||
return strings.TrimSpace(xri)
|
||||
}
|
||||
|
||||
// 使用 RemoteAddr
|
||||
if idx := strings.LastIndex(r.RemoteAddr, ":"); idx != -1 {
|
||||
return r.RemoteAddr[:idx]
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
323
utils/crypto.go
Normal file
323
utils/crypto.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// CryptoManager 加密管理器,提供高性能的加密解密服务
|
||||
type CryptoManager struct {
|
||||
key []byte
|
||||
gcm cipher.AEAD
|
||||
mutex sync.RWMutex
|
||||
inited bool
|
||||
}
|
||||
|
||||
// 全局加密管理器实例
|
||||
var cryptoManager = &CryptoManager{}
|
||||
|
||||
// initCrypto 初始化加密管理器
|
||||
// 缓存密钥和GCM实例,避免重复创建
|
||||
func (cm *CryptoManager) initCrypto() error {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
if cm.inited {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 从配置中获取密钥
|
||||
secret := viper.GetString("encryption_key")
|
||||
if secret == "" {
|
||||
secret = "default-secret"
|
||||
}
|
||||
|
||||
// 生成AES密钥
|
||||
sum := sha256.Sum256([]byte(secret))
|
||||
cm.key = sum[:]
|
||||
|
||||
// 创建AES cipher
|
||||
block, err := aes.NewCipher(cm.key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建GCM
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm.gcm = gcm
|
||||
cm.inited = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncryptString 字符串加密(AES-256-GCM)
|
||||
// 使用缓存的密钥和GCM实例,提高性能
|
||||
func EncryptString(plain string) (string, error) {
|
||||
if err := cryptoManager.initCrypto(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cryptoManager.mutex.RLock()
|
||||
gcm := cryptoManager.gcm
|
||||
cryptoManager.mutex.RUnlock()
|
||||
|
||||
// 生成随机nonce
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 加密
|
||||
ciphertext := gcm.Seal(nil, nonce, []byte(plain), nil)
|
||||
buf := append(nonce, ciphertext...)
|
||||
return base64.StdEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
// DecryptString 字符串解密(AES-256-GCM)
|
||||
// 使用缓存的密钥和GCM实例,提高性能
|
||||
func DecryptString(enc string) (string, error) {
|
||||
if err := cryptoManager.initCrypto(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cryptoManager.mutex.RLock()
|
||||
gcm := cryptoManager.gcm
|
||||
cryptoManager.mutex.RUnlock()
|
||||
|
||||
// 解码base64
|
||||
data, err := base64.StdEncoding.DecodeString(enc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 检查数据长度
|
||||
if len(data) < gcm.NonceSize() {
|
||||
return "", errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
// 分离nonce和密文
|
||||
nonce := data[:gcm.NonceSize()]
|
||||
ciphertext := data[gcm.NonceSize():]
|
||||
|
||||
// 解密
|
||||
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(plain), nil
|
||||
}
|
||||
|
||||
// ResetCrypto 重置加密管理器(用于配置更新后重新初始化)
|
||||
func ResetCrypto() {
|
||||
cryptoManager.mutex.Lock()
|
||||
defer cryptoManager.mutex.Unlock()
|
||||
cryptoManager.inited = false
|
||||
cryptoManager.key = nil
|
||||
cryptoManager.gcm = nil
|
||||
}
|
||||
|
||||
// EncryptStringBatch 批量加密字符串
|
||||
// 减少锁竞争,提高批量处理性能
|
||||
func EncryptStringBatch(plains []string) ([]string, error) {
|
||||
if err := cryptoManager.initCrypto(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cryptoManager.mutex.RLock()
|
||||
gcm := cryptoManager.gcm
|
||||
cryptoManager.mutex.RUnlock()
|
||||
|
||||
results := make([]string, len(plains))
|
||||
for i, plain := range plains {
|
||||
// 生成随机nonce
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 加密
|
||||
ciphertext := gcm.Seal(nil, nonce, []byte(plain), nil)
|
||||
buf := append(nonce, ciphertext...)
|
||||
results[i] = base64.StdEncoding.EncodeToString(buf)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// DecryptStringBatch 批量解密字符串
|
||||
// 减少锁竞争,提高批量处理性能
|
||||
func DecryptStringBatch(encs []string) ([]string, error) {
|
||||
if err := cryptoManager.initCrypto(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cryptoManager.mutex.RLock()
|
||||
gcm := cryptoManager.gcm
|
||||
cryptoManager.mutex.RUnlock()
|
||||
|
||||
results := make([]string, len(encs))
|
||||
for i, enc := range encs {
|
||||
// 解码base64
|
||||
data, err := base64.StdEncoding.DecodeString(enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查数据长度
|
||||
if len(data) < gcm.NonceSize() {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
// 分离nonce和密文
|
||||
nonce := data[:gcm.NonceSize()]
|
||||
ciphertext := data[gcm.NonceSize():]
|
||||
|
||||
// 解密
|
||||
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results[i] = string(plain)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GenerateRandomSalt 生成随机密码盐值
|
||||
// 生成32字节(64个十六进制字符)的随机盐值,用于加密
|
||||
// 返回: 十六进制格式的盐值字符串和错误信息
|
||||
func GenerateRandomSalt() (string, error) {
|
||||
length := 32 // 固定32字节
|
||||
|
||||
// 生成随机字节
|
||||
bytes := make([]byte, length)
|
||||
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 转换为十六进制字符串
|
||||
return fmt.Sprintf("%x", bytes), nil
|
||||
}
|
||||
|
||||
// EncryptStringWithSalt 使用盐值进行字符串加密(AES-256-GCM)
|
||||
// 将明文和盐值组合后进行加密,增强安全性
|
||||
// plain: 待加密的明文字符串
|
||||
// salt: 加密盐值
|
||||
// 返回: base64编码的密文字符串和错误信息
|
||||
func EncryptStringWithSalt(plain, salt string) (string, error) {
|
||||
if err := cryptoManager.initCrypto(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cryptoManager.mutex.RLock()
|
||||
gcm := cryptoManager.gcm
|
||||
cryptoManager.mutex.RUnlock()
|
||||
|
||||
// 将明文和盐值组合
|
||||
combined := plain + salt
|
||||
|
||||
// 生成随机nonce
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 加密
|
||||
ciphertext := gcm.Seal(nil, nonce, []byte(combined), nil)
|
||||
buf := append(nonce, ciphertext...)
|
||||
return base64.StdEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
// DecryptStringWithSalt 使用盐值进行字符串解密(AES-256-GCM)
|
||||
// 解密密文并移除盐值,返回原始明文
|
||||
// enc: base64编码的密文字符串
|
||||
// salt: 解密盐值
|
||||
// 返回: 解密后的明文字符串和错误信息
|
||||
func DecryptStringWithSalt(enc, salt string) (string, error) {
|
||||
if err := cryptoManager.initCrypto(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cryptoManager.mutex.RLock()
|
||||
gcm := cryptoManager.gcm
|
||||
cryptoManager.mutex.RUnlock()
|
||||
|
||||
// 解码base64
|
||||
data, err := base64.StdEncoding.DecodeString(enc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 检查数据长度
|
||||
if len(data) < gcm.NonceSize() {
|
||||
return "", errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
// 分离nonce和密文
|
||||
nonce := data[:gcm.NonceSize()]
|
||||
ciphertext := data[gcm.NonceSize():]
|
||||
|
||||
// 解密
|
||||
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 移除盐值,返回原始明文
|
||||
combined := string(plain)
|
||||
if len(combined) < len(salt) {
|
||||
return "", errors.New("decrypted data too short")
|
||||
}
|
||||
|
||||
// 验证盐值是否匹配
|
||||
if combined[len(combined)-len(salt):] != salt {
|
||||
return "", errors.New("salt mismatch")
|
||||
}
|
||||
|
||||
return combined[:len(combined)-len(salt)], nil
|
||||
}
|
||||
|
||||
// HashPasswordWithSalt 使用盐值对密码进行哈希处理
|
||||
// 将密码和盐值组合后使用bcrypt进行哈希
|
||||
// password: 原始密码
|
||||
// salt: 密码盐值
|
||||
// 返回: bcrypt哈希值和错误信息
|
||||
func HashPasswordWithSalt(password, salt string) (string, error) {
|
||||
// 将密码和盐值组合
|
||||
combined := password + salt
|
||||
|
||||
// 使用bcrypt进行哈希(成本因子12,平衡安全性和性能)
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(combined), 12)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(hashed), nil
|
||||
}
|
||||
|
||||
// VerifyPasswordWithSalt 验证密码和盐值的组合是否匹配哈希值
|
||||
// password: 原始密码
|
||||
// salt: 密码盐值
|
||||
// hashedPassword: 存储的哈希密码
|
||||
// 返回: 验证结果(true表示匹配)
|
||||
func VerifyPasswordWithSalt(password, salt, hashedPassword string) bool {
|
||||
// 将密码和盐值组合
|
||||
combined := password + salt
|
||||
|
||||
// 使用bcrypt验证
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(combined))
|
||||
return err == nil
|
||||
}
|
||||
327
utils/database.go
Normal file
327
utils/database.go
Normal file
@@ -0,0 +1,327 @@
|
||||
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
|
||||
}
|
||||
269
utils/errors.go
Normal file
269
utils/errors.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ErrorResponse 统一的错误响应结构
|
||||
// 用于标准化API错误响应格式
|
||||
type ErrorResponse struct {
|
||||
Success bool `json:"success"` // 请求是否成功,错误响应时固定为false
|
||||
Message string `json:"message"` // 错误消息描述
|
||||
ErrorCode string `json:"error_code,omitempty"` // 错误代码,用于客户端识别错误类型
|
||||
Data interface{} `json:"data"` // 附加数据,可为空
|
||||
Timestamp int64 `json:"timestamp"` // 响应时间戳
|
||||
}
|
||||
|
||||
// SuccessResponse 统一的成功响应结构
|
||||
// 用于标准化API成功响应格式
|
||||
type SuccessResponse struct {
|
||||
Success bool `json:"success"` // 请求是否成功,成功响应时固定为true
|
||||
Message string `json:"message"` // 成功消息描述
|
||||
Data interface{} `json:"data"` // 响应数据
|
||||
Timestamp int64 `json:"timestamp"` // 响应时间戳
|
||||
}
|
||||
|
||||
// ErrorCode 错误代码常量
|
||||
// 定义标准化的错误代码,用于客户端识别和处理不同类型的错误
|
||||
const (
|
||||
ErrCodeInvalidRequest = "INVALID_REQUEST" // 无效请求,通常是请求参数格式错误
|
||||
ErrCodeUnauthorized = "UNAUTHORIZED" // 未授权,需要登录或token无效
|
||||
ErrCodeForbidden = "FORBIDDEN" // 禁止访问,权限不足
|
||||
ErrCodeNotFound = "NOT_FOUND" // 资源不存在
|
||||
ErrCodeConflict = "CONFLICT" // 资源冲突,如重复创建
|
||||
ErrCodeInternalError = "INTERNAL_ERROR" // 服务器内部错误
|
||||
ErrCodeDatabaseError = "DATABASE_ERROR" // 数据库操作错误
|
||||
ErrCodeValidationError = "VALIDATION_ERROR" // 数据验证错误
|
||||
ErrCodeTokenExpired = "TOKEN_EXPIRED" // 令牌已过期
|
||||
ErrCodeInsufficientData = "INSUFFICIENT_DATA" // 数据不足,缺少必要信息
|
||||
)
|
||||
|
||||
// LogLevel 日志级别
|
||||
// 定义不同的日志记录级别
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
LogLevelInfo LogLevel = iota // 信息级别,记录一般信息
|
||||
LogLevelWarn // 警告级别,记录潜在问题
|
||||
LogLevelError // 错误级别,记录错误信息
|
||||
LogLevelDebug // 调试级别,记录调试信息
|
||||
)
|
||||
|
||||
// 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"` // 源文件行号
|
||||
}
|
||||
|
||||
// WriteJSONResponse 写入JSON响应
|
||||
// w: HTTP响应写入器
|
||||
// statusCode: HTTP状态码
|
||||
// response: 响应数据
|
||||
func WriteJSONResponse(w http.ResponseWriter, statusCode int, response interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
LogError("Failed to encode JSON response", err, nil)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteErrorResponse 写入错误响应
|
||||
// w: HTTP响应写入器
|
||||
// statusCode: HTTP状态码
|
||||
// message: 错误消息
|
||||
// errorCode: 错误代码
|
||||
// data: 附加数据
|
||||
func WriteErrorResponse(w http.ResponseWriter, statusCode int, message, errorCode string, data interface{}) {
|
||||
response := ErrorResponse{
|
||||
Success: false,
|
||||
Message: message,
|
||||
ErrorCode: errorCode,
|
||||
Data: data,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
|
||||
WriteJSONResponse(w, statusCode, response)
|
||||
}
|
||||
|
||||
// WriteSuccessResponse 写入成功响应
|
||||
// w: HTTP响应写入器
|
||||
// statusCode: HTTP状态码
|
||||
// message: 成功消息
|
||||
// data: 响应数据
|
||||
func WriteSuccessResponse(w http.ResponseWriter, statusCode int, message string, data interface{}) {
|
||||
response := SuccessResponse{
|
||||
Success: true,
|
||||
Message: message,
|
||||
Data: data,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
|
||||
WriteJSONResponse(w, statusCode, response)
|
||||
}
|
||||
|
||||
// HandleDatabaseError 处理数据库错误
|
||||
// w: HTTP响应写入器
|
||||
// err: 数据库错误
|
||||
// operation: 操作描述
|
||||
func HandleDatabaseError(w http.ResponseWriter, err error, operation string) {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
LogWarn(fmt.Sprintf("Record not found during %s", operation), map[string]interface{}{
|
||||
"operation": operation,
|
||||
"error": err.Error(),
|
||||
})
|
||||
WriteErrorResponse(w, http.StatusNotFound, "记录不存在", ErrCodeNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
LogError(fmt.Sprintf("Database error during %s", operation), err, map[string]interface{}{
|
||||
"operation": operation,
|
||||
})
|
||||
WriteErrorResponse(w, http.StatusInternalServerError, "数据库操作失败", ErrCodeDatabaseError, nil)
|
||||
}
|
||||
|
||||
// HandleValidationError 处理验证错误
|
||||
// w: HTTP响应写入器
|
||||
// message: 验证错误消息
|
||||
// details: 验证错误详情
|
||||
func HandleValidationError(w http.ResponseWriter, message string, details interface{}) {
|
||||
LogWarn("Validation error: "+message, map[string]interface{}{
|
||||
"details": details,
|
||||
})
|
||||
WriteErrorResponse(w, http.StatusBadRequest, message, ErrCodeValidationError, details)
|
||||
}
|
||||
|
||||
// HandleUnauthorizedError 处理未授权错误
|
||||
// w: HTTP响应写入器
|
||||
// message: 错误消息
|
||||
func HandleUnauthorizedError(w http.ResponseWriter, message string) {
|
||||
LogWarn("Unauthorized access: "+message, nil)
|
||||
WriteErrorResponse(w, http.StatusUnauthorized, message, ErrCodeUnauthorized, nil)
|
||||
}
|
||||
|
||||
// HandleInternalError 处理内部错误
|
||||
// w: HTTP响应写入器
|
||||
// err: 错误
|
||||
// operation: 操作描述
|
||||
func HandleInternalError(w http.ResponseWriter, err error, operation string) {
|
||||
LogError(fmt.Sprintf("Internal error during %s", operation), err, map[string]interface{}{
|
||||
"operation": operation,
|
||||
})
|
||||
WriteErrorResponse(w, http.StatusInternalServerError, "服务器内部错误", ErrCodeInternalError, nil)
|
||||
}
|
||||
|
||||
// LogInfo 记录信息日志
|
||||
// message: 日志消息
|
||||
// context: 上下文信息
|
||||
func LogInfo(message string, context interface{}) {
|
||||
logEntry := createLogEntry(LogLevelInfo, message, nil, context)
|
||||
printLog(logEntry)
|
||||
}
|
||||
|
||||
// LogWarn 记录警告日志
|
||||
// message: 日志消息
|
||||
// context: 上下文信息
|
||||
func LogWarn(message string, context interface{}) {
|
||||
logEntry := createLogEntry(LogLevelWarn, message, nil, context)
|
||||
printLog(logEntry)
|
||||
}
|
||||
|
||||
// LogError 记录错误日志
|
||||
// message: 日志消息
|
||||
// err: 错误对象
|
||||
// context: 上下文信息
|
||||
func LogError(message string, err error, context interface{}) {
|
||||
errorStr := ""
|
||||
if err != nil {
|
||||
errorStr = err.Error()
|
||||
}
|
||||
logEntry := createLogEntry(LogLevelError, message, &errorStr, context)
|
||||
printLog(logEntry)
|
||||
}
|
||||
|
||||
// LogDebug 记录调试日志
|
||||
// message: 日志消息
|
||||
// context: 上下文信息
|
||||
func LogDebug(message string, context interface{}) {
|
||||
logEntry := createLogEntry(LogLevelDebug, message, nil, context)
|
||||
printLog(logEntry)
|
||||
}
|
||||
|
||||
// createLogEntry 创建日志条目
|
||||
// level: 日志级别
|
||||
// message: 日志消息
|
||||
// errorStr: 错误字符串
|
||||
// context: 上下文信息
|
||||
// 返回: 日志条目
|
||||
func createLogEntry(level LogLevel, message string, errorStr *string, context interface{}) LogEntry {
|
||||
_, file, line, _ := runtime.Caller(2)
|
||||
|
||||
entry := LogEntry{
|
||||
Level: level,
|
||||
Message: message,
|
||||
Context: context,
|
||||
Timestamp: time.Now(),
|
||||
File: file,
|
||||
Line: line,
|
||||
}
|
||||
|
||||
if errorStr != nil {
|
||||
entry.Error = *errorStr
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// printLog 打印日志
|
||||
// entry: 日志条目
|
||||
func printLog(entry LogEntry) {
|
||||
levelStr := getLevelString(entry.Level)
|
||||
timestamp := entry.Timestamp.Format("2006-01-02 15:04:05")
|
||||
|
||||
logMessage := fmt.Sprintf("[%s] %s %s", levelStr, timestamp, entry.Message)
|
||||
|
||||
if entry.Error != "" {
|
||||
logMessage += fmt.Sprintf(" | Error: %s", entry.Error)
|
||||
}
|
||||
|
||||
if entry.Context != nil {
|
||||
contextJSON, _ := json.Marshal(entry.Context)
|
||||
logMessage += fmt.Sprintf(" | Context: %s", string(contextJSON))
|
||||
}
|
||||
|
||||
logMessage += fmt.Sprintf(" | %s:%d", entry.File, entry.Line)
|
||||
|
||||
log.Println(logMessage)
|
||||
}
|
||||
|
||||
// getLevelString 获取日志级别字符串
|
||||
// level: 日志级别
|
||||
// 返回: 级别字符串
|
||||
func getLevelString(level LogLevel) string {
|
||||
switch level {
|
||||
case LogLevelInfo:
|
||||
return "INFO"
|
||||
case LogLevelWarn:
|
||||
return "WARN"
|
||||
case LogLevelError:
|
||||
return "ERROR"
|
||||
case LogLevelDebug:
|
||||
return "DEBUG"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
59
utils/logger/http.go
Normal file
59
utils/logger/http.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogRequest 记录HTTP请求日志 - 使用标准Apache Common Log Format
|
||||
// 格式: IP - - [timestamp] "METHOD path HTTP/1.1" status_code response_size
|
||||
// method: HTTP请求方法
|
||||
// path: 请求路径
|
||||
// clientIP: 客户端IP地址
|
||||
// statusCode: HTTP状态码
|
||||
// duration: 请求处理时长
|
||||
func (l *Logger) LogRequest(method, path, clientIP string, statusCode int, duration time.Duration) {
|
||||
l.LogRequestWithHeaders(method, path, clientIP, statusCode, duration, "-", "-")
|
||||
}
|
||||
|
||||
// LogRequestWithHeaders 记录HTTP请求日志 - 使用修改的Apache Log Format(移除Referer字段)
|
||||
// 直接输出标准格式,不通过logrus格式化器
|
||||
// method: HTTP请求方法
|
||||
// path: 请求路径
|
||||
// clientIP: 客户端IP地址
|
||||
// statusCode: HTTP状态码
|
||||
// duration: 请求处理时长
|
||||
// referer: 引用页面(已废弃,保留参数兼容性)
|
||||
// userAgent: 用户代理字符串
|
||||
func (l *Logger) LogRequestWithHeaders(method, path, clientIP string, statusCode int, duration time.Duration, referer, userAgent string) {
|
||||
// 格式化时间戳为Apache标准格式
|
||||
timestamp := time.Now().Format("02/Jan/2006:15:04:05 -0700")
|
||||
|
||||
// 处理空值
|
||||
if userAgent == "" {
|
||||
userAgent = "-"
|
||||
}
|
||||
|
||||
// 构建修改的HTTP Log格式(完全移除Referer字段)
|
||||
logLine := fmt.Sprintf(`%s - - [%s] "%s %s HTTP/1.1" %d - "%s" %dms`,
|
||||
clientIP,
|
||||
timestamp,
|
||||
method,
|
||||
path,
|
||||
statusCode,
|
||||
userAgent,
|
||||
duration.Milliseconds(),
|
||||
)
|
||||
|
||||
// 直接输出到标准输出和日志文件,不使用logrus格式化
|
||||
l.writeHTTPLog(logLine)
|
||||
}
|
||||
|
||||
// writeHTTPLog 直接输出HTTP日志到标准输出
|
||||
// 避免Logrus的任何格式化和转义,保持Apache日志格式的原始性
|
||||
// logLine: 格式化后的日志行
|
||||
func (l *Logger) writeHTTPLog(logLine string) {
|
||||
// 直接输出到标准输出,避免Logrus的转义处理
|
||||
fmt.Fprintln(os.Stdout, logLine)
|
||||
}
|
||||
112
utils/logger/logger.go
Normal file
112
utils/logger/logger.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Logger 日志工具结构体
|
||||
// 封装logrus.Logger,提供统一的日志接口
|
||||
type Logger struct {
|
||||
*log.Logger // 嵌入logrus.Logger,继承其所有方法
|
||||
}
|
||||
|
||||
// NewLogger 创建新的日志实例,使用全局logrus配置
|
||||
// 返回: 新的Logger实例
|
||||
func NewLogger() *Logger {
|
||||
// 使用全局logrus实例而不是创建新实例,确保配置一致性
|
||||
return &Logger{Logger: log.StandardLogger()}
|
||||
}
|
||||
|
||||
// InitLogger 初始化HTTP日志处理器
|
||||
// 创建专门用于HTTP请求日志的Logger实例,使用全局logrus配置
|
||||
// 返回: 初始化后的Logger实例
|
||||
func InitLogger() *Logger {
|
||||
logger := NewLogger()
|
||||
|
||||
// HTTP日志使用全局logrus的配置
|
||||
// 通过使用log.StandardLogger()确保与全局配置保持一致
|
||||
|
||||
// 更新全局日志实例
|
||||
SetGlobalLogger(logger)
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// WithFields 添加字段到日志条目
|
||||
// fields: 要添加的字段映射
|
||||
// 返回: 包含字段的日志条目
|
||||
func (l *Logger) WithFields(fields log.Fields) *log.Entry {
|
||||
return l.Logger.WithFields(fields)
|
||||
}
|
||||
|
||||
// WithField 添加单个字段到日志条目
|
||||
// key: 字段名
|
||||
// value: 字段值
|
||||
// 返回: 包含字段的日志条目
|
||||
func (l *Logger) WithField(key string, value interface{}) *log.Entry {
|
||||
return l.Logger.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithError 添加错误字段到日志条目
|
||||
// err: 要记录的错误
|
||||
// 返回: 包含错误信息的日志条目
|
||||
func (l *Logger) WithError(err error) *log.Entry {
|
||||
return l.Logger.WithError(err)
|
||||
}
|
||||
|
||||
// InfoWithFields 记录带字段的信息级别日志
|
||||
// msg: 日志消息
|
||||
// fields: 附加字段
|
||||
func (l *Logger) InfoWithFields(msg string, fields log.Fields) {
|
||||
l.WithFields(fields).Info(msg)
|
||||
}
|
||||
|
||||
// ErrorWithFields 记录带字段的错误级别日志
|
||||
// msg: 日志消息
|
||||
// fields: 附加字段
|
||||
func (l *Logger) ErrorWithFields(msg string, fields log.Fields) {
|
||||
l.WithFields(fields).Error(msg)
|
||||
}
|
||||
|
||||
// WarnWithFields 记录带字段的警告级别日志
|
||||
// msg: 日志消息
|
||||
// fields: 附加字段
|
||||
func (l *Logger) WarnWithFields(msg string, fields log.Fields) {
|
||||
l.WithFields(fields).Warn(msg)
|
||||
}
|
||||
|
||||
// DebugWithFields 记录带字段的调试级别日志
|
||||
// msg: 日志消息
|
||||
// fields: 附加字段
|
||||
func (l *Logger) DebugWithFields(msg string, fields log.Fields) {
|
||||
l.WithFields(fields).Debug(msg)
|
||||
}
|
||||
|
||||
// LogError 记录错误日志
|
||||
// err: 错误对象
|
||||
// msg: 日志消息
|
||||
func (l *Logger) LogError(err error, msg string) {
|
||||
l.WithError(err).Error(msg)
|
||||
}
|
||||
|
||||
// GlobalLogger 全局日志实例
|
||||
// 提供全局访问的日志记录器
|
||||
var GlobalLogger *Logger
|
||||
|
||||
// init 包初始化函数
|
||||
// 创建全局日志实例,使用全局logrus配置
|
||||
func init() {
|
||||
GlobalLogger = NewLogger()
|
||||
}
|
||||
|
||||
// GetLogger 获取全局日志实例
|
||||
// 返回: 全局Logger实例
|
||||
func GetLogger() *Logger {
|
||||
return GlobalLogger
|
||||
}
|
||||
|
||||
// SetGlobalLogger 设置全局日志实例
|
||||
// logger: 要设置的Logger实例
|
||||
func SetGlobalLogger(logger *Logger) {
|
||||
GlobalLogger = logger
|
||||
}
|
||||
26
utils/logger/server.go
Normal file
26
utils/logger/server.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// LogServerStart 记录服务器启动日志
|
||||
// host: 服务器监听地址
|
||||
// port: 服务器监听端口
|
||||
func (l *Logger) LogServerStart(host string, port int) {
|
||||
l.WithFields(log.Fields{
|
||||
"host": host,
|
||||
"port": port,
|
||||
}).Info("HTTP服务器启动")
|
||||
}
|
||||
|
||||
// LogServerStop 记录服务器停止日志
|
||||
func (l *Logger) LogServerStop() {
|
||||
l.Info("HTTP服务器停止")
|
||||
}
|
||||
|
||||
// LogConfigLoad 记录配置加载日志
|
||||
// configFile: 配置文件路径
|
||||
func (l *Logger) LogConfigLoad(configFile string) {
|
||||
l.WithField("config_file", configFile).Info("配置文件加载")
|
||||
}
|
||||
44
utils/timeutil/server.go
Normal file
44
utils/timeutil/server.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// serverStartTime 记录进程启动时间(近似服务器启动时间)
|
||||
var serverStartTime = time.Now()
|
||||
|
||||
// GetServerStartTime 获取服务器启动时间
|
||||
// 返回: 服务器启动的时间戳
|
||||
func GetServerStartTime() time.Time {
|
||||
return serverStartTime
|
||||
}
|
||||
|
||||
// GetServerUptime 获取服务器运行时长
|
||||
// 返回: 从服务器启动到现在的时间间隔
|
||||
func GetServerUptime() time.Duration {
|
||||
return time.Since(serverStartTime)
|
||||
}
|
||||
|
||||
// GetServerUptimeString 获取服务器运行时长的字符串表示
|
||||
// 返回: 格式化的运行时长字符串
|
||||
func GetServerUptimeString() string {
|
||||
duration := time.Since(serverStartTime)
|
||||
|
||||
// 获取总秒数并转换为整数
|
||||
totalSeconds := int(duration.Seconds())
|
||||
|
||||
// 计算小时、分钟、秒
|
||||
hours := totalSeconds / 3600
|
||||
minutes := (totalSeconds % 3600) / 60
|
||||
seconds := totalSeconds % 60
|
||||
|
||||
// 根据时长长度选择合适的格式
|
||||
if hours > 0 {
|
||||
return fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
|
||||
} else if minutes > 0 {
|
||||
return fmt.Sprintf("%dm%ds", minutes, seconds)
|
||||
} else {
|
||||
return fmt.Sprintf("%ds", seconds)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user