From d0dbc4a3a724297b14de6b1391006712a0f473bc Mon Sep 17 00:00:00 2001 From: fish Date: Sat, 28 Mar 2026 20:00:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=8C=85=EF=BC=8C=E5=8C=85=E6=8B=AC=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E3=80=81=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E3=80=81?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E5=92=8C=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/shared/pkg/cache/redis.go | 120 +++++++++++++++++++++++ backend/shared/pkg/database/postgres.go | 79 +++++++++++++++ backend/shared/pkg/errors/errors.go | 96 ++++++++++++++++++ backend/shared/pkg/logger/logger.go | 38 +++++++ backend/shared/proto/common/common.proto | 55 +++++++++++ 5 files changed, 388 insertions(+) create mode 100644 backend/shared/pkg/cache/redis.go create mode 100644 backend/shared/pkg/database/postgres.go create mode 100644 backend/shared/pkg/errors/errors.go create mode 100644 backend/shared/pkg/logger/logger.go create mode 100644 backend/shared/proto/common/common.proto diff --git a/backend/shared/pkg/cache/redis.go b/backend/shared/pkg/cache/redis.go new file mode 100644 index 0000000..15dabf8 --- /dev/null +++ b/backend/shared/pkg/cache/redis.go @@ -0,0 +1,120 @@ +package cache + +import ( + "context" + "time" + + "backend/shared/pkg/logger" + + "github.com/go-redis/redis/v8" +) + +type RedisConfig struct { + Addr string + Password string + DB int +} + +type RedisCache struct { + Client *redis.Client + ctx context.Context +} + +// NewRedisCache 创建新的 Redis 缓存连接 +func NewRedisCache(config RedisConfig) (*RedisCache, error) { + client := redis.NewClient(&redis.Options{ + Addr: config.Addr, + Password: config.Password, + DB: config.DB, + }) + + ctx := context.Background() + + // 测试连接 + if _, err := client.Ping(ctx).Result(); err != nil { + return nil, err + } + + logger.Info("Connected to Redis cache") + + return &RedisCache{ + Client: client, + ctx: ctx, + }, nil +} + +// Close 关闭 Redis 连接 +func (r *RedisCache) Close() error { + if r.Client != nil { + return r.Client.Close() + } + return nil +} + +// Set 设置缓存 +func (r *RedisCache) Set(key string, value interface{}, expiration time.Duration) error { + return r.Client.Set(r.ctx, key, value, expiration).Err() +} + +// Get 获取缓存 +func (r *RedisCache) Get(key string) (string, error) { + return r.Client.Get(r.ctx, key).Result() +} + +// Delete 删除缓存 +func (r *RedisCache) Delete(key string) error { + return r.Client.Del(r.ctx, key).Err() +} + +// Exists 检查键是否存在 +func (r *RedisCache) Exists(key string) (bool, error) { + result, err := r.Client.Exists(r.ctx, key).Result() + if err != nil { + return false, err + } + return result > 0, nil +} + +// Expire 设置键的过期时间 +func (r *RedisCache) Expire(key string, expiration time.Duration) error { + return r.Client.Expire(r.ctx, key, expiration).Err() +} + +// TTL 获取键的剩余过期时间 +func (r *RedisCache) TTL(key string) (time.Duration, error) { + return r.Client.TTL(r.ctx, key).Result() +} + +// Incr 递增键的值 +func (r *RedisCache) Incr(key string) (int64, error) { + return r.Client.Incr(r.ctx, key).Result() +} + +// Decr 递减键的值 +func (r *RedisCache) Decr(key string) (int64, error) { + return r.Client.Decr(r.ctx, key).Result() +} + +// HashSet 设置哈希表字段 +func (r *RedisCache) HashSet(key, field string, value interface{}) error { + return r.Client.HSet(r.ctx, key, field, value).Err() +} + +// HashGet 获取哈希表字段 +func (r *RedisCache) HashGet(key, field string) (string, error) { + return r.Client.HGet(r.ctx, key, field).Result() +} + +// HashDelete 删除哈希表字段 +func (r *RedisCache) HashDelete(key string, fields ...string) error { + return r.Client.HDel(r.ctx, key, fields...).Err() +} + +// HashExists 检查哈希表字段是否存在 +func (r *RedisCache) HashExists(key, field string) (bool, error) { + result, err := r.Client.HExists(r.ctx, key, field).Result() + if err != nil { + return false, err + } + return result, nil +} diff --git a/backend/shared/pkg/database/postgres.go b/backend/shared/pkg/database/postgres.go new file mode 100644 index 0000000..11f75f4 --- /dev/null +++ b/backend/shared/pkg/database/postgres.go @@ -0,0 +1,79 @@ +package database + +import ( + "database/sql" + "fmt" + "time" + + "backend/shared/pkg/logger" + + _ "github.com/lib/pq" +) + +type PostgresConfig struct { + Host string + Port int + User string + Password string + DBName string + SSLMode string +} + +type PostgresDB struct { + DB *sql.DB +} + +// NewPostgresDB 创建新的 PostgreSQL 数据库连接 +func NewPostgresDB(config PostgresConfig) (*PostgresDB, error) { + connStr := fmt.Sprintf( + "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", + config.Host, config.Port, config.User, config.Password, config.DBName, config.SSLMode, + ) + + db, err := sql.Open("postgres", connStr) + if err != nil { + return nil, err + } + + // 设置连接池参数 + db.SetMaxOpenConns(25) + db.SetMaxIdleConns(5) + db.SetConnMaxLifetime(5 * time.Minute) + + // 测试连接 + if err := db.Ping(); err != nil { + return nil, err + } + + logger.Info("Connected to PostgreSQL database") + + return &PostgresDB{DB: db}, nil +} + +// Close 关闭数据库连接 +func (p *PostgresDB) Close() error { + if p.DB != nil { + return p.DB.Close() + } + return nil +} + +// Exec 执行 SQL 语句 +func (p *PostgresDB) Exec(query string, args ...interface{}) (sql.Result, error) { + return p.DB.Exec(query, args...) +} + +// Query 执行查询 +func (p *PostgresDB) Query(query string, args ...interface{}) (*sql.Rows, error) { + return p.DB.Query(query, args...) +} + +// QueryRow 执行单行查询 +func (p *PostgresDB) QueryRow(query string, args ...interface{}) *sql.Row { + return p.DB.QueryRow(query, args...) +} + +// Begin 开始事务 +func (p *PostgresDB) Begin() (*sql.Tx, error) { + return p.DB.Begin() +} diff --git a/backend/shared/pkg/errors/errors.go b/backend/shared/pkg/errors/errors.go new file mode 100644 index 0000000..5c65aa3 --- /dev/null +++ b/backend/shared/pkg/errors/errors.go @@ -0,0 +1,96 @@ +package errors + +import ( + "errors" + "fmt" +) + +// 错误类型定义 +var ( + ErrNotFound = errors.New("resource not found") + ErrInvalidInput = errors.New("invalid input") + ErrInternalServer = errors.New("internal server error") + ErrUnauthorized = errors.New("unauthorized") + ErrForbidden = errors.New("forbidden") + ErrConflict = errors.New("conflict") + ErrBadRequest = errors.New("bad request") + ErrTimeout = errors.New("timeout") + ErrServiceUnavailable = errors.New("service unavailable") +) + +// AppError 应用错误结构 +type AppError struct { + Err error + Message string + Code int +} + +func (e *AppError) Error() string { + if e.Message != "" { + return e.Message + } + return e.Err.Error() +} + +func (e *AppError) Unwrap() error { + return e.Err +} + +// NewAppError 创建新的应用错误 +func NewAppError(err error, message string, code int) *AppError { + return &AppError{ + Err: err, + Message: message, + Code: code, + } +} + +// WrapError 包装错误 +func WrapError(err error, message string) error { + return fmt.Errorf("%s: %w", message, err) +} + +// IsNotFound 检查是否为资源未找到错误 +func IsNotFound(err error) bool { + return errors.Is(err, ErrNotFound) +} + +// IsInvalidInput 检查是否为无效输入错误 +func IsInvalidInput(err error) bool { + return errors.Is(err, ErrInvalidInput) +} + +// IsInternalServer 检查是否为内部服务器错误 +func IsInternalServer(err error) bool { + return errors.Is(err, ErrInternalServer) +} + +// IsUnauthorized 检查是否为未授权错误 +func IsUnauthorized(err error) bool { + return errors.Is(err, ErrUnauthorized) +} + +// IsForbidden 检查是否为禁止访问错误 +func IsForbidden(err error) bool { + return errors.Is(err, ErrForbidden) +} + +// IsConflict 检查是否为冲突错误 +func IsConflict(err error) bool { + return errors.Is(err, ErrConflict) +} + +// IsBadRequest 检查是否为请求错误 +func IsBadRequest(err error) bool { + return errors.Is(err, ErrBadRequest) +} + +// IsTimeout 检查是否为超时错误 +func IsTimeout(err error) bool { + return errors.Is(err, ErrTimeout) +} + +// IsServiceUnavailable 检查是否为服务不可用错误 +func IsServiceUnavailable(err error) bool { + return errors.Is(err, ErrServiceUnavailable) +} diff --git a/backend/shared/pkg/logger/logger.go b/backend/shared/pkg/logger/logger.go new file mode 100644 index 0000000..cdc8941 --- /dev/null +++ b/backend/shared/pkg/logger/logger.go @@ -0,0 +1,38 @@ +package logger + +import ( + "log" + "os" +) + +type Logger struct { + infoLogger *log.Logger + errorLogger *log.Logger + debugLogger *log.Logger +} + +var instance *Logger + +func init() { + instance = NewLogger() +} + +func NewLogger() *Logger { + return &Logger{ + infoLogger: log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile), + errorLogger: log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile), + debugLogger: log.New(os.Stdout, "DEBUG: ", log.Ldate|log.Ltime|log.Lshortfile), + } +} + +func Info(format string, v ...interface{}) { + instance.infoLogger.Printf(format, v...) +} + +func Error(format string, v ...interface{}) { + instance.errorLogger.Printf(format, v...) +} + +func Debug(format string, v ...interface{}) { + instance.debugLogger.Printf(format, v...) +} diff --git a/backend/shared/proto/common/common.proto b/backend/shared/proto/common/common.proto new file mode 100644 index 0000000..6703c60 --- /dev/null +++ b/backend/shared/proto/common/common.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package common; + +// 通用响应结构 +message Response { + int32 code = 1; + string message = 2; + bytes data = 3; +} + +// 分页请求 +message PaginationRequest { + int32 page = 1; + int32 page_size = 2; +} + +// 分页响应 +message PaginationResponse { + int32 total = 1; + int32 page = 2; + int32 page_size = 3; + int32 total_pages = 4; +} + +// 错误信息 +message Error { + int32 code = 1; + string message = 2; + string details = 3; +} + +// 空请求 +message EmptyRequest { +} + +// 空响应 +message EmptyResponse { +} + +// ID 请求 +message IDRequest { + string id = 1; +} + +// ID 响应 +message IDResponse { + string id = 1; +} + +// 状态响应 +message StatusResponse { + bool success = 1; + string message = 2; +}