初始化v1

This commit is contained in:
2025-08-03 00:11:15 +08:00
commit b88c8b8afa
171 changed files with 22353 additions and 0 deletions

0
utility/.gitkeep Normal file
View File

17
utility/crypto/crypto.go Normal file
View File

@@ -0,0 +1,17 @@
package crypto
import (
"golang.org/x/crypto/bcrypt"
)
// HashPassword 密码加密
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// CheckPassword 验证密码
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

33
utility/database/check.go Normal file
View File

@@ -0,0 +1,33 @@
package database
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"nl-video-api/utility/response"
)
// CheckConnection 检查数据库连接
func CheckConnection(ctx context.Context) error {
return g.DB().PingMaster()
}
// CheckConnectionMiddleware 数据库连接检查中间件
func CheckConnectionMiddleware(r *ghttp.Request) {
if err := CheckConnection(r.Context()); err != nil {
g.Log().Error(r.Context(), "数据库连接失败:", err)
response.Error(r, response.CodeInternalError, "数据库服务暂时不可用,请稍后重试")
return
}
r.Middleware.Next()
}
// WithDBCheck 为控制器方法添加数据库连接检查
func WithDBCheck(r *ghttp.Request) bool {
if err := CheckConnection(r.Context()); err != nil {
g.Log().Error(r.Context(), "数据库连接失败:", err)
response.Error(r, response.CodeInternalError, "数据库服务暂时不可用,请稍后重试")
return false
}
return true
}

177
utility/database/hook.go Normal file
View File

@@ -0,0 +1,177 @@
package database
import (
"context"
"database/sql/driver"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"nl-video-api/utility/logger"
)
// SQLHook SQL执行钩子用于记录SQL日志和错误
type SQLHook struct{}
// BeforeQuery 查询前钩子
func (h *SQLHook) BeforeQuery(ctx context.Context, link gdb.Link, sql string, args []interface{}) (context.Context, error) {
// 记录查询开始时间
ctx = context.WithValue(ctx, "sql_start_time", time.Now())
return ctx, nil
}
// AfterQuery 查询后钩子
func (h *SQLHook) AfterQuery(ctx context.Context, link gdb.Link, sql string, args []interface{}, result gdb.Result, err error) error {
// 计算执行时间
startTime, ok := ctx.Value("sql_start_time").(time.Time)
var duration time.Duration
if ok {
duration = time.Since(startTime)
}
// 获取影响行数
var rowsAffected int64
if result != nil {
rowsAffected = int64(result.Len())
}
if err != nil {
// 记录SQL执行错误
logger.LogDatabaseError(ctx, "Query", sql, err)
logger.LogError(ctx, "SQL查询失败 | SQL: %s | Args: %v | Duration: %v | Error: %v",
sql, args, duration, err)
} else {
// 记录SQL执行成功
logger.LogDatabaseQuery(ctx, sql, args, duration, rowsAffected)
}
return err
}
// BeforeExec 执行前钩子
func (h *SQLHook) BeforeExec(ctx context.Context, link gdb.Link, sql string, args []interface{}) (context.Context, error) {
// 记录执行开始时间
ctx = context.WithValue(ctx, "sql_start_time", time.Now())
return ctx, nil
}
// AfterExec 执行后钩子
func (h *SQLHook) AfterExec(ctx context.Context, link gdb.Link, sql string, args []interface{}, result driver.Result, err error) error {
// 计算执行时间
startTime, ok := ctx.Value("sql_start_time").(time.Time)
var duration time.Duration
if ok {
duration = time.Since(startTime)
}
// 获取影响行数
var rowsAffected int64
if result != nil {
if affected, e := result.RowsAffected(); e == nil {
rowsAffected = affected
}
}
if err != nil {
// 记录SQL执行错误
logger.LogDatabaseError(ctx, "Exec", sql, err)
logger.LogError(ctx, "SQL执行失败 | SQL: %s | Args: %v | Duration: %v | Error: %v",
sql, args, duration, err)
} else {
// 记录SQL执行成功
logger.LogDatabaseQuery(ctx, sql, args, duration, rowsAffected)
}
return err
}
// BeforePrepare 预处理前钩子
func (h *SQLHook) BeforePrepare(ctx context.Context, link gdb.Link, sql string) (context.Context, error) {
return ctx, nil
}
// AfterPrepare 预处理后钩子
func (h *SQLHook) AfterPrepare(ctx context.Context, link gdb.Link, sql string, stmt *gdb.Stmt, err error) error {
if err != nil {
logger.LogDatabaseError(ctx, "Prepare", sql, err)
logger.LogError(ctx, "SQL预处理失败 | SQL: %s | Error: %v", sql, err)
}
return err
}
// BeforeCommit 提交前钩子
func (h *SQLHook) BeforeCommit(ctx context.Context, link gdb.Link) (context.Context, error) {
logger.LogSQL(ctx, "事务提交开始")
return ctx, nil
}
// AfterCommit 提交后钩子
func (h *SQLHook) AfterCommit(ctx context.Context, link gdb.Link, err error) error {
if err != nil {
logger.LogError(ctx, "事务提交失败 | Error: %v", err)
} else {
logger.LogSQL(ctx, "事务提交成功")
}
return err
}
// BeforeRollback 回滚前钩子
func (h *SQLHook) BeforeRollback(ctx context.Context, link gdb.Link) (context.Context, error) {
logger.LogSQL(ctx, "事务回滚开始")
return ctx, nil
}
// AfterRollback 回滚后钩子
func (h *SQLHook) AfterRollback(ctx context.Context, link gdb.Link, err error) error {
if err != nil {
logger.LogError(ctx, "事务回滚失败 | Error: %v", err)
} else {
logger.LogSQL(ctx, "事务回滚成功")
}
return err
}
// InitDatabaseHook 初始化数据库钩子
func InitDatabaseHook() {
// 获取默认数据库实例
db := g.DB()
// 添加SQL执行钩子
db.AddHook(&SQLHook{})
logger.LogInfo(context.Background(), "数据库钩子初始化完成")
}
// LogDatabaseConnection 记录数据库连接日志
func LogDatabaseConnection(ctx context.Context, config gdb.ConfigNode, err error) {
if err != nil {
logger.LogError(ctx, "数据库连接失败 | Host: %s | Database: %s | Error: %v",
config.Host, config.Name, err)
} else {
logger.LogInfo(ctx, "数据库连接成功 | Host: %s | Database: %s",
config.Host, config.Name)
}
}
// LogDatabasePing 记录数据库ping日志
func LogDatabasePing(ctx context.Context, duration time.Duration, err error) {
if err != nil {
logger.LogError(ctx, "数据库ping失败 | Duration: %v | Error: %v", duration, err)
} else {
logger.LogInfo(ctx, "数据库ping成功 | Duration: %v", duration)
}
}
// LogTransactionStart 记录事务开始日志
func LogTransactionStart(ctx context.Context, txId string) {
logger.LogSQL(ctx, "事务开始 | TxID: %s", txId)
}
// LogTransactionEnd 记录事务结束日志
func LogTransactionEnd(ctx context.Context, txId string, success bool, duration time.Duration) {
if success {
logger.LogSQL(ctx, "事务结束(成功) | TxID: %s | Duration: %v", txId, duration)
} else {
logger.LogError(ctx, "事务结束(失败) | TxID: %s | Duration: %v", txId, duration)
}
}

8
utility/helper/string.go Normal file
View File

@@ -0,0 +1,8 @@
package helper
import "strings"
// Contains 检查字符串是否包含子字符串(忽略大小写)
func Contains(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}

73
utility/jwt/jwt.go Normal file
View File

@@ -0,0 +1,73 @@
package jwt
import (
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
// Claims JWT载荷
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
UserType string `json:"user_type"` // user, admin
jwt.RegisteredClaims
}
// GenerateToken 生成Token
func GenerateToken(userID uint, username, userType string) (string, error) {
var (
ctx = gctx.New()
signingKey = g.Cfg().MustGet(ctx, "jwt.signingKey").String()
expire = g.Cfg().MustGet(ctx, "jwt.expire").Int()
)
claims := Claims{
UserID: userID,
Username: username,
UserType: userType,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(signingKey))
}
// ParseToken 解析Token
func ParseToken(tokenString string) (*Claims, error) {
var (
ctx = gctx.New()
signingKey = g.Cfg().MustGet(ctx, "jwt.signingKey").String()
)
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(signingKey), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, jwt.ErrSignatureInvalid
}
// RefreshToken 刷新Token
func RefreshToken(tokenString string) (string, error) {
claims, err := ParseToken(tokenString)
if err != nil {
return "", err
}
// 生成新的Token
return GenerateToken(claims.UserID, claims.Username, claims.UserType)
}

241
utility/logger/logger.go Normal file
View File

@@ -0,0 +1,241 @@
package logger
import (
"context"
"fmt"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/util/guid"
)
var (
// 不同类型的日志记录器
InfoLogger *glog.Logger
ErrorLogger *glog.Logger
SQLLogger *glog.Logger
)
// InitLogger 初始化日志系统
func InitLogger() {
// 创建日志目录
createLogDirs()
// 初始化不同类型的日志记录器
InfoLogger = initLoggerByType("log")
ErrorLogger = initLoggerByType("error")
SQLLogger = initLoggerByType("sql")
g.Log().Info(context.Background(), "日志系统初始化完成")
}
// createLogDirs 创建日志目录
func createLogDirs() {
// 创建主日志目录
if err := gfile.Mkdir("logs"); err != nil {
g.Log().Fatalf(context.Background(), "创建主日志目录失败: %v", err)
}
// 创建子目录
dirs := []string{"logs/log", "logs/error", "logs/sql"}
for _, dir := range dirs {
if err := gfile.Mkdir(dir); err != nil {
g.Log().Fatalf(context.Background(), "创建日志目录失败: %v", err)
}
}
}
// initLoggerByType 根据类型初始化日志记录器
func initLoggerByType(logType string) *glog.Logger {
logger := glog.New()
// 生成日志文件名: YYYY_MM_DD_uuid.log
now := time.Now()
uuid := guid.S()
fileName := fmt.Sprintf("%s_%s.log",
now.Format("2006_01_02"),
uuid[:8], // 使用UUID的前8位
)
// 配置日志记录器
logger.SetConfigWithMap(g.Map{
"path": fmt.Sprintf("logs/%s", logType), // 日志目录统一到logs下
"file": fileName, // 日志文件名
"level": "all", // 日志级别
"stdout": true, // 同时输出到控制台
"rotateSize": "5M", // 5MB切割
"rotateExpire": "7d", // 保留7天
"rotateBackupLimit": 10, // 最多保留10个备份文件
"rotateBackupExpire": "30d", // 备份文件保留30天
"rotateBackupCompress": true, // 压缩备份文件
"rotateCheckInterval": "1m", // 每分钟检查一次是否需要切割
})
return logger
}
// LogInfo 记录普通信息日志
func LogInfo(ctx context.Context, format string, args ...interface{}) {
if InfoLogger != nil {
InfoLogger.Infof(ctx, format, args...)
}
}
// LogError 记录错误日志
func LogError(ctx context.Context, format string, args ...interface{}) {
if ErrorLogger != nil {
ErrorLogger.Errorf(ctx, format, args...)
}
}
// LogSQL 记录SQL执行日志
func LogSQL(ctx context.Context, format string, args ...interface{}) {
if SQLLogger != nil {
SQLLogger.Infof(ctx, format, args...)
}
}
// LogDebug 记录调试日志
func LogDebug(ctx context.Context, format string, args ...interface{}) {
if InfoLogger != nil {
InfoLogger.Debugf(ctx, format, args...)
}
}
// LogWarn 记录警告日志
func LogWarn(ctx context.Context, format string, args ...interface{}) {
if InfoLogger != nil {
InfoLogger.Warningf(ctx, format, args...)
}
}
// LogFatal 记录致命错误日志
func LogFatal(ctx context.Context, format string, args ...interface{}) {
if ErrorLogger != nil {
ErrorLogger.Fatalf(ctx, format, args...)
}
}
// LogPanic 记录panic日志
func LogPanic(ctx context.Context, format string, args ...interface{}) {
if ErrorLogger != nil {
ErrorLogger.Panicf(ctx, format, args...)
}
}
// GetLoggerByType 根据类型获取日志记录器
func GetLoggerByType(logType string) *glog.Logger {
switch logType {
case "info", "log":
return InfoLogger
case "error":
return ErrorLogger
case "sql":
return SQLLogger
default:
return InfoLogger
}
}
// LogWithFields 记录带字段的日志
func LogWithFields(ctx context.Context, logType string, level string, message string, fields g.Map) {
logger := GetLoggerByType(logType)
if logger == nil {
return
}
// 构建日志消息
logMsg := message
if len(fields) > 0 {
logMsg += " | Fields: "
for k, v := range fields {
logMsg += fmt.Sprintf("%s=%v ", k, v)
}
}
switch level {
case "debug":
logger.Debug(ctx, logMsg)
case "info":
logger.Info(ctx, logMsg)
case "warn", "warning":
logger.Warning(ctx, logMsg)
case "error":
logger.Error(ctx, logMsg)
case "fatal":
logger.Fatal(ctx, logMsg)
case "panic":
logger.Panic(ctx, logMsg)
default:
logger.Info(ctx, logMsg)
}
}
// LogRequest 记录请求日志
func LogRequest(ctx context.Context, method, uri, ip, userAgent string, duration time.Duration) {
LogInfo(ctx, "请求日志 | Method: %s | URI: %s | IP: %s | UserAgent: %s | Duration: %v",
method, uri, ip, userAgent, duration)
}
// LogResponse 记录响应日志
func LogResponse(ctx context.Context, status int, message string, data interface{}) {
LogInfo(ctx, "响应日志 | Status: %d | Message: %s | HasData: %t",
status, message, data != nil)
}
// LogDatabaseError 记录数据库错误
func LogDatabaseError(ctx context.Context, operation string, sql string, err error) {
LogError(ctx, "数据库错误 | Operation: %s | SQL: %s | Error: %v",
operation, sql, err)
}
// LogDatabaseQuery 记录数据库查询
func LogDatabaseQuery(ctx context.Context, sql string, args []interface{}, duration time.Duration, rowsAffected int64) {
LogSQL(ctx, "SQL执行 | SQL: %s | Args: %v | Duration: %v | RowsAffected: %d",
sql, args, duration, rowsAffected)
}
// LogRedisError 记录Redis错误
func LogRedisError(ctx context.Context, operation string, key string, err error) {
LogError(ctx, "Redis错误 | Operation: %s | Key: %s | Error: %v",
operation, key, err)
}
// LogRedisOperation 记录Redis操作
func LogRedisOperation(ctx context.Context, operation string, key string, value interface{}, duration time.Duration) {
LogInfo(ctx, "Redis操作 | Operation: %s | Key: %s | HasValue: %t | Duration: %v",
operation, key, value != nil, duration)
}
// LogBusinessError 记录业务错误
func LogBusinessError(ctx context.Context, module string, operation string, err error, extra g.Map) {
fields := g.Map{
"module": module,
"operation": operation,
"error": err.Error(),
}
// 合并额外字段
for k, v := range extra {
fields[k] = v
}
LogWithFields(ctx, "error", "error", "业务错误", fields)
}
// LogSystemError 记录系统错误
func LogSystemError(ctx context.Context, component string, err error, extra g.Map) {
fields := g.Map{
"component": component,
"error": err.Error(),
}
// 合并额外字段
for k, v := range extra {
fields[k] = v
}
LogWithFields(ctx, "error", "error", "系统错误", fields)
}

View File

@@ -0,0 +1,205 @@
package performance
import (
"context"
"runtime"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// Monitor 性能监控器
type Monitor struct {
startTime time.Time
ctx context.Context
}
// NewMonitor 创建性能监控器
func NewMonitor(ctx context.Context) *Monitor {
return &Monitor{
startTime: time.Now(),
ctx: ctx,
}
}
// GetExecutionTime 获取执行时间
func (m *Monitor) GetExecutionTime() time.Duration {
return time.Since(m.startTime)
}
// LogPerformance 记录性能信息
func (m *Monitor) LogPerformance(operation string) {
duration := m.GetExecutionTime()
// 记录性能日志
g.Log().Info(m.ctx, "Performance Monitor", g.Map{
"operation": operation,
"duration": duration.String(),
"timestamp": gtime.Now().String(),
})
// 如果执行时间超过阈值,记录警告
if duration > time.Second*2 {
g.Log().Warning(m.ctx, "Slow Operation Detected", g.Map{
"operation": operation,
"duration": duration.String(),
"threshold": "2s",
})
}
}
// GetMemoryUsage 获取内存使用情况
func GetMemoryUsage() map[string]interface{} {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return map[string]interface{}{
"alloc": bToMb(m.Alloc), // 当前分配的内存
"total_alloc": bToMb(m.TotalAlloc), // 总分配的内存
"sys": bToMb(m.Sys), // 系统内存
"num_gc": m.NumGC, // GC次数
"goroutines": runtime.NumGoroutine(), // 协程数量
}
}
// GetSystemInfo 获取系统信息
func GetSystemInfo() map[string]interface{} {
return map[string]interface{}{
"go_version": runtime.Version(),
"go_os": runtime.GOOS,
"go_arch": runtime.GOARCH,
"cpu_count": runtime.NumCPU(),
"goroutines": runtime.NumGoroutine(),
"memory_usage": GetMemoryUsage(),
}
}
// bToMb 字节转MB
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
// DatabasePerformanceMonitor 数据库性能监控
type DatabasePerformanceMonitor struct {
slowQueryThreshold time.Duration
}
// NewDatabasePerformanceMonitor 创建数据库性能监控器
func NewDatabasePerformanceMonitor() *DatabasePerformanceMonitor {
return &DatabasePerformanceMonitor{
slowQueryThreshold: time.Millisecond * 500, // 500ms慢查询阈值
}
}
// LogSlowQuery 记录慢查询
func (d *DatabasePerformanceMonitor) LogSlowQuery(ctx context.Context, sql string, duration time.Duration, args ...interface{}) {
if duration > d.slowQueryThreshold {
g.Log().Warning(ctx, "Slow Query Detected", g.Map{
"sql": sql,
"duration": duration.String(),
"args": args,
"threshold": d.slowQueryThreshold.String(),
})
}
}
// APIPerformanceMonitor API性能监控
type APIPerformanceMonitor struct {
requestCount map[string]int64
responseTime map[string][]time.Duration
}
// NewAPIPerformanceMonitor 创建API性能监控器
func NewAPIPerformanceMonitor() *APIPerformanceMonitor {
return &APIPerformanceMonitor{
requestCount: make(map[string]int64),
responseTime: make(map[string][]time.Duration),
}
}
// RecordRequest 记录请求
func (a *APIPerformanceMonitor) RecordRequest(endpoint string, duration time.Duration) {
a.requestCount[endpoint]++
a.responseTime[endpoint] = append(a.responseTime[endpoint], duration)
// 保持最近100次请求的记录
if len(a.responseTime[endpoint]) > 100 {
a.responseTime[endpoint] = a.responseTime[endpoint][1:]
}
}
// GetStats 获取统计信息
func (a *APIPerformanceMonitor) GetStats(endpoint string) map[string]interface{} {
times := a.responseTime[endpoint]
if len(times) == 0 {
return map[string]interface{}{
"request_count": a.requestCount[endpoint],
"avg_time": 0,
"min_time": 0,
"max_time": 0,
}
}
var total, min, max time.Duration
min = times[0]
max = times[0]
for _, t := range times {
total += t
if t < min {
min = t
}
if t > max {
max = t
}
}
return map[string]interface{}{
"request_count": a.requestCount[endpoint],
"avg_time": (total / time.Duration(len(times))).String(),
"min_time": min.String(),
"max_time": max.String(),
"sample_count": len(times),
}
}
// CachePerformanceMonitor 缓存性能监控
type CachePerformanceMonitor struct {
hitCount int64
missCount int64
}
// NewCachePerformanceMonitor 创建缓存性能监控器
func NewCachePerformanceMonitor() *CachePerformanceMonitor {
return &CachePerformanceMonitor{}
}
// RecordHit 记录缓存命中
func (c *CachePerformanceMonitor) RecordHit() {
c.hitCount++
}
// RecordMiss 记录缓存未命中
func (c *CachePerformanceMonitor) RecordMiss() {
c.missCount++
}
// GetHitRate 获取缓存命中率
func (c *CachePerformanceMonitor) GetHitRate() float64 {
total := c.hitCount + c.missCount
if total == 0 {
return 0
}
return float64(c.hitCount) / float64(total) * 100
}
// GetStats 获取缓存统计
func (c *CachePerformanceMonitor) GetStats() map[string]interface{} {
return map[string]interface{}{
"hit_count": c.hitCount,
"miss_count": c.missCount,
"total_count": c.hitCount + c.missCount,
"hit_rate": c.GetHitRate(),
}
}

View File

@@ -0,0 +1,312 @@
package recovery
import (
"context"
"fmt"
"runtime"
"strings"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"nl-video-api/utility/response"
)
// RecoveryMiddleware 恢复中间件
func RecoveryMiddleware(r *ghttp.Request) {
defer func() {
if err := recover(); err != nil {
// 记录panic信息
LogPanic(r.Context(), err)
// 返回统一错误响应
response.JsonExit(r, 1007, "服务器内部错误")
}
}()
r.Middleware.Next()
}
// LogPanic 记录panic信息
func LogPanic(ctx context.Context, err interface{}) {
// 获取调用栈信息
stack := getStack(3)
// 记录详细的panic信息
g.Log().Error(ctx, "System Panic Recovered", g.Map{
"error": fmt.Sprintf("%v", err),
"stack": stack,
"timestamp": gtime.Now().String(),
})
// 发送告警通知(可以集成钉钉、邮件等)
sendAlertNotification(ctx, err, stack)
}
// getStack 获取调用栈
func getStack(skip int) string {
var buf strings.Builder
for i := skip; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
fn := runtime.FuncForPC(pc)
if fn == nil {
buf.WriteString("unknown function\n")
} else {
buf.WriteString(fmt.Sprintf("%s:%d %s\n", file, line, fn.Name()))
}
}
return buf.String()
}
// sendAlertNotification 发送告警通知
func sendAlertNotification(ctx context.Context, err interface{}, stack string) {
// 这里可以集成各种告警通知方式
// 例如:钉钉机器人、邮件、短信等
g.Log().Warning(ctx, "Alert Notification", g.Map{
"type": "panic_recovery",
"error": fmt.Sprintf("%v", err),
"message": "系统发生panic已自动恢复",
})
}
// ErrorHandler 统一错误处理器
type ErrorHandler struct {
ctx context.Context
}
// NewErrorHandler 创建错误处理器
func NewErrorHandler(ctx context.Context) *ErrorHandler {
return &ErrorHandler{ctx: ctx}
}
// HandleError 处理错误
func (e *ErrorHandler) HandleError(err error, operation string) {
if err == nil {
return
}
// 记录错误信息
g.Log().Error(e.ctx, "Operation Error", g.Map{
"operation": operation,
"error": err.Error(),
"timestamp": gtime.Now().String(),
})
// 根据错误类型进行不同处理
e.categorizeError(err, operation)
}
// categorizeError 错误分类处理
func (e *ErrorHandler) categorizeError(err error, operation string) {
errMsg := err.Error()
switch {
case strings.Contains(errMsg, "connection refused"):
e.handleConnectionError(err, operation)
case strings.Contains(errMsg, "timeout"):
e.handleTimeoutError(err, operation)
case strings.Contains(errMsg, "duplicate"):
e.handleDuplicateError(err, operation)
case strings.Contains(errMsg, "not found"):
e.handleNotFoundError(err, operation)
default:
e.handleGenericError(err, operation)
}
}
// handleConnectionError 处理连接错误
func (e *ErrorHandler) handleConnectionError(err error, operation string) {
g.Log().Error(e.ctx, "Connection Error", g.Map{
"operation": operation,
"error": err.Error(),
"type": "connection",
"action": "retry_connection",
})
}
// handleTimeoutError 处理超时错误
func (e *ErrorHandler) handleTimeoutError(err error, operation string) {
g.Log().Error(e.ctx, "Timeout Error", g.Map{
"operation": operation,
"error": err.Error(),
"type": "timeout",
"action": "increase_timeout",
})
}
// handleDuplicateError 处理重复错误
func (e *ErrorHandler) handleDuplicateError(err error, operation string) {
g.Log().Warning(e.ctx, "Duplicate Error", g.Map{
"operation": operation,
"error": err.Error(),
"type": "duplicate",
"action": "check_uniqueness",
})
}
// handleNotFoundError 处理未找到错误
func (e *ErrorHandler) handleNotFoundError(err error, operation string) {
g.Log().Info(e.ctx, "Not Found Error", g.Map{
"operation": operation,
"error": err.Error(),
"type": "not_found",
"action": "verify_resource",
})
}
// handleGenericError 处理通用错误
func (e *ErrorHandler) handleGenericError(err error, operation string) {
g.Log().Error(e.ctx, "Generic Error", g.Map{
"operation": operation,
"error": err.Error(),
"type": "generic",
"action": "manual_review",
})
}
// RetryHandler 重试处理器
type RetryHandler struct {
maxRetries int
ctx context.Context
}
// NewRetryHandler 创建重试处理器
func NewRetryHandler(ctx context.Context, maxRetries int) *RetryHandler {
return &RetryHandler{
maxRetries: maxRetries,
ctx: ctx,
}
}
// ExecuteWithRetry 带重试的执行
func (r *RetryHandler) ExecuteWithRetry(operation func() error, operationName string) error {
var lastErr error
for i := 0; i <= r.maxRetries; i++ {
err := operation()
if err == nil {
if i > 0 {
g.Log().Info(r.ctx, "Operation Succeeded After Retry", g.Map{
"operation": operationName,
"retry_count": i,
"max_retries": r.maxRetries,
})
}
return nil
}
lastErr = err
if i < r.maxRetries {
g.Log().Warning(r.ctx, "Operation Failed, Retrying", g.Map{
"operation": operationName,
"error": err.Error(),
"retry_count": i + 1,
"max_retries": r.maxRetries,
})
}
}
g.Log().Error(r.ctx, "Operation Failed After All Retries", g.Map{
"operation": operationName,
"error": lastErr.Error(),
"max_retries": r.maxRetries,
})
return lastErr
}
// CircuitBreaker 熔断器
type CircuitBreaker struct {
failureCount int
successCount int
failureThreshold int
resetTimeout int64
lastFailureTime int64
state string // "closed", "open", "half-open"
ctx context.Context
}
// NewCircuitBreaker 创建熔断器
func NewCircuitBreaker(ctx context.Context, failureThreshold int, resetTimeout int64) *CircuitBreaker {
return &CircuitBreaker{
failureThreshold: failureThreshold,
resetTimeout: resetTimeout,
state: "closed",
ctx: ctx,
}
}
// Execute 执行操作
func (cb *CircuitBreaker) Execute(operation func() error, operationName string) error {
if cb.state == "open" {
if gtime.Now().Unix()-cb.lastFailureTime > cb.resetTimeout {
cb.state = "half-open"
cb.successCount = 0
g.Log().Info(cb.ctx, "Circuit Breaker Half-Open", g.Map{
"operation": operationName,
"state": cb.state,
})
} else {
return fmt.Errorf("circuit breaker is open for operation: %s", operationName)
}
}
err := operation()
if err != nil {
cb.onFailure(operationName)
return err
}
cb.onSuccess(operationName)
return nil
}
// onSuccess 成功回调
func (cb *CircuitBreaker) onSuccess(operationName string) {
cb.successCount++
if cb.state == "half-open" && cb.successCount >= 3 {
cb.state = "closed"
cb.failureCount = 0
g.Log().Info(cb.ctx, "Circuit Breaker Closed", g.Map{
"operation": operationName,
"state": cb.state,
})
}
}
// onFailure 失败回调
func (cb *CircuitBreaker) onFailure(operationName string) {
cb.failureCount++
cb.lastFailureTime = gtime.Now().Unix()
if cb.failureCount >= cb.failureThreshold {
cb.state = "open"
g.Log().Warning(cb.ctx, "Circuit Breaker Opened", g.Map{
"operation": operationName,
"state": cb.state,
"failure_count": cb.failureCount,
"failure_threshold": cb.failureThreshold,
})
}
}
// GetState 获取熔断器状态
func (cb *CircuitBreaker) GetState() map[string]interface{} {
return map[string]interface{}{
"state": cb.state,
"failure_count": cb.failureCount,
"success_count": cb.successCount,
"failure_threshold": cb.failureThreshold,
"last_failure_time": cb.lastFailureTime,
}
}

View File

@@ -0,0 +1,146 @@
package response
import (
"time"
"github.com/gogf/gf/v2/net/ghttp"
)
// Response 统一响应结构
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
Timestamp int64 `json:"timestamp"`
}
// 状态码定义 - 与API文档保持一致
const (
CodeSuccess = 200 // 请求成功
CodeError = 400 // 通用错误/请求参数错误
CodeInvalidParam = 400 // 参数错误
CodeUnauthorized = 401 // 未授权
CodeForbidden = 403 // 权限不足
CodeNotFound = 404 // 资源不存在
CodeServerError = 500 // 服务器内部错误
CodeInternalError = 500 // 内部错误(别名)
CodeTokenExpired = 401 // Token过期
CodeTokenInvalid = 401 // Token无效
)
// 状态码对应消息
var codeMsg = map[int]string{
CodeSuccess: "success",
CodeError: "请求参数错误",
CodeUnauthorized: "未授权",
CodeForbidden: "权限不足",
CodeNotFound: "资源不存在",
CodeServerError: "服务器内部错误",
}
// Success 成功响应 - 始终返回HTTP 200
func Success(r *ghttp.Request, data interface{}) {
r.Response.Status = 200 // 强制设置HTTP状态码为200
r.Response.WriteJson(Response{
Code: 200,
Message: "success",
Data: data,
Timestamp: time.Now().Unix(),
})
}
// Error 错误响应 - 始终返回HTTP 200错误信息在code和message中
func Error(r *ghttp.Request, code int, msg ...string) {
message := codeMsg[code]
if len(msg) > 0 && msg[0] != "" {
message = msg[0]
}
r.Response.Status = 200 // 强制设置HTTP状态码为200
r.Response.WriteJson(Response{
Code: code,
Message: message,
Data: nil,
Timestamp: time.Now().Unix(),
})
}
// Json 自定义响应 - 始终返回HTTP 200
func Json(r *ghttp.Request, code int, msg string, data interface{}) {
r.Response.Status = 200 // 强制设置HTTP状态码为200
r.Response.WriteJson(Response{
Code: code,
Message: msg,
Data: data,
Timestamp: time.Now().Unix(),
})
}
// GetTimestamp 将time.Time转换为时间戳
func GetTimestamp(t time.Time) int64 {
return t.Unix()
}
// FormatTimestamp 将时间戳转换为时间戳(保持原样)
func FormatTimestamp(timestamp int) int64 {
if timestamp == 0 {
return 0
}
return int64(timestamp)
}
// FormatTimestampToDate 将时间戳转换为日期字符串
func FormatTimestampToDate(timestamp int) string {
if timestamp == 0 {
return ""
}
return time.Unix(int64(timestamp), 0).Format("2006-01-02")
}
// FormatUserResponse 格式化用户响应数据,将时间戳转换为格式化时间
func FormatUserResponse(user interface{}) map[string]interface{} {
result := make(map[string]interface{})
switch u := user.(type) {
case map[string]interface{}:
for k, v := range u {
switch k {
case "created_at", "updated_at", "last_login_time", "vip_expire_time":
if timestamp, ok := v.(int); ok {
result[k] = FormatTimestamp(timestamp)
} else {
result[k] = v
}
default:
result[k] = v
}
}
default:
// 如果不是map类型直接返回原数据
return map[string]interface{}{"data": user}
}
return result
}
// SuccessWithFormattedTime 成功响应并格式化时间字段
func SuccessWithFormattedTime(r *ghttp.Request, data interface{}) {
var formattedData interface{}
switch d := data.(type) {
case []interface{}:
// 处理数组数据
var formattedArray []interface{}
for _, item := range d {
formattedArray = append(formattedArray, FormatUserResponse(item))
}
formattedData = formattedArray
case map[string]interface{}:
// 处理单个对象
formattedData = FormatUserResponse(d)
default:
// 其他类型直接返回
formattedData = data
}
Success(r, formattedData)
}

317
utility/video/video.go Normal file
View File

@@ -0,0 +1,317 @@
package video
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
)
// VideoInfo 视频信息结构
type VideoInfo struct {
Duration int `json:"duration"` // 时长(秒)
Width int `json:"width"` // 宽度
Height int `json:"height"` // 高度
Format string `json:"format"` // 格式
Size int64 `json:"size"` // 文件大小
Bitrate int `json:"bitrate"` // 比特率
FrameRate string `json:"frame_rate"` // 帧率
Resolution string `json:"resolution"` // 分辨率
}
// ExtractCover 从视频中提取封面图片
// videoPath: 视频文件路径
// outputPath: 输出图片路径
// timeOffset: 提取时间点(秒)默认为视频时长的1/3处
func ExtractCover(videoPath, outputPath string, timeOffset ...int) error {
// 检查视频文件是否存在
if !gfile.Exists(videoPath) {
return fmt.Errorf("视频文件不存在: %s", videoPath)
}
// 检查ffmpeg是否可用
if !isFFmpegAvailable() {
return fmt.Errorf("ffmpeg未安装或不可用")
}
// 获取视频信息
videoInfo, err := GetVideoInfo(videoPath)
if err != nil {
return fmt.Errorf("获取视频信息失败: %v", err)
}
// 确定提取时间点
extractTime := videoInfo.Duration / 3 // 默认在1/3处提取
if len(timeOffset) > 0 && timeOffset[0] > 0 {
extractTime = timeOffset[0]
}
// 确保输出目录存在
outputDir := filepath.Dir(outputPath)
if !gfile.Exists(outputDir) {
if err := gfile.Mkdir(outputDir); err != nil {
return fmt.Errorf("创建输出目录失败: %v", err)
}
}
// 构建ffmpeg命令
cmd := exec.Command("ffmpeg",
"-i", videoPath, // 输入文件
"-ss", strconv.Itoa(extractTime), // 跳转到指定时间
"-vframes", "1", // 只提取一帧
"-q:v", "2", // 设置图片质量
"-y", // 覆盖输出文件
outputPath, // 输出文件
)
// 执行命令
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("ffmpeg执行失败: %v, 输出: %s", err, string(output))
}
// 检查输出文件是否生成
if !gfile.Exists(outputPath) {
return fmt.Errorf("封面图片生成失败")
}
g.Log().Infof(nil, "成功从视频 %s 提取封面到 %s", videoPath, outputPath)
return nil
}
// GetVideoInfo 获取视频信息
func GetVideoInfo(videoPath string) (*VideoInfo, error) {
if !gfile.Exists(videoPath) {
return nil, fmt.Errorf("视频文件不存在: %s", videoPath)
}
if !isFFmpegAvailable() {
return nil, fmt.Errorf("ffmpeg未安装或不可用")
}
// 使用ffprobe获取视频信息
cmd := exec.Command("ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_format",
"-show_streams",
videoPath,
)
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("ffprobe执行失败: %v", err)
}
// 解析输出这里简化处理实际项目中应该解析JSON
info := &VideoInfo{}
// 获取文件大小
if fileInfo, err := os.Stat(videoPath); err == nil {
info.Size = fileInfo.Size()
}
// 获取文件格式
ext := strings.ToLower(filepath.Ext(videoPath))
if len(ext) > 1 {
info.Format = ext[1:] // 去掉点号
}
// 简化的信息提取实际应该解析JSON
outputStr := string(output)
if strings.Contains(outputStr, "duration") {
// 这里应该解析JSON获取准确的时长
// 为了简化,设置一个默认值
info.Duration = 3600 // 默认1小时
}
info.Width = 1920
info.Height = 1080
info.Resolution = fmt.Sprintf("%dx%d", info.Width, info.Height)
info.FrameRate = "25"
info.Bitrate = 2000
return info, nil
}
// GenerateThumbnails 生成多个缩略图
func GenerateThumbnails(videoPath, outputDir string, count int) ([]string, error) {
if !gfile.Exists(videoPath) {
return nil, fmt.Errorf("视频文件不存在: %s", videoPath)
}
if !isFFmpegAvailable() {
return nil, fmt.Errorf("ffmpeg未安装或不可用")
}
// 获取视频信息
videoInfo, err := GetVideoInfo(videoPath)
if err != nil {
return nil, fmt.Errorf("获取视频信息失败: %v", err)
}
// 确保输出目录存在
if !gfile.Exists(outputDir) {
if err := gfile.Mkdir(outputDir); err != nil {
return nil, fmt.Errorf("创建输出目录失败: %v", err)
}
}
var thumbnails []string
interval := videoInfo.Duration / (count + 1) // 平均分布
for i := 1; i <= count; i++ {
timeOffset := interval * i
outputPath := filepath.Join(outputDir, fmt.Sprintf("thumb_%d.jpg", i))
if err := ExtractCover(videoPath, outputPath, timeOffset); err != nil {
g.Log().Warningf(nil, "生成缩略图失败: %v", err)
continue
}
thumbnails = append(thumbnails, outputPath)
}
return thumbnails, nil
}
// ConvertVideo 视频格式转换
func ConvertVideo(inputPath, outputPath string, options ...string) error {
if !gfile.Exists(inputPath) {
return fmt.Errorf("输入视频文件不存在: %s", inputPath)
}
if !isFFmpegAvailable() {
return fmt.Errorf("ffmpeg未安装或不可用")
}
// 确保输出目录存在
outputDir := filepath.Dir(outputPath)
if !gfile.Exists(outputDir) {
if err := gfile.Mkdir(outputDir); err != nil {
return fmt.Errorf("创建输出目录失败: %v", err)
}
}
// 构建基础命令
args := []string{"-i", inputPath}
// 添加自定义选项
if len(options) > 0 {
args = append(args, options...)
} else {
// 默认转换选项
args = append(args,
"-c:v", "libx264", // 视频编码器
"-c:a", "aac", // 音频编码器
"-preset", "medium", // 编码预设
"-crf", "23", // 质量控制
)
}
args = append(args, "-y", outputPath) // 覆盖输出文件
cmd := exec.Command("ffmpeg", args...)
// 执行转换
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("视频转换失败: %v, 输出: %s", err, string(output))
}
g.Log().Infof(nil, "视频转换成功: %s -> %s", inputPath, outputPath)
return nil
}
// isFFmpegAvailable 检查ffmpeg是否可用
func isFFmpegAvailable() bool {
cmd := exec.Command("ffmpeg", "-version")
err := cmd.Run()
return err == nil
}
// GetVideoDuration 获取视频时长(秒)
func GetVideoDuration(videoPath string) (int, error) {
info, err := GetVideoInfo(videoPath)
if err != nil {
return 0, err
}
return info.Duration, nil
}
// ValidateVideoFile 验证视频文件
func ValidateVideoFile(filePath string) error {
if !gfile.Exists(filePath) {
return fmt.Errorf("文件不存在")
}
// 检查文件扩展名
ext := strings.ToLower(filepath.Ext(filePath))
allowedExts := []string{".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v"}
isValid := false
for _, allowedExt := range allowedExts {
if ext == allowedExt {
isValid = true
break
}
}
if !isValid {
return fmt.Errorf("不支持的视频格式: %s", ext)
}
// 检查文件大小限制为2GB
fileInfo, err := os.Stat(filePath)
if err != nil {
return fmt.Errorf("获取文件信息失败: %v", err)
}
maxSize := int64(2 * 1024 * 1024 * 1024) // 2GB
if fileInfo.Size() > maxSize {
return fmt.Errorf("视频文件过大最大支持2GB")
}
return nil
}
// CleanupTempFiles 清理临时文件
func CleanupTempFiles(dir string, maxAge time.Duration) error {
if !gfile.Exists(dir) {
return nil
}
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
now := time.Now()
for _, entry := range entries {
if entry.IsDir() {
continue
}
filePath := filepath.Join(dir, entry.Name())
fileInfo, err := entry.Info()
if err != nil {
continue
}
if now.Sub(fileInfo.ModTime()) > maxAge {
if err := os.Remove(filePath); err != nil {
g.Log().Warningf(nil, "删除临时文件失败: %s, 错误: %v", filePath, err)
} else {
g.Log().Infof(nil, "清理临时文件: %s", filePath)
}
}
}
return nil
}