diff --git a/trading_assistant_api/Makefile b/trading_assistant_api/Makefile deleted file mode 100644 index 56d2f21..0000000 --- a/trading_assistant_api/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# 单独启动某一个业务(如user,make up-svc svc=user) -up-svc: - docker compose up -d $(svc) - -# 全局停止所有服务 -down: - docker compose down - -# 清理无用镜像/容器(团队开发环境,定期清理) -clean: - docker system prune -f - -# 初始化Go工作区(新成员拉取代码后,一键执行) -go-init: - go work init - go work use ./common - go work use ./services/user - go work use ./services/order - go work use ./services/pay - go mod tidy \ No newline at end of file diff --git a/trading_assistant_api/common/db/options.go b/trading_assistant_api/common/db/options.go deleted file mode 100644 index b3cd39d..0000000 --- a/trading_assistant_api/common/db/options.go +++ /dev/null @@ -1,112 +0,0 @@ -// common/db/options.go -package db - -import ( - "time" - - "gorm.io/gorm" -) - -// PostgresOptions PostgreSQL连接配置项 -// 包含基础连接信息+连接池配置,按需暴露,无冗余 -type PostgresOptions struct { - Host string // 数据库地址 - Port string // 数据库端口 - User string // 数据库账号 - Password string // 数据库密码 - DBName string // 数据库名 - SSLMode string // SSL模式(开发环境一般disable,生产可enable) - TimeZone string // 时区(如Asia/Shanghai) - // 连接池配置 - MaxOpenConns int // 最大打开连接数 - MaxIdleConns int // 最大空闲连接数 - ConnMaxLifetime time.Duration // 连接最大生命周期 - ConnMaxIdleTime time.Duration // 连接最大空闲时间 - // GORM配置 - LogLevel gorm.LogLevel // GORM日志级别(开发:Info,生产:Error) -} - -// PostgresOption 选项模式函数类型 -type PostgresOption func(*PostgresOptions) - -// 初始化默认配置,避免用户传参不全导致连接失败 -// 开发环境常用默认值,生产环境通过业务层传参覆盖 -func defaultPostgresOptions() *PostgresOptions { - return &PostgresOptions{ - Host: "postgres", // 默认匹配根目录Compose的服务名,开发环境直接用 - Port: "5432", - SSLMode: "disable", - TimeZone: "Asia/Shanghai", - MaxOpenConns: 20, - MaxIdleConns: 10, - ConnMaxLifetime: 30 * time.Minute, - ConnMaxIdleTime: 10 * time.Minute, - LogLevel: gorm.LogLevelInfo, // 开发环境默认打印Info日志 - } -} - -// 以下为配置项的设置函数,用户可通过链式调用配置 -// 示例:db.WithHost("192.168.1.100").WithUser("prod_user") - -// WithHost 设置数据库地址 -func WithHost(host string) PostgresOption { - return func(o *PostgresOptions) { - o.Host = host - } -} - -// WithPort 设置数据库端口 -func WithPort(port string) PostgresOption { - return func(o *PostgresOptions) { - o.Port = port - } -} - -// WithUser 设置数据库账号 -func WithUser(user string) PostgresOption { - return func(o *PostgresOptions) { - o.User = user - } -} - -// WithPassword 设置数据库密码 -func WithPassword(pwd string) PostgresOption { - return func(o *PostgresOptions) { - o.Password = pwd - } -} - -// WithDBName 设置数据库名 -func WithDBName(dbName string) PostgresOption { - return func(o *PostgresOptions) { - o.DBName = dbName - } -} - -// WithSSLMode 设置SSL模式 -func WithSSLMode(mode string) PostgresOption { - return func(o *PostgresOptions) { - o.SSLMode = mode - } -} - -// WithTimeZone 设置时区 -func WithTimeZone(tz string) PostgresOption { - return func(o *PostgresOptions) { - o.TimeZone = tz - } -} - -// WithMaxOpenConns 设置最大打开连接数 -func WithMaxOpenConns(num int) PostgresOption { - return func(o *PostgresOptions) { - o.MaxOpenConns = num - } -} - -// WithLogLevel 设置GORM日志级别 -func WithLogLevel(level gorm.LogLevel) PostgresOption { - return func(o *PostgresOptions) { - o.LogLevel = level - } -} \ No newline at end of file diff --git a/trading_assistant_api/common/db/postgres.go b/trading_assistant_api/common/db/postgres.go deleted file mode 100644 index b6ca194..0000000 --- a/trading_assistant_api/common/db/postgres.go +++ /dev/null @@ -1,136 +0,0 @@ -// common/db/postgres.go -package db - -import ( - "context" - "database/sql" - "fmt" - "sync" - - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" -) - -// 全局单例连接,避免重复创建连接(连接池全局复用) -var ( - pgInstance *gorm.DB - pgOnce sync.Once - pgErr error -) - -// InitPostgres 初始化PostgreSQL连接(单例模式,仅执行一次) -// 入参:选项模式的配置函数,用户可灵活配置 -// 返回:原生*gorm.DB + 错误,业务层可直接使用GORM所有方法 -func InitPostgres(opts ...PostgresOption) (*gorm.DB, error) { - pgOnce.Do(func() { - // 加载默认配置 + 覆盖用户自定义配置 - options := defaultPostgresOptions() - for _, opt := range opts { - opt(options) - } - - // 校验必传配置(账号、密码、数据库名不能为空) - if options.User == "" || options.Password == "" || options.DBName == "" { - pgErr = fmt.Errorf("postgres config error: user/password/dbname can not be empty") - return - } - - // 拼接PostgreSQL DSN(数据源名称),GORM官方规范 - dsn := fmt.Sprintf( - "host=%s port=%s user=%s password=%s dbname=%s sslmode=%s TimeZone=%s", - options.Host, - options.Port, - options.User, - options.Password, - options.DBName, - options.SSLMode, - options.TimeZone, - ) - - // 配置GORM日志(按传入的日志级别,生产环境可关闭) - gormConfig := &gorm.Config{ - Logger: logger.Default.LogMode(options.LogLevel), - } - - // 建立GORM连接 - pgInstance, pgErr = gorm.Open(postgres.Open(dsn), gormConfig) - if pgErr != nil { - pgErr = fmt.Errorf("postgres connect failed: %w", pgErr) - return - } - - // 获取底层*sql.DB,配置连接池(关键:避免连接泄漏,提升性能) - sqlDB, err := pgInstance.DB() - if err != nil { - pgErr = fmt.Errorf("get postgres sql.DB failed: %w", err) - return - } - - // 设置连接池参数 - sqlDB.SetMaxOpenConns(options.MaxOpenConns) - sqlDB.SetMaxIdleConns(options.MaxIdleConns) - sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime) - sqlDB.SetConnMaxIdleTime(options.ConnMaxIdleTime) - - // 测试连接(ping一下,确保连接有效) - if err := sqlDB.PingContext(context.Background()); err != nil { - pgErr = fmt.Errorf("postgres ping failed: %w", err) - pgInstance = nil // 连接失败,置空实例 - return - } - }) - - // 单例执行完成后,返回实例和错误 - return pgInstance, pgErr -} - -// GetPostgres 获取全局PostgreSQL单例连接 -// 业务层初始化后,可通过此方法直接获取连接,无需重复调用InitPostgres -func GetPostgres() (*gorm.DB, error) { - if pgInstance == nil { - return nil, fmt.Errorf("postgres not initialized, please call InitPostgres first") - } - return pgInstance, nil -} - -// ClosePostgres 关闭PostgreSQL连接(应用退出时调用,释放资源) -// 一般在main函数的defer中调用,如:defer db.ClosePostgres() -func ClosePostgres() error { - if pgInstance == nil { - return fmt.Errorf("postgres not initialized") - } - - // 获取底层*sql.DB,执行关闭 - sqlDB, err := pgInstance.DB() - if err != nil { - return fmt.Errorf("get postgres sql.DB failed: %w", err) - } - - if err := sqlDB.Close(); err != nil { - return fmt.Errorf("close postgres failed: %w", err) - } - - // 关闭后重置单例,避免重复关闭 - pgOnce = sync.Once{} - pgInstance = nil - pgErr = nil - - return nil -} - -// PingPostgres 测试PostgreSQL连接是否有效 -// 业务层可定时调用(如健康检查),确保连接未断开 -func PingPostgres() error { - db, err := GetPostgres() - if err != nil { - return err - } - - sqlDB, err := db.DB() - if err != nil { - return fmt.Errorf("get postgres sql.DB failed: %w", err) - } - - return sqlDB.PingContext(context.Background()) -} \ No newline at end of file diff --git a/trading_assistant_api/common/go.mod b/trading_assistant_api/common/go.mod deleted file mode 100644 index d4384e7..0000000 --- a/trading_assistant_api/common/go.mod +++ /dev/null @@ -1,27 +0,0 @@ -module common - -go 1.25.7 - -require ( - github.com/redis/go-redis/v9 v9.3.0 - go.uber.org/zap v1.26.0 - gopkg.in/natefinch/lumberjack.v2 v2.2.1 - gorm.io/driver/postgres v1.6.0 - gorm.io/gorm v1.31.1 -) - -require ( - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/stretchr/testify v1.8.3 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/text v0.21.0 // indirect -) diff --git a/trading_assistant_api/common/go.sum b/trading_assistant_api/common/go.sum deleted file mode 100644 index 56b2248..0000000 --- a/trading_assistant_api/common/go.sum +++ /dev/null @@ -1,54 +0,0 @@ -github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= -github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= -gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= -gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/trading_assistant_api/common/logger/logger.go b/trading_assistant_api/common/logger/logger.go deleted file mode 100644 index c5e3750..0000000 --- a/trading_assistant_api/common/logger/logger.go +++ /dev/null @@ -1,240 +0,0 @@ -// common/logger/logger.go -package logger - -import ( - "os" - "sync" - "time" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "gopkg.in/natefinch/lumberjack.v2" -) - -// 全局单例日志实例 -var ( - _logger *zap.Logger - once sync.Once -) - -// LoggerOptions 日志配置项 -type LoggerOptions struct { - Level string // 日志级别:debug/info/warn/error/panic/fatal - Format string // 输出格式:console(控制台)/json(JSON) - OutputPath string // 文件输出路径(如./logs/app.log) - MaxSize int // 单个日志文件最大大小(MB) - MaxBackups int // 最大保留日志文件数 - MaxAge int // 最大保留天数 - Compress bool // 是否压缩日志文件 - ShowLine bool // 是否显示代码行号 - ConsoleColor bool // 控制台是否显示彩色(开发环境用) - ServiceName string // 服务名(多业务时区分日志归属,如user/order) - StacktraceKey string // 堆栈信息键名 - CallerSkip int // 调用栈跳过数(适配封装层,正确显示业务代码行号) - FlushInterval time.Duration // 日志刷盘间隔 -} - -// LoggerOption 选项模式函数类型 -type LoggerOption func(*LoggerOptions) - -// defaultLoggerOptions 初始化默认配置 -// 开发环境默认:控制台彩色日志、info级别、显示行号 -func defaultLoggerOptions() *LoggerOptions { - return &LoggerOptions{ - Level: "info", - Format: "console", - OutputPath: "./logs/app.log", - MaxSize: 100, // 单个文件100MB - MaxBackups: 10, // 保留10个备份 - MaxAge: 7, // 保留7天 - Compress: true, // 压缩备份文件 - ShowLine: true, // 显示行号 - ConsoleColor: true, // 控制台彩色 - ServiceName: "trading_assistant", // 默认服务名 - StacktraceKey: "stacktrace", - CallerSkip: 1, // 跳过当前封装层,正确显示业务代码行号 - FlushInterval: 3 * time.Second, - } -} - -// 以下为配置项设置函数,支持链式调用 -func WithLevel(level string) LoggerOption { - return func(o *LoggerOptions) { o.Level = level } -} - -func WithFormat(format string) LoggerOption { - return func(o *LoggerOptions) { o.Format = format } -} - -func WithOutputPath(path string) LoggerOption { - return func(o *LoggerOptions) { o.OutputPath = path } -} - -func WithServiceName(name string) LoggerOption { - return func(o *LoggerOptions) { o.ServiceName = name } -} - -func WithShowLine(show bool) LoggerOption { - return func(o *LoggerOptions) { o.ShowLine = show } -} - -func WithConsoleColor(color bool) LoggerOption { - return func(o *LoggerOptions) { o.ConsoleColor = color } -} - -// getZapLevel 转换日志级别为zapcore.Level -func getZapLevel(level string) zapcore.Level { - switch level { - case "debug": - return zapcore.DebugLevel - case "warn": - return zapcore.WarnLevel - case "error": - return zapcore.ErrorLevel - case "panic": - return zapcore.PanicLevel - case "fatal": - return zapcore.FatalLevel - default: - return zapcore.InfoLevel // 默认info级别 - } -} - -// getEncoder 获取日志编码器(console/json) -func getEncoder(opts *LoggerOptions) zapcore.Encoder { - // 日志基础配置:时间格式、服务名、调用栈等 - encoderConfig := zapcore.EncoderConfig{ - TimeKey: "time", - LevelKey: "level", - NameKey: "service", - CallerKey: "caller", - MessageKey: "msg", - StacktraceKey: opts.StacktraceKey, - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.CapitalLevelEncoder, // 级别大写(INFO/ERROR) - EncodeTime: zapcore.RFC3339TimeEncoder, // 时间格式RFC3339 - EncodeDuration: zapcore.SecondsDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, // 调用者格式:文件:行号 - } - - // 开发环境:控制台彩色编码器 - if opts.Format == "console" && opts.ConsoleColor { - return zapcore.NewConsoleEncoder(encoderConfig) - } - - // 生产环境:JSON编码器(便于日志收集分析,如ELK) - return zapcore.NewJSONEncoder(encoderConfig) -} - -// getWriteSyncer 获取日志写入器(文件+控制台) -// 同时输出到文件和控制台,文件自动切割 -func getWriteSyncer(opts *LoggerOptions) zapcore.WriteSyncer { - // 日志文件切割配置(基于lumberjack) - lumberjackLogger := &lumberjack.Logger{ - Filename: opts.OutputPath, - MaxSize: opts.MaxSize, - MaxBackups: opts.MaxBackups, - MaxAge: opts.MaxAge, - Compress: opts.Compress, - } - - // 同时输出到文件和控制台 - return zapcore.NewMultiWriteSyncer( - zapcore.AddSync(os.Stdout), - zapcore.AddSync(lumberjackLogger), - ) -} - -// InitLogger 初始化全局单例日志实例 -func InitLogger(opts ...LoggerOption) *zap.Logger { - once.Do(func() { - // 加载默认配置 + 覆盖用户自定义配置 - options := defaultLoggerOptions() - for _, opt := range opts { - opt(options) - } - - // 1. 设置日志级别 - level := getZapLevel(options.Level) - core := zapcore.NewCore( - getEncoder(options), // 编码器 - getWriteSyncer(options), // 写入器 - level, // 日志级别 - ) - - // 2. 构建日志实例配置:是否显示调用者、堆栈信息 - zapOpts := []zap.Option{zap.AddCallerSkip(options.CallerSkip)} - if options.ShowLine { - zapOpts = append(zapOpts, zap.AddCaller()) // 显示调用者(文件:行号) - } - // 错误级别及以上显示堆栈信息 - zapOpts = append(zapOpts, zap.AddStacktrace(zapcore.ErrorLevel)) - // 设置服务名 - zapOpts = append(zapOpts, zap.Fields(zap.String("service", options.ServiceName))) - - // 3. 创建日志实例 - _logger = zap.New(core, zapOpts...) - - // 4. 定时刷盘(避免日志驻留内存) - go func() { - ticker := time.NewTicker(options.FlushInterval) - defer ticker.Stop() - for range ticker.C { - _ = _logger.Sync() - } - }() - - // 5. 应用退出时刷盘 - os.Setenv("ZAP_FLUSH_ON_EXIT", "true") - }) - - return _logger -} - -// GetLogger 获取全局单例日志实例 -func GetLogger() *zap.Logger { - if _logger == nil { - // 未初始化时,返回默认控制台日志(兜底) - return InitLogger() - } - return _logger -} - -// --------------- 轻量封装日志方法 --------------- -// 简化业务层调用,无需每次获取logger实例 -// 复杂日志(如带字段)可直接使用GetLogger()获取原生实例 - -// Debug 调试日志 -func Debug(msg string, fields ...zap.Field) { - GetLogger().Debug(msg, fields...) -} - -// Info 信息日志 -func Info(msg string, fields ...zap.Field) { - GetLogger().Info(msg, fields...) -} - -// Warn 警告日志 -func Warn(msg string, fields ...zap.Field) { - GetLogger().Warn(msg, fields...) -} - -// Error 错误日志(带堆栈) -func Error(msg string, fields ...zap.Field) { - GetLogger().Error(msg, fields...) -} - -// Panic 恐慌日志(打印后触发panic) -func Panic(msg string, fields ...zap.Field) { - GetLogger().Panic(msg, fields...) -} - -// Fatal 致命日志(打印后退出程序) -func Fatal(msg string, fields ...zap.Field) { - GetLogger().Fatal(msg, fields...) -} - -// Sync 手动刷盘(如重要操作后) -func Sync() error { - return GetLogger().Sync() -} \ No newline at end of file diff --git a/trading_assistant_api/common/middleware/cors.go b/trading_assistant_api/common/middleware/cors.go deleted file mode 100644 index 0e754a7..0000000 --- a/trading_assistant_api/common/middleware/cors.go +++ /dev/null @@ -1,152 +0,0 @@ -// common/middleware/cors.go -package middleware - -import ( - "net/http" - "strings" - "time" -) - -// CORSOptions 跨域配置项 -// 支持自定义允许的源、方法、头、凭证、缓存时间,按需扩展 -type CORSOptions struct { - AllowOrigins []string // 允许的跨域源,如["http://localhost:8080", "https://xxx.com"],*表示允许所有 - AllowMethods []string // 允许的HTTP方法,默认GET/POST/PUT/DELETE/PATCH/OPTIONS - AllowHeaders []string // 允许的请求头,*表示允许所有 - AllowCredentials bool // 是否允许携带凭证(Cookie/Token),前后端联调必备 - ExposeHeaders []string // 允许前端获取的响应头 - MaxAge time.Duration // 预检请求(OPTIONS)的缓存时间,避免重复预检 -} - -// CORSOption 选项模式函数类型,用于灵活配置跨域参数 -type CORSOption func(*CORSOptions) - -// defaultCORSOptions 初始化默认跨域配置 -// 开发环境默认允许所有源、常用方法,生产环境可通过配置覆盖 -func defaultCORSOptions() *CORSOptions { - return &CORSOptions{ - AllowOrigins: []string{"*"}, // 开发环境默认允许所有源 - AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions}, - AllowHeaders: []string{"*"}, // 允许所有请求头 - AllowCredentials: true, // 允许携带凭证 - ExposeHeaders: []string{"Content-Length", "Content-Type", "X-Token"}, - MaxAge: 12 * time.Hour, // 预检请求缓存12小时 - } -} - -// 以下为配置项设置函数,支持链式调用 -// WithAllowOrigins 设置允许的跨域源,示例:WithAllowOrigins("http://localhost:3000", "https://app.com") -func WithAllowOrigins(origins ...string) CORSOption { - return func(o *CORSOptions) { o.AllowOrigins = origins } -} - -// WithAllowCredentials 设置是否允许携带凭证(Cookie/Token) -func WithAllowCredentials(allow bool) CORSOption { - return func(o *CORSOptions) { o.AllowCredentials = allow } -} - -// WithMaxAge 设置预检请求缓存时间 -func WithMaxAge(age time.Duration) CORSOption { - return func(o *CORSOptions) { o.MaxAge = age } -} - -// WithAllowHeaders 设置允许的请求头 -func WithAllowHeaders(headers ...string) CORSOption { - return func(o *CORSOptions) { o.AllowHeaders = headers } -} - -// CORS 跨域中间件核心方法 -// 适配Go原生http.Handler,可直接用于Gin/Echo等框架(兼容框架中间件规范) -func CORS(opts ...CORSOption) func(http.Handler) http.Handler { - // 加载默认配置 + 覆盖用户自定义配置 - options := defaultCORSOptions() - for _, opt := range opts { - opt(options) - } - - // 处理允许的源:拼接为字符串 - allowOrigins := strings.Join(options.AllowOrigins, ", ") - // 处理允许的方法:拼接为字符串 - allowMethods := strings.Join(options.AllowMethods, ", ") - // 处理允许的请求头:拼接为字符串 - allowHeaders := strings.Join(options.AllowHeaders, ", ") - // 处理允许暴露的响应头:拼接为字符串 - exposeHeaders := strings.Join(options.ExposeHeaders, ", ") - - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // 1. 获取前端请求的Origin(跨域核心) - origin := r.Header.Get("Origin") - // 若配置了*,则直接使用请求的Origin;否则使用配置的源(生产环境建议精准配置) - if len(options.AllowOrigins) > 0 && options.AllowOrigins[0] == "*" { - w.Header().Set("Access-Control-Allow-Origin", origin) - } else { - w.Header().Set("Access-Control-Allow-Origin", allowOrigins) - } - - // 2. 设置跨域核心响应头 - w.Header().Set("Access-Control-Allow-Methods", allowMethods) - w.Header().Set("Access-Control-Allow-Headers", allowHeaders) - w.Header().Set("Access-Control-Expose-Headers", exposeHeaders) - w.Header().Set("Access-Control-Max-Age", string(rune(options.MaxAge.Seconds()))) - // 允许携带凭证时,不能将Allow-Origin设为*,需动态匹配请求Origin(已做处理) - if options.AllowCredentials { - w.Header().Set("Access-Control-Allow-Credentials", "true") - } - - // 3. 处理预检请求(OPTIONS):直接返回204,无需执行后续业务逻辑 - if r.Method == http.MethodOptions { - w.WriteHeader(http.StatusNoContent) - return - } - - // 4. 非预检请求,执行后续业务逻辑 - next.ServeHTTP(w, r) - }) - } -} - -// ---- 兼容Gin框架的快捷中间件(可选)---- -// 若团队使用Gin框架开发,可直接使用此方法,无需额外转换,提升开发效率 -// 需提前安装Gin:go get github.com/gin-gonic/gin -import ( - "github.com/gin-gonic/gin" -) - -// CorsGin 适配Gin框架的跨域中间件 -func CorsGin(opts ...CORSOption) gin.HandlerFunc { - // 复用原生CORS配置逻辑 - options := defaultCORSOptions() - for _, opt := range opts { - opt(options) - } - allowOrigins := strings.Join(options.AllowOrigins, ", ") - allowMethods := strings.Join(options.Methods, ", ") - allowHeaders := strings.Join(options.AllowHeaders, ", ") - exposeHeaders := strings.Join(options.ExposeHeaders, ", ") - - return func(c *gin.Context) { - origin := c.Request.Header.Get("Origin") - if options.AllowOrigins[0] == "*" { - c.Writer.Header().Set("Access-Control-Allow-Origin", origin) - } else { - c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigins) - } - c.Writer.Header().Set("Access-Control-Allow-Methods", allowMethods) - c.Writer.Header().Set("Access-Control-Allow-Headers", allowHeaders) - c.Writer.Header().Set("Access-Control-Expose-Headers", exposeHeaders) - c.Writer.Header().Set("Access-Control-Max-Age", string(rune(options.MaxAge.Seconds()))) - if options.AllowCredentials { - c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") - } - - // 处理预检请求 - if c.Request.Method == http.MethodOptions { - c.AbortWithStatus(http.StatusNoContent) - return - } - - // 继续执行后续中间件/业务逻辑 - c.Next() - } -} \ No newline at end of file diff --git a/trading_assistant_api/common/redis/redis.go b/trading_assistant_api/common/redis/redis.go deleted file mode 100644 index 28e1300..0000000 --- a/trading_assistant_api/common/redis/redis.go +++ /dev/null @@ -1,220 +0,0 @@ -// common/redis/redis.go -package redis - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/redis/go-redis/v9" -) - -// 全局单例Redis客户端(连接池) -var ( - clientInstance *redis.Client - once sync.Once - initErr error -) - -// RedisOptions Redis连接配置项 -// 包含基础连接信息+连接池配置,按需配置 -type RedisOptions struct { - Host string // Redis地址 - Port string // Redis端口 - Password string // Redis密码 - DB int // 数据库索引(建议每个业务一个DB,如user=0, order=1) - PoolSize int // 连接池最大连接数 - MinIdleConns int // 连接池最小空闲连接数 - ConnTimeout time.Duration // 连接超时时间 - ReadTimeout time.Duration // 读超时时间 - WriteTimeout time.Duration // 写超时时间 - IdleTimeout time.Duration // 连接空闲超时时间 -} - -// RedisOption 选项模式函数类型 -type RedisOption func(*RedisOptions) - -// defaultRedisOptions 初始化默认配置 -// 开发环境直接对接根目录docker-compose的redis服务,无需额外配置 -func defaultRedisOptions() *RedisOptions { - return &RedisOptions{ - Host: "redis", // 匹配Compose服务名 - Port: "6379", // 默认端口 - Password: "", // 开发环境默认无密码 - DB: 0, // 默认DB0 - PoolSize: 50, // 连接池最大连接数 - MinIdleConns: 10, // 最小空闲连接,避免频繁创建连接 - ConnTimeout: 5 * time.Second, - ReadTimeout: 3 * time.Second, - WriteTimeout: 3 * time.Second, - IdleTimeout: 30 * time.Minute, - } -} - -// 以下为配置项设置函数,支持链式调用 -func WithHost(host string) RedisOption { - return func(o *RedisOptions) { o.Host = host } -} - -func WithPort(port string) RedisOption { - return func(o *RedisOptions) { o.Port = port } -} - -func WithPassword(pwd string) RedisOption { - return func(o *RedisOptions) { o.Password = pwd } -} - -func WithDB(db int) RedisOption { - return func(o *RedisOptions) { o.DB = db } -} - -func WithPoolSize(size int) RedisOption { - return func(o *RedisOptions) { o.PoolSize = size } -} - -func WithLogLevel(level redis.LogLevel) RedisOption { - return func(o *RedisOptions) { - // 兼容日志级别配置,需配合客户端初始化 - } -} - -// InitRedis 初始化Redis单例客户端(连接池) -// 选项模式配置,仅执行一次,全局复用连接池 -func InitRedis(opts ...RedisOption) (*redis.Client, error) { - once.Do(func() { - // 加载默认配置 + 覆盖用户自定义配置 - options := defaultRedisOptions() - for _, opt := range opts { - opt(options) - } - - // 构建Redis客户端配置 - rdbOpts := &redis.Options{ - Addr: fmt.Sprintf("%s:%s", options.Host, options.Port), - Password: options.Password, - DB: options.DB, - PoolSize: options.PoolSize, - MinIdleConns: options.MinIdleConns, - DialTimeout: options.ConnTimeout, - ReadTimeout: options.ReadTimeout, - WriteTimeout: options.WriteTimeout, - IdleTimeout: options.IdleTimeout, - } - - // 创建客户端实例 - clientInstance = redis.NewClient(rdbOpts) - - // 测试连接,确保连接有效 - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if _, err := clientInstance.Ping(ctx).Result(); err != nil { - initErr = fmt.Errorf("redis ping failed: %w", err) - clientInstance = nil // 连接失败置空实例 - return - } - - initErr = nil - }) - - return clientInstance, initErr -} - -// GetRedis 获取全局Redis单例客户端 -// 业务层初始化后,直接调用获取,无需重复初始化 -func GetRedis() (*redis.Client, error) { - if clientInstance == nil { - return nil, errors.New("redis not initialized, please call InitRedis first") - } - return clientInstance, nil -} - -// CloseRedis 关闭Redis连接池(应用退出时调用,优雅释放资源) -func CloseRedis() error { - if clientInstance == nil { - return errors.New("redis not initialized") - } - - if err := clientInstance.Close(); err != nil { - return fmt.Errorf("close redis failed: %w", err) - } - - // 重置单例,避免重复关闭 - once = sync.Once{} - clientInstance = nil - initErr = errors.New("redis client closed") - return nil -} - -// --------------- 基础操作封装 --------------- -// 轻量封装高频基础操作,简化业务层调用(无需关注context和错误处理基础逻辑) -// 复杂操作(如哈希、列表、事务)直接使用原生*redis.Client即可 - -// Ctx 全局默认context,业务层可自定义传入 -var Ctx = context.Background() - -// Set 封装Set操作,带过期时间 -// key: 键, val: 值, expire: 过期时间(0表示永不过期) -func Set(key string, val interface{}, expire time.Duration) error { - rdb, err := GetRedis() - if err != nil { - return err - } - return rdb.Set(Ctx, key, val, expire).Err() -} - -// Get 封装Get操作,返回字符串值 -func Get(key string) (string, error) { - rdb, err := GetRedis() - if err != nil { - return "", err - } - return rdb.Get(Ctx, key).Result() -} - -// Del 封装Del操作,删除一个或多个键 -func Del(keys ...string) (int64, error) { - rdb, err := GetRedis() - if err != nil { - return 0, err - } - return rdb.Del(Ctx, keys...).Result() -} - -// Exists 检查键是否存在 -func Exists(key string) (bool, error) { - rdb, err := GetRedis() - if err != nil { - return false, err - } - res, err := rdb.Exists(Ctx, key).Result() - return res > 0, err -} - -// Expire 为键设置过期时间 -func Expire(key string, expire time.Duration) error { - rdb, err := GetRedis() - if err != nil { - return err - } - return rdb.Expire(Ctx, key, expire).Err() -} - -// HSet 封装哈希Set操作,设置单个字段 -func HSet(key, field string, val interface{}) error { - rdb, err := GetRedis() - if err != nil { - return err - } - return rdb.HSet(Ctx, key, field, val).Err() -} - -// HGet 封装哈希Get操作,获取单个字段值 -func HGet(key, field string) (string, error) { - rdb, err := GetRedis() - if err != nil { - return "", err - } - return rdb.HGet(Ctx, key, field).Result() -} \ No newline at end of file diff --git a/trading_assistant_api/common/utils/common.go b/trading_assistant_api/common/utils/common.go deleted file mode 100644 index 816483f..0000000 --- a/trading_assistant_api/common/utils/common.go +++ /dev/null @@ -1,276 +0,0 @@ -// common/common.go -package common - -import ( - "crypto/rand" - "encoding/base64" - "errors" - "fmt" - "math/big" - "os" - "reflect" - "strconv" - "strings" - "time" -) - -// ---------------------- 环境变量操作 ---------------------- -// 项目中最常用的通用能力,适配Docker容器化(环境变量传配置) -// GetEnv 读取环境变量,若不存在则返回默认值 -func GetEnv(key, defVal string) string { - val := os.Getenv(key) - if val == "" { - return defVal - } - return val -} - -// GetEnvInt 读取环境变量并转换为int,转换失败/不存在返回默认值 -func GetEnvInt(key string, defVal int) int { - val := os.Getenv(key) - if val == "" { - return defVal - } - num, err := strconv.Atoi(val) - if err != nil { - return defVal - } - return num -} - -// GetEnvBool 读取环境变量并转换为bool(支持1/0、true/false、on/off),失败/不存在返回默认值 -func GetEnvBool(key string, defVal bool) bool { - val := strings.ToLower(os.Getenv(key)) - switch val { - case "1", "true", "on": - return true - case "0", "false", "off": - return false - default: - return defVal - } -} - -// ---------------------- 类型转换 ---------------------- -// 日常开发中频繁的类型转换,封装后避免重复写err判断 -// StrToInt 字符串转int,失败返回0和错误 -func StrToInt(s string) (int, error) { - return strconv.Atoi(s) -} - -// StrToInt64 字符串转int64,失败返回0和错误 -func StrToInt64(s string) (int64, error) { - return strconv.ParseInt(s, 10, 64) -} - -// IntToStr int转字符串 -func IntToStr(i int) string { - return strconv.Itoa(i) -} - -// Int64ToStr int64转字符串 -func Int64ToStr(i int64) string { - return strconv.FormatInt(i, 10) -} - -// StrToFloat64 字符串转float64,失败返回0和错误 -func StrToFloat64(s string) (float64, error) { - return strconv.ParseFloat(s, 64) -} - -// Float64ToStr float64转字符串,保留n位小数 -func Float64ToStr(f float64, n int) string { - format := fmt.Sprintf("%%.%df", n) - return fmt.Sprintf(format, f) -} - -// ---------------------- 时间通用处理 ---------------------- -// 统一项目时间格式,避免各业务自定义格式导致的混乱 -const ( - // TimeFormatYmdHms 常规时间格式:2006-01-02 15:04:05(Go诞生时间,固定模板) - TimeFormatYmdHms = "2006-01-02 15:04:05" - // TimeFormatYmd 日期格式:2006-01-02 - TimeFormatYmd = "2006-01-02" - // TimeFormatHms 时间格式:15:04:05 - TimeFormatHms = "15:04:05" - // TimeFormatYmdHmsS 带毫秒的时间格式:2006-01-02 15:04:05.000 - TimeFormatYmdHmsS = "2006-01-02 15:04:05.000" -) - -// TimeToStr 时间转字符串,指定格式(不传则用默认YmdHms) -func TimeToStr(t time.Time, format ...string) string { - ft := TimeFormatYmdHms - if len(format) > 0 && format[0] != "" { - ft = format[0] - } - // 转换为本地时区(避免UTC时间偏移) - return t.In(time.Local).Format(ft) -} - -// NowToStr 当前时间转字符串,指定格式(不传则用默认YmdHms) -func NowToStr(format ...string) string { - return TimeToStr(time.Now(), format...) -} - -// StrToTime 字符串转时间,指定格式(不传则用默认YmdHms),失败返回零值和错误 -func StrToTime(s string, format ...string) (time.Time, error) { - ft := TimeFormatYmdHms - if len(format) > 0 && format[0] != "" { - ft = format[0] - } - return time.ParseInLocation(ft, s, time.Local) -} - -// GetDayStart 获取某天的开始时间(如2026-02-06 00:00:00) -func GetDayStart(t time.Time) time.Time { - year, month, day := t.In(time.Local).Date() - return time.Date(year, month, day, 0, 0, 0, 0, time.Local) -} - -// GetDayEnd 获取某天的结束时间(如2026-02-06 23:59:59.999999999) -func GetDayEnd(t time.Time) time.Time { - year, month, day := t.In(time.Local).Date() - return time.Date(year, month, day, 23, 59, 59, 999999999, time.Local) -} - -// ---------------------- 空值判断 ---------------------- -// 通用的空值检查,支持所有基础类型和引用类型,避免业务层重复写if判断 -// IsEmpty 判断值是否为空(0/""//nil/空切片/空map等) -func IsEmpty(v interface{}) bool { - if v == nil { - return true - } - val := reflect.ValueOf(v) - switch val.Kind() { - case reflect.String: - return val.String() == "" - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return val.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return val.Uint() == 0 - case reflect.Float32, reflect.Float64: - return val.Float() == 0 - case reflect.Bool: - return !val.Bool() - case reflect.Slice, reflect.Array: - return val.Len() == 0 - case reflect.Map, reflect.Chan: - return val.Len() == 0 || val.IsNil() - case reflect.Ptr, reflect.Interface: - return val.IsNil() - default: - return false - } -} - -// IsNotEmpty 判断值是否非空,与IsEmpty相反 -func IsNotEmpty(v interface{}) bool { - return !IsEmpty(v) -} - -// ---------------------- 字符串通用处理 ---------------------- -// 高频的字符串操作,封装后简化调用 -// StrTrim 去除字符串两端的空格/制表符/换行符 -func StrTrim(s string) string { - return strings.TrimSpace(s) -} - -// StrToLower 字符串转小写 -func StrToLower(s string) string { - return strings.ToLower(s) -} - -// StrToUpper 字符串转大写 -func StrToUpper(s string) string { - return strings.ToUpper(s) -} - -// StrIsIn 判断字符串是否在指定的切片中(忽略大小写) -func StrIsIn(s string, list []string) bool { - s = StrToLower(s) - for _, v := range list { - if StrToLower(v) == s { - return true - } - } - return false -} - -// StrJoin 拼接切片为字符串,指定分隔符 -func StrJoin(list []string, sep string) string { - return strings.Join(list, sep) -} - -// ---------------------- 随机数/随机字符串 ---------------------- -// 生成随机数、随机字符串(用于生成验证码、随机密码、唯一标识等) -// RandInt 生成[min, max]范围内的随机整数,失败返回0和错误 -func RandInt(min, max int) (int, error) { - if min > max { - return 0, errors.New("min is greater than max") - } - // 基于crypto/rand生成安全随机数(比math/rand更安全,适合生产环境) - num, err := rand.Int(rand.Reader, big.NewInt(int64(max-min+1))) - if err != nil { - return 0, err - } - return min + int(num.Int64()), nil -} - -// RandStr 生成指定长度的随机字符串(数字+大小写字母),失败返回""和错误 -func RandStr(length int) (string, error) { - if length <= 0 { - return "", errors.New("length must be greater than 0") - } - // 随机字符串源(可根据需求添加/移除字符,如去掉大写字母) - chars := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - charsLen := big.NewInt(int64(len(chars))) - result := make([]byte, length) - for i := 0; i < length; i++ { - // 逐个生成随机字符索引 - idx, err := rand.Int(rand.Reader, charsLen) - if err != nil { - return "", err - } - result[i] = chars[idx.Int64()] - } - return string(result), nil -} - -// RandNumStr 生成指定长度的纯数字随机字符串,失败返回""和错误 -func RandNumStr(length int) (string, error) { - if length <= 0 { - return "", errors.New("length must be greater than 0") - } - nums := "0123456789" - numsLen := big.NewInt(int64(len(nums))) - result := make([]byte, length) - for i := 0; i < length; i++ { - idx, err := rand.Int(rand.Reader, numsLen) - if err != nil { - return "", err - } - result[i] = nums[idx.Int64()] - } - return string(result), nil -} - -// ---------------------- 其他通用工具 ---------------------- -// GetCurrentDir 获取当前程序运行目录(适配Docker容器化,避免路径问题) -func GetCurrentDir() (string, error) { - dir, err := os.Getwd() - if err != nil { - return "", fmt.Errorf("get current dir failed: %w", err) - } - return dir, nil -} - -// MkdirIfNotExist 目录不存在则创建(支持多级目录),适配日志/文件存储 -func MkdirIfNotExist(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - // 0755:所有者读写执行,其他用户读执行,符合Linux/Docker权限规范 - if err := os.MkdirAll(path, 0755); err != nil { - return fmt.Errorf("mkdir %s failed: %w", path, err) - } - } - return nil -} \ No newline at end of file diff --git a/trading_assistant_api/container.sh b/trading_assistant_api/container.sh deleted file mode 100644 index 788b0d1..0000000 --- a/trading_assistant_api/container.sh +++ /dev/null @@ -1,4 +0,0 @@ -docker run --name trading_container -itd \ - -v $(pwd):/app \ - golang:1.25.7-alpine3.23 \ - /bin/sh \ No newline at end of file diff --git a/trading_assistant_api/services/country/Dockerfile b/trading_assistant_api/services/country/Dockerfile deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/country/api/country_api.go b/trading_assistant_api/services/country/api/country_api.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/country/conf/config.go b/trading_assistant_api/services/country/conf/config.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/country/dao/country_dao.go b/trading_assistant_api/services/country/dao/country_dao.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/country/deploy/postgres/init.sql b/trading_assistant_api/services/country/deploy/postgres/init.sql deleted file mode 100644 index 2413da1..0000000 --- a/trading_assistant_api/services/country/deploy/postgres/init.sql +++ /dev/null @@ -1,113 +0,0 @@ ----------------------------------- 国家主表 ---------------------------------- - --- 创建国家ID记录表(主表,存储基础主键+通用字段) -CREATE TABLE IF NOT EXISTS countries ( - country_id UUID PRIMARY KEY DEFAULT uuidv7(), - deleted boolean DEFAULT false, - create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - --- 表注释 -COMMENT ON TABLE countries IS '国家主表,仅存储国家基础主键及通用公共字段,关联国家/地区子表'; --- 字段注释 -COMMENT ON COLUMN countries.country_id IS '国家主键ID,PG18原生UUIDv7,作为所有子表的关联外键'; -COMMENT ON COLUMN countries.deleted IS '逻辑删除标识,false-未删除,true-已删除,默认未删除'; -COMMENT ON COLUMN countries.create_time IS '记录创建时间,默认当前系统时间,不可手动修改'; -COMMENT ON COLUMN countries.update_time IS '记录最后更新时间,默认当前系统时间'; - --- 创建update_time自动刷新触发器函数(所有表复用) -CREATE OR REPLACE FUNCTION update_table_modify_time() -RETURNS TRIGGER AS $$ -BEGIN - NEW.update_time = CURRENT_TIMESTAMP; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; --- 绑定触发器到国家主表 -CREATE TRIGGER tr_countries_update_time -BEFORE UPDATE ON countries -FOR EACH ROW -EXECUTE FUNCTION update_table_modify_time(); - - - ----------------------------------- 国家核心信息表 ---------------------------------- - --- 创建国家核心信息记录表(子表,存储国家编码/名称等核心信息) -CREATE TABLE IF NOT EXISTS country_core_infos ( - core_id UUID PRIMARY KEY DEFAULT uuidv7(), - country_id UUID NOT NULL, - country_code VARCHAR(5) NOT NULL, -- 如CN(中国)/US(美国),遵循ISO 3166-1 - country_cn_short_name VARCHAR(50) NOT NULL, -- 如中国/美国,中文简称 - deleted boolean DEFAULT false, - create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - -- 外键约束:关联国家主表,保证数据一致性(删除主表时级联置空/禁止删除,按需选择) - CONSTRAINT fk_country_core_infos_country_id - FOREIGN KEY (country_id) REFERENCES countries (country_id) - ON DELETE RESTRICT -- 主表国家未删除时,禁止删除子表信息(推荐生产使用) - -- ON DELETE SET NULL -- 可选:主表删除时,子表country_id置空(适合软删除场景) -); - --- 表注释 -COMMENT ON TABLE country_core_infos IS '国家核心信息子表,存储国家编码、中文名称等专属信息,与国家主表一对一关联'; --- 字段注释 -COMMENT ON COLUMN country_core_infos.core_id IS '子表主键ID,PG18原生UUIDv7'; -COMMENT ON COLUMN country_core_infos.country_id IS '关联国家主表countries.country_id,外键约束'; -COMMENT ON COLUMN country_core_infos.country_code IS '国家二位编码,遵循ISO 3166-1,如CN(中国)/US(美国),唯一标识国家'; -COMMENT ON COLUMN country_core_infos.country_cn_short_name IS '国家中文简称,如中国/美国,业务展示专用'; -COMMENT ON COLUMN country_core_infos.deleted IS '逻辑删除标识,false-未删除,true-已删除,默认未删除'; -COMMENT ON COLUMN country_core_infos.create_time IS '记录创建时间,默认当前系统时间,不可手动修改'; -COMMENT ON COLUMN country_core_infos.update_time IS '记录最后更新时间,更新时触发器自动刷新'; - --- 索引:优化高频业务查询(核心) -CREATE UNIQUE INDEX uk_country_core_infos_code ON country_core_infos (country_code) WHERE deleted = false; -- 唯一索引:国家编码不重复(未删除数据) -CREATE INDEX idx_country_core_infos_country_id ON country_core_infos (country_id, deleted); -- 联合索引:按国家ID关联查询+过滤删除数据 -CREATE INDEX idx_country_core_infos_name ON country_core_infos (country_cn_short_name, deleted); -- 联合索引:按国家名称模糊查询+过滤删除数据 - --- 绑定触发器:更新时自动刷新update_time -CREATE TRIGGER tr_country_core_infos_update_time -BEFORE UPDATE ON country_core_infos -FOR EACH ROW -EXECUTE FUNCTION update_table_modify_time(); - - ----------------------------------- 国家地区核心信息表 ---------------------------------- - --- 创建国家地区信息记录表(子表,存储国家下属地区编码/名称等信息,如中国香港/美国纽约) -CREATE TABLE IF NOT EXISTS country_region_core_infos ( - region_id UUID PRIMARY KEY DEFAULT uuidv7(), - country_id UUID NOT NULL, - region_code VARCHAR(5) NOT NULL, -- 如HK(香港)/TW(台湾)/NYC(纽约),建议按 国家码-地区码 设计如CN-HK - region_cn_short_name VARCHAR(50) NOT NULL, -- 如香港/台湾/纽约,中文简称 - deleted boolean DEFAULT false, - create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - -- 外键约束:关联国家主表,保证数据一致性 - CONSTRAINT fk_country_region_core_infos_country_id - FOREIGN KEY (country_id) REFERENCES countries (country_id) - ON DELETE RESTRICT -- 主表国家未删除时,禁止删除子表地区信息(推荐生产使用) -); - --- 表注释 -COMMENT ON TABLE country_region_core_infos IS '国家地区核心信息子表,存储国家下属地区编码、中文名称等信息,与国家主表一对多关联,支持区分中国香港/澳门/台湾等地区'; --- 字段注释 -COMMENT ON COLUMN country_region_core_infos.region_id IS '子表主键ID,PG18原生UUIDv7'; -COMMENT ON COLUMN country_region_core_infos.country_id IS '关联国家主表countries.country_id,外键约束,标识地区所属国家'; -COMMENT ON COLUMN country_region_core_infos.region_code IS '地区编码,如HK(香港)/TW(台湾)/NYC(纽约),建议遵循ISO 3166-2设计为CN-HK/CN-TW'; -COMMENT ON COLUMN country_region_core_infos.region_cn_short_name IS '地区中文简称,如香港/台湾/纽约,业务展示专用'; -COMMENT ON COLUMN country_region_core_infos.deleted IS '逻辑删除标识,false-未删除,true-已删除,默认未删除'; -COMMENT ON COLUMN country_region_core_infos.create_time IS '记录创建时间,默认当前系统时间,不可手动修改'; -COMMENT ON COLUMN country_region_core_infos.update_time IS '记录最后更新时间,更新时触发器自动刷新'; - --- 索引:优化高频业务查询(核心) -CREATE UNIQUE INDEX uk_country_region_core_infos_country_region ON country_region_core_infos (country_id, region_code) WHERE deleted = false; -- 唯一索引:同一国家下地区编码不重复(未删除数据) -CREATE INDEX idx_country_region_core_infos_country_id ON country_region_core_infos (country_id, deleted); -- 联合索引:按国家ID查询下属所有地区+过滤删除数据 -CREATE INDEX idx_country_region_core_infos_name ON country_region_core_infos (region_cn_short_name, deleted); -- 联合索引:按地区名称模糊查询+过滤删除数据 - --- 绑定触发器:更新时自动刷新update_time -CREATE TRIGGER tr_country_region_core_infos_update_time -BEFORE UPDATE ON country_region_core_infos -FOR EACH ROW -EXECUTE FUNCTION update_table_modify_time(); \ No newline at end of file diff --git a/trading_assistant_api/services/country/deploy/redis/redis.conf b/trading_assistant_api/services/country/deploy/redis/redis.conf deleted file mode 100644 index 5ecb4c1..0000000 --- a/trading_assistant_api/services/country/deploy/redis/redis.conf +++ /dev/null @@ -1,8 +0,0 @@ -# Redis基础配置 -bind 0.0.0.0 -protected-mode no -port 6379 -daemonize no -requirepass 123456 -appendonly yes -appendfsync everysec diff --git a/trading_assistant_api/services/country/docker-compose.yml b/trading_assistant_api/services/country/docker-compose.yml deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/country/go.mod b/trading_assistant_api/services/country/go.mod deleted file mode 100644 index 905c1b0..0000000 --- a/trading_assistant_api/services/country/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module country - -go 1.25.7 diff --git a/trading_assistant_api/services/country/main.go b/trading_assistant_api/services/country/main.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/country/model/country_model.go b/trading_assistant_api/services/country/model/country_model.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/user/Dockerfile b/trading_assistant_api/services/user/Dockerfile deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/user/api/user_api.go b/trading_assistant_api/services/user/api/user_api.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/user/conf/config.go b/trading_assistant_api/services/user/conf/config.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/user/dao/user_dao.go b/trading_assistant_api/services/user/dao/user_dao.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/user/deploy/postgres/init.sql b/trading_assistant_api/services/user/deploy/postgres/init.sql deleted file mode 100644 index 602418a..0000000 --- a/trading_assistant_api/services/user/deploy/postgres/init.sql +++ /dev/null @@ -1,4 +0,0 @@ --- PostgresSQL全局初始化脚本 --- 所有业务公共建库/建表语句写在这里,单独业务表建议在各自业务中处理 -CREATE DATABASE IF NOT EXISTS monorepo; -\c monorepo; diff --git a/trading_assistant_api/services/user/deploy/redis/redis.conf b/trading_assistant_api/services/user/deploy/redis/redis.conf deleted file mode 100644 index 5ecb4c1..0000000 --- a/trading_assistant_api/services/user/deploy/redis/redis.conf +++ /dev/null @@ -1,8 +0,0 @@ -# Redis基础配置 -bind 0.0.0.0 -protected-mode no -port 6379 -daemonize no -requirepass 123456 -appendonly yes -appendfsync everysec diff --git a/trading_assistant_api/services/user/docker-compose.yml b/trading_assistant_api/services/user/docker-compose.yml deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/user/main.go b/trading_assistant_api/services/user/main.go deleted file mode 100644 index e69de29..0000000 diff --git a/trading_assistant_api/services/user/model/user_model.go b/trading_assistant_api/services/user/model/user_model.go deleted file mode 100644 index e69de29..0000000