From bccc20e30707fc6343a6ef03f83d2c4e13a7499e Mon Sep 17 00:00:00 2001 From: vipg Date: Mon, 22 Dec 2025 18:28:26 +0800 Subject: [PATCH] add --- .../src/crud/create.go | 45 +++++----------- .../cn_futures_trading_records/src/go.mod | 3 +- .../cn_futures_trading_records/src/go.sum | 2 + .../src/infra/infra.go | 50 ++++++++++++++++++ .../src/{logger => infra}/logger.go | 5 +- .../src/{db => infra}/postgres.go | 5 +- .../cn_futures_trading_records/src/main.go | 52 ++++--------------- .../src/model/payload.go | 25 +++++++++ 8 files changed, 106 insertions(+), 81 deletions(-) create mode 100644 services/cn_futures_trading_records/src/infra/infra.go rename services/cn_futures_trading_records/src/{logger => infra}/logger.go (98%) rename services/cn_futures_trading_records/src/{db => infra}/postgres.go (97%) create mode 100644 services/cn_futures_trading_records/src/model/payload.go diff --git a/services/cn_futures_trading_records/src/crud/create.go b/services/cn_futures_trading_records/src/crud/create.go index 52bda32..3de0bf5 100644 --- a/services/cn_futures_trading_records/src/crud/create.go +++ b/services/cn_futures_trading_records/src/crud/create.go @@ -2,50 +2,29 @@ package crud import ( "bytes" - "cn_futures_trading_records/db" "encoding/csv" "encoding/json" "fmt" "net/http" "reflect" "strconv" + "strings" "time" + "trading_records/infra" + "trading_records/model" + "github.com/gin-gonic/gin" "github.com/gocarina/gocsv" "github.com/google/uuid" "go.uber.org/zap" ) -/* ---------- 公共 Payload ---------- */ -type Payload struct { - Status int `csv:"status" json:"status" binding:"required,min=-1,max=1"` - OpenYear int `csv:"open_year" json:"open_year" binding:"required,min=1900,max=2200"` - OpenMonth int `csv:"open_month" json:"open_month" binding:"required,min=1,max=12"` - OpenDay int `csv:"open_day" json:"open_day" binding:"required,min=1,max=31"` - Symbol string `csv:"symbol" json:"symbol" binding:"required"` - Contract string `csv:"contract" json:"contract" binding:"required"` - Direction int `csv:"direction" json:"direction" binding:"required,min=-1,max=1"` - OpenPrice float64 `csv:"open_price" json:"open_price" binding:"required"` - OpenFee float64 `csv:"open_fee" json:"open_fee" binding:"required,min=0"` - CloseYear int `csv:"close_year" json:"close_year" binding:"required,min=1900,max=2200"` - CloseMonth int `csv:"close_month" json:"close_month" binding:"required,min=1,max=12"` - CloseDay int `csv:"close_day" json:"close_day" binding:"required,min=1,max=31"` - ClosePrice float64 `csv:"close_price" json:"close_price" binding:"required"` - CloseFee float64 `csv:"close_fee" json:"close_fee" binding:"required,min=0"` - PriceDiff float64 `csv:"price_diff" json:"price_diff" binding:"required"` - MinTick float64 `csv:"min_tick" json:"min_tick" binding:"required"` - TickPrice float64 `csv:"tick_price" json:"tick_price" binding:"required"` - DiffPnL float64 `csv:"diff_pnl" json:"diff_pnl" binding:"required"` - TotalFee float64 `csv:"total_fee" json:"total_fee" binding:"required,min=0"` - ClosePnL float64 `csv:"close_pnl" json:"close_pnl" binding:"required"` -} - /* ---------- 包级常量:带 csv 标签的字段个数 ---------- */ var payloadCSVCols int func init() { - typ := reflect.TypeOf(Payload{}) + typ := reflect.TypeFor[model.Payload]() for i := 0; i < typ.NumField(); i++ { if _, ok := typ.Field(i).Tag.Lookup("csv"); ok { payloadCSVCols++ @@ -55,7 +34,7 @@ func init() { /* ---------- 单条创建 ---------- */ type TradingRecordsCreateRequest struct { - Payload Payload `json:"payload" binding:"required"` + Payload model.Payload `json:"payload" binding:"required"` } type TradingRecordsCreateResponse struct { @@ -91,7 +70,7 @@ func CreateHandler(c *gin.Context) { zap.L().Debug("✅ 请求参数验证通过", zap.String("req_id", reqID), zap.Any("payload", req.Payload)) - tx, err := db.DB.Begin() + tx, err := infra.DB.Begin() if err != nil { zap.L().Error("❌ 事务开启失败", zap.String("req_id", reqID), zap.Error(err)) c.JSON(http.StatusOK, TradingRecordsCreateResponse{Success: false, Message: "系统错误,请稍后重试"}) @@ -114,7 +93,7 @@ func CreateHandler(c *gin.Context) { } var recordID string - err = tx.QueryRow(`INSERT INTO cn_futures_trading_records (payload) VALUES ($1) RETURNING id`, payloadBytes).Scan(&recordID) + err = tx.QueryRow(`INSERT INTO trading_records (payload) VALUES ($1) RETURNING id`, payloadBytes).Scan(&recordID) if err != nil { tx.Rollback() zap.L().Error("❌ 插入失败", zap.String("req_id", reqID), zap.Error(err)) @@ -167,7 +146,7 @@ func CreateBatchHandler(c *gin.Context) { zap.L().Debug("✅ 请求参数验证通过", zap.String("req_id", reqID), zap.Int("row_count", len(req.Rows))) - tx, err := db.DB.Begin() + tx, err := infra.DB.Begin() if err != nil { zap.L().Error("❌ 事务开启失败", zap.String("req_id", reqID), zap.Error(err)) c.JSON(http.StatusOK, TradingRecordsBatchCreateResponse{Success: false, Message: "系统错误,请稍后重试"}) @@ -181,7 +160,7 @@ func CreateBatchHandler(c *gin.Context) { } }() - stmt, err := tx.Prepare(`INSERT INTO cn_futures_trading_records (payload) VALUES ($1) RETURNING id`) + stmt, err := tx.Prepare(`INSERT INTO trading_records (payload) VALUES ($1) RETURNING id`) if err != nil { tx.Rollback() zap.L().Error("❌ 预编译语句失败", zap.String("req_id", reqID), zap.Error(err)) @@ -200,7 +179,7 @@ func CreateBatchHandler(c *gin.Context) { } // 一次性映射 - var p Payload + var p model.Payload if err := gocsv.UnmarshalCSV(csv.NewReader(bytes.NewReader([]byte(strings.Join(row, ",")+"\n"))), &p); err != nil { tx.Rollback() zap.L().Error("❌ CSV 映射失败", zap.String("req_id", reqID), zap.Int("row_index", idx), zap.Error(err)) @@ -228,4 +207,4 @@ func CreateBatchHandler(c *gin.Context) { zap.L().Info("✅ 交易记录批量创建完成", zap.String("req_id", reqID), zap.Int("inserted_count", len(recordIDs)), zap.Duration("duration", time.Since(start))) c.JSON(http.StatusOK, TradingRecordsBatchCreateResponse{Success: true, Message: "批量创建成功", RecordIDs: recordIDs}) -} \ No newline at end of file +} diff --git a/services/cn_futures_trading_records/src/go.mod b/services/cn_futures_trading_records/src/go.mod index 4ad544c..fc78798 100644 --- a/services/cn_futures_trading_records/src/go.mod +++ b/services/cn_futures_trading_records/src/go.mod @@ -1,10 +1,11 @@ -module cn_futures_trading_records +module trading_records go 1.25.0 require ( github.com/gin-contrib/cors v1.7.6 github.com/gin-gonic/gin v1.11.0 + github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/google/uuid v1.6.0 github.com/lib/pq v1.10.9 github.com/spf13/viper v1.21.0 diff --git a/services/cn_futures_trading_records/src/go.sum b/services/cn_futures_trading_records/src/go.sum index 0117005..59207b6 100644 --- a/services/cn_futures_trading_records/src/go.sum +++ b/services/cn_futures_trading_records/src/go.sum @@ -29,6 +29,8 @@ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHO github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= diff --git a/services/cn_futures_trading_records/src/infra/infra.go b/services/cn_futures_trading_records/src/infra/infra.go new file mode 100644 index 0000000..3027288 --- /dev/null +++ b/services/cn_futures_trading_records/src/infra/infra.go @@ -0,0 +1,50 @@ +package infra + +import ( + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// Init 初始化基础设施 +func Init() { + launchLogger() + launchDB() + cnfigGin() +} + +func launchLogger() { + InitLogger() +} + +func launchDB() { + InitDB() + // 程序退出时关闭数据库连接(defer确保在函数退出前执行) + defer DB.Close() +} + +func cnfigGin() { + // 设置Gin框架为发布模式(关闭调试信息) + gin.SetMode(gin.ReleaseMode) + // 创建Gin默认路由器 + r := gin.Default() + + // 配置跨域中间件 + r.Use(cors.New(cors.Config{ + // 允许所有来源(生产环境建议指定具体域名) + AllowOrigins: []string{"*"}, + // 允许的请求方法 + AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + // 允许的请求头 + AllowHeaders: []string{"Origin", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "X-LoginRequest-ID"}, + // 允许前端读取的响应头 + ExposeHeaders: []string{"Content-Length"}, + // 是否允许携带cookie + AllowCredentials: true, + // 预检请求的缓存时间 + MaxAge: 12 * time.Hour, + })) + zap.L().Info("✅ 配置跨域中间件完成") +} diff --git a/services/cn_futures_trading_records/src/logger/logger.go b/services/cn_futures_trading_records/src/infra/logger.go similarity index 98% rename from services/cn_futures_trading_records/src/logger/logger.go rename to services/cn_futures_trading_records/src/infra/logger.go index d7eee0b..f718db0 100644 --- a/services/cn_futures_trading_records/src/logger/logger.go +++ b/services/cn_futures_trading_records/src/infra/logger.go @@ -1,4 +1,4 @@ -package logger +package infra import ( "log" @@ -28,7 +28,7 @@ func init() { } // Init 初始化日志(依赖配置文件已加载) -func Init() { +func InitLogger() { // 日志级别转换 level := zap.InfoLevel switch viper.GetString("logger.level") { @@ -73,7 +73,6 @@ func Init() { // 创建logger实例(开启调用者信息和堆栈跟踪) logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)) zap.ReplaceGlobals(logger) - zap.L().Info("✅ 日志初始化成功", zap.String("level", level.String())) } diff --git a/services/cn_futures_trading_records/src/db/postgres.go b/services/cn_futures_trading_records/src/infra/postgres.go similarity index 97% rename from services/cn_futures_trading_records/src/db/postgres.go rename to services/cn_futures_trading_records/src/infra/postgres.go index aba5033..ec4e6fe 100644 --- a/services/cn_futures_trading_records/src/db/postgres.go +++ b/services/cn_futures_trading_records/src/infra/postgres.go @@ -1,4 +1,4 @@ -package db +package infra import ( "database/sql" @@ -13,7 +13,7 @@ import ( var DB *sql.DB // 初始化数据库连接 -func Init() { +func InitDB() { // 从环境变量获取数据库配置 dbHost := os.Getenv("DB_HOST") dbPort := os.Getenv("DB_PORT") @@ -49,6 +49,5 @@ func Init() { if err := DB.Ping(); err != nil { zap.L().Panic("❌ 数据库连接失败", zap.Error(err)) } - zap.L().Info("✅ 数据库连接验证成功") } diff --git a/services/cn_futures_trading_records/src/main.go b/services/cn_futures_trading_records/src/main.go index 98cf461..4bb0ed2 100644 --- a/services/cn_futures_trading_records/src/main.go +++ b/services/cn_futures_trading_records/src/main.go @@ -1,64 +1,34 @@ package main import ( - "cn_futures_trading_records/crud" - "cn_futures_trading_records/db" // 数据库相关操作包 - "cn_futures_trading_records/logger" // 日志工具包 + "trading_records/crud" + "trading_records/infra" - // 业务逻辑处理包 - "time" - - "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" // Gin框架,用于构建HTTP服务 _ "github.com/lib/pq" // PostgreSQL数据库驱动(下划线表示仅初始化不直接使用) "go.uber.org/zap" // Zap日志库,用于结构化日志输出 ) -// main函数是程序的入口点 func main() { - // 初始化日志配置 - logger.Init() - // 记录服务初始化日志 - zap.L().Info("🚀 用户服务初始化") + infra.Init() + configRouter() + startServe() +} - // 记录数据库初始化开始日志 - zap.L().Info("⌛️ 数据库初始化开始") - // 初始化数据库连接 - db.Init() - // 程序退出时关闭数据库连接(defer确保在函数退出前执行) - defer db.DB.Close() - // 记录数据库初始化成功日志 - zap.L().Info("✅ 数据库初始化成功") - - // 设置Gin框架为发布模式(关闭调试信息) - gin.SetMode(gin.ReleaseMode) +func configRouter() { // 创建Gin默认路由器 r := gin.Default() - - // 配置跨域中间件 - r.Use(cors.New(cors.Config{ - // 允许所有来源(生产环境建议指定具体域名) - AllowOrigins: []string{"*"}, - // 允许的请求方法 - AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - // 允许的请求头 - AllowHeaders: []string{"Origin", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "X-LoginRequest-ID"}, - // 允许前端读取的响应头 - ExposeHeaders: []string{"Content-Length"}, - // 是否允许携带cookie - AllowCredentials: true, - // 预检请求的缓存时间 - MaxAge: 12 * time.Hour, - })) - zap.L().Info("✅ 配置跨域中间件完成") - // 注册用户接口 trading := r.Group("/cn_futures_trading_record") { trading.POST("/open", crud.CreateHandler) } zap.L().Info("✅ 中国期货交易记录接口注册完成") +} +func startServe() { + // 创建Gin默认路由器 + r := gin.Default() // 记录服务启动日志,监听80端口 zap.L().Info("✅ 服务启动在80端口") r.Run(":80") diff --git a/services/cn_futures_trading_records/src/model/payload.go b/services/cn_futures_trading_records/src/model/payload.go new file mode 100644 index 0000000..cebf0fc --- /dev/null +++ b/services/cn_futures_trading_records/src/model/payload.go @@ -0,0 +1,25 @@ +package model + +type Payload struct { + Status int `csv:"status" json:"status" binding:"required,min=-1,max=1"` + Market int `csv:"market" json:"market" binding:"required,min=0,max=500"` + OpenYear int `csv:"open_year" json:"open_year" binding:"required,min=1900,max=2200"` + OpenMonth int `csv:"open_month" json:"open_month" binding:"required,min=1,max=12"` + OpenDay int `csv:"open_day" json:"open_day" binding:"required,min=1,max=31"` + Symbol string `csv:"symbol" json:"symbol" binding:"required"` + Contract string `csv:"contract" json:"contract" binding:"required"` + Direction int `csv:"direction" json:"direction" binding:"required,min=-1,max=1"` + OpenPrice float64 `csv:"open_price" json:"open_price" binding:"required"` + OpenFee float64 `csv:"open_fee" json:"open_fee" binding:"required,min=0"` + CloseYear int `csv:"close_year" json:"close_year" binding:"required,min=1900,max=2200"` + CloseMonth int `csv:"close_month" json:"close_month" binding:"required,min=1,max=12"` + CloseDay int `csv:"close_day" json:"close_day" binding:"required,min=1,max=31"` + ClosePrice float64 `csv:"close_price" json:"close_price" binding:"required"` + CloseFee float64 `csv:"close_fee" json:"close_fee" binding:"required,min=0"` + PriceDiff float64 `csv:"price_diff" json:"price_diff" binding:"required"` + MinTick float64 `csv:"min_tick" json:"min_tick" binding:"required"` + TickPrice float64 `csv:"tick_price" json:"tick_price" binding:"required"` + DiffPnL float64 `csv:"diff_pnl" json:"diff_pnl" binding:"required"` + TotalFee float64 `csv:"total_fee" json:"total_fee" binding:"required,min=0"` + ClosePnL float64 `csv:"close_pnl" json:"close_pnl" binding:"required"` +}