diff --git a/backend/futures_trade_record/src/logic/variety.go b/backend/futures_trade_record/src/logic/variety.go index f04d9fb..6581970 100644 --- a/backend/futures_trade_record/src/logic/variety.go +++ b/backend/futures_trade_record/src/logic/variety.go @@ -1,78 +1,97 @@ -package logic +package service import ( "database/sql" - "errors" - "fmt" + "futures_trade_record/db" + "futures_trade_record/logger" + "net/http" "time" + "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) -// Variety 主表结构体 +// Variety 品种主表结构体 type Variety struct { - ID uuid.UUID - Deleted bool - CreatedAt time.Time - UpdatedAt time.Time + ID string `json:"id"` + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } -// VarietyName 子表结构体(名称) +// VarietyName 品种名称子表结构体 type VarietyName struct { - ID uuid.UUID - VarietyID uuid.UUID - Name string - Deleted bool - CreatedAt time.Time - UpdatedAt time.Time + ID string `json:"id"` + VarietyID string `json:"variety_id"` + Name string `json:"name" binding:"required"` + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } -// VarietyCode 子表结构体(代码) +// VarietyCode 品种代码子表结构体 type VarietyCode struct { - ID uuid.UUID - VarietyID uuid.UUID - Code string - Deleted bool - CreatedAt time.Time - UpdatedAt time.Time + ID string `json:"id"` + VarietyID string `json:"variety_id"` + Code string `json:"code" binding:"required"` + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } -// VarietyTick 子表结构体(跳点) +// VarietyTick 品种跳点子表结构体 type VarietyTick struct { - ID uuid.UUID - VarietyID uuid.UUID - Tick float64 - Deleted bool - CreatedAt time.Time - UpdatedAt time.Time + ID string `json:"id"` + VarietyID string `json:"variety_id"` + Tick float64 `json:"tick"` + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } -// VarietyTickPrice 子表结构体(跳点价格) +// VarietyTickPrice 品种跳点价格子表结构体 type VarietyTickPrice struct { - ID uuid.UUID - VarietyID uuid.UUID - Price float64 - Deleted bool - CreatedAt time.Time - UpdatedAt time.Time + ID string `json:"id"` + VarietyID string `json:"variety_id"` + Price float64 `json:"price"` + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } -// VarietyInfo 联合查询视图结构体 -type VarietyInfo struct { - VarietyID uuid.UUID - Name string - Code string - Tick float64 - TickPrice float64 +// CreateVarietyRequest 创建品种请求参数 +type CreateVarietyRequest struct { + Name string `json:"name" binding:"required"` + Code string `json:"code" binding:"required"` + Tick float64 `json:"tick"` + TickPrice float64 `json:"tick_price"` } -// CreateVariety 创建品种主记录及关联信息 -func CreateVariety(name, code string, tick, tickPrice float64) (uuid.UUID, error) { - tx, err := DB.Begin() +// CreateVarietyHandler 创建品种接口处理函数 +func CreateVarietyHandler(c *gin.Context) { + var req CreateVarietyRequest + if err := c.ShouldBindJSON(&req); err != nil { + logger.ZapLogger.Error("请求参数绑定失败", zap.Error(err)) + c.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": "无效的请求参数: " + err.Error(), + }) + return + } + + // 生成主表ID + varietyID := uuid.New().String() + + // 开启数据库事务 + tx, err := db.DB.Begin() if err != nil { - zap.L().Error("创建事务失败", zap.Error(err)) - return uuid.Nil, err + logger.ZapLogger.Error("开启事务失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "服务器内部错误", + }) + return } defer func() { if r := recover(); r != nil { @@ -80,277 +99,159 @@ func CreateVariety(name, code string, tick, tickPrice float64) (uuid.UUID, error } }() - // 1. 创建主表记录 - var varietyID uuid.UUID - err = tx.QueryRow(` - INSERT INTO variety (deleted) - VALUES ($1) - RETURNING id - `, false).Scan(&varietyID) - if err != nil { - tx.Rollback() - zap.L().Error("创建品种主表失败", zap.Error(err)) - return uuid.Nil, err - } - - // 2. 创建名称记录 + // 插入主表 + now := time.Now() _, err = tx.Exec(` - INSERT INTO variety_name (variety_id, name, deleted) - VALUES ($1, $2, $3) - `, varietyID, name, false) + INSERT INTO variety (id, deleted, created_at, updated_at) + VALUES ($1, $2, $3, $4) + `, varietyID, false, now, now) if err != nil { tx.Rollback() - zap.L().Error("创建品种名称记录失败", zap.Error(err)) - return uuid.Nil, err + logger.ZapLogger.Error("插入品种主表失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "创建品种失败", + }) + return } - // 3. 创建代码记录 + // 插入名称子表 _, err = tx.Exec(` - INSERT INTO variety_code (variety_id, code, deleted) - VALUES ($1, $2, $3) - `, varietyID, code, false) + INSERT INTO variety_name (id, variety_id, name, deleted, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6) + `, uuid.New().String(), varietyID, req.Name, false, now, now) if err != nil { tx.Rollback() - zap.L().Error("创建品种代码记录失败", zap.Error(err)) - return uuid.Nil, err + logger.ZapLogger.Error("插入品种名称表失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "创建品种失败", + }) + return } - // 4. 创建跳点记录 + // 插入代码子表 _, err = tx.Exec(` - INSERT INTO variety_tick (variety_id, tick, deleted) - VALUES ($1, $2, $3) - `, varietyID, tick, false) + INSERT INTO variety_code (id, variety_id, code, deleted, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6) + `, uuid.New().String(), varietyID, req.Code, false, now, now) if err != nil { tx.Rollback() - zap.L().Error("创建跳点记录失败", zap.Error(err)) - return uuid.Nil, err + logger.ZapLogger.Error("插入品种代码表失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "创建品种失败", + }) + return } - // 5. 创建跳点价格记录 + // 插入跳点子表 _, err = tx.Exec(` - INSERT INTO variety_tick_price (variety_id, price, deleted) - VALUES ($1, $2, $3) - `, varietyID, tickPrice, false) + INSERT INTO variety_tick (id, variety_id, tick, deleted, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6) + `, uuid.New().String(), varietyID, req.Tick, false, now, now) if err != nil { tx.Rollback() - zap.L().Error("创建跳点价格记录失败", zap.Error(err)) - return uuid.Nil, err + logger.ZapLogger.Error("插入品种跳点表失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "创建品种失败", + }) + return } + // 插入跳点价格子表 + _, err = tx.Exec(` + INSERT INTO variety_tick_price (id, variety_id, price, deleted, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6) + `, uuid.New().String(), varietyID, req.TickPrice, false, now, now) + if err != nil { + tx.Rollback() + logger.ZapLogger.Error("插入品种跳点价格表失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "创建品种失败", + }) + return + } + + // 提交事务 if err := tx.Commit(); err != nil { tx.Rollback() - zap.L().Error("提交事务失败", zap.Error(err)) - return uuid.Nil, err + logger.ZapLogger.Error("提交事务失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "创建品种失败", + }) + return } - return varietyID, nil + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "message": "品种创建成功", + "data": gin.H{ + "variety_id": varietyID, + }, + }) } -// GetVarietyByID 查询品种详情(通过ID) -func GetVarietyByID(id uuid.UUID) (*VarietyInfo, error) { - var info VarietyInfo - err := DB.QueryRow(` - SELECT - variety_id, name, code, tick, tick_price - FROM variety_info_view - WHERE variety_id = $1 - `, id).Scan( - &info.VarietyID, - &info.Name, - &info.Code, - &info.Tick, - &info.TickPrice, - ) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, fmt.Errorf("品种不存在: %s", id) - } - zap.L().Error("查询品种失败", zap.String("id", id.String()), zap.Error(err)) - return nil, err - } - return &info, nil -} - -// GetVarietyByName 按名称查询品种 -func GetVarietyByName(name string) (*VarietyInfo, error) { - var info VarietyInfo - err := DB.QueryRow(` - SELECT - variety_id, name, code, tick, tick_price - FROM variety_info_view - WHERE name = $1 AND name IS NOT NULL - `, name).Scan( - &info.VarietyID, - &info.Name, - &info.Code, - &info.Tick, - &info.TickPrice, - ) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, fmt.Errorf("未找到名称为 %s 的品种", name) - } - zap.L().Error("按名称查询品种失败", zap.String("name", name), zap.Error(err)) - return nil, err - } - return &info, nil -} - -// UpdateVarietyName 更新品种名称 -func UpdateVarietyName(varietyID uuid.UUID, newName string) error { - result, err := DB.Exec(` - UPDATE variety_name - SET name = $1 - WHERE variety_id = $2 AND deleted = false - `, newName, varietyID) - if err != nil { - zap.L().Error("更新品种名称失败", zap.String("id", varietyID.String()), zap.Error(err)) - return err - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - if rowsAffected == 0 { - return fmt.Errorf("未找到可更新的品种名称记录: %s", varietyID) - } - return nil -} - -// UpdateVarietyTick 更新跳点值 -func UpdateVarietyTick(varietyID uuid.UUID, newTick float64) error { - result, err := DB.Exec(` - UPDATE variety_tick - SET tick = $1 - WHERE variety_id = $2 AND deleted = false - `, newTick, varietyID) - if err != nil { - zap.L().Error("更新跳点值失败", zap.String("id", varietyID.String()), zap.Error(err)) - return err - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - if rowsAffected == 0 { - return fmt.Errorf("未找到可更新的跳点记录: %s", varietyID) - } - return nil -} - -// DeleteVariety 逻辑删除品种(软删除) -func DeleteVariety(varietyID uuid.UUID) error { - tx, err := DB.Begin() - if err != nil { - zap.L().Error("创建事务失败", zap.Error(err)) - return err - } - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - - // 1. 软删除主表 - _, err = tx.Exec(` - UPDATE variety - SET deleted = true - WHERE id = $1 - `, varietyID) - if err != nil { - tx.Rollback() - zap.L().Error("删除品种主表失败", zap.String("id", varietyID.String()), zap.Error(err)) - return err - } - - // 2. 软删除关联子表(实际可依赖外键级联删除,这里显式处理确保一致性) - tables := []string{ - "variety_name", - "variety_code", - "variety_tick", - "variety_tick_price", - } - for _, table := range tables { - _, err = tx.Exec(fmt.Sprintf(` - UPDATE %s - SET deleted = true - WHERE variety_id = $1 - `, table), varietyID) - if err != nil { - tx.Rollback() - zap.L().Error("删除子表记录失败", - zap.String("table", table), - zap.String("id", varietyID.String()), - zap.Error(err)) - return err - } - } - - if err := tx.Commit(); err != nil { - tx.Rollback() - zap.L().Error("提交删除事务失败", zap.Error(err)) - return err - } - return nil -} - -// ListVarieties 分页查询品种列表 -func ListVarieties(page, pageSize int) ([]VarietyInfo, int, error) { - if page < 1 { - page = 1 - } - offset := (page - 1) * pageSize - - // 查询总数 - var total int - err := DB.QueryRow(` - SELECT COUNT(DISTINCT variety_id) - FROM variety_info_view - WHERE variety_id IS NOT NULL - `).Scan(&total) - if err != nil { - zap.L().Error("查询品种总数失败", zap.Error(err)) - return nil, 0, err - } - - // 查询分页数据 - rows, err := DB.Query(` - SELECT - variety_id, name, code, tick, tick_price - FROM variety_info_view - WHERE variety_id IS NOT NULL +// GetVarietyListHandler 获取品种列表接口 +func GetVarietyListHandler(c *gin.Context) { + rows, err := db.DB.Query(` + SELECT variety_id, name, code, tick, tick_price + FROM variety_info_view ORDER BY variety_id DESC - LIMIT $1 OFFSET $2 - `, pageSize, offset) + `) if err != nil { - zap.L().Error("查询品种列表失败", zap.Error(err)) - return nil, 0, err + logger.ZapLogger.Error("查询品种列表失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "获取品种列表失败", + }) + return } defer rows.Close() - var varieties []VarietyInfo + var varieties []gin.H for rows.Next() { - var info VarietyInfo - err := rows.Scan( - &info.VarietyID, - &info.Name, - &info.Code, - &info.Tick, - &info.TickPrice, + var ( + varietyID string + name sql.NullString + code sql.NullString + tick sql.NullFloat64 + price sql.NullFloat64 ) - if err != nil { - zap.L().Error("解析品种记录失败", zap.Error(err)) - return nil, 0, err + + if err := rows.Scan(&varietyID, &name, &code, &tick, &price); err != nil { + logger.ZapLogger.Error("扫描品种数据失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "获取品种列表失败", + }) + return } - varieties = append(varieties, info) + + varieties = append(varieties, gin.H{ + "variety_id": varietyID, + "name": name.String, + "code": code.String, + "tick": tick.Float64, + "tick_price": price.Float64, + }) } - if err = rows.Err(); err != nil { - zap.L().Error("遍历品种记录失败", zap.Error(err)) - return nil, 0, err + if err := rows.Err(); err != nil { + logger.ZapLogger.Error("行迭代错误", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": "获取品种列表失败", + }) + return } - return varieties, total, nil + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "message": "success", + "data": varieties, + }) } \ No newline at end of file diff --git a/backend/futures_trade_record/src/service/variety.go b/backend/futures_trade_record/src/service/variety.go deleted file mode 100644 index 2e5a6c3..0000000 --- a/backend/futures_trade_record/src/service/variety.go +++ /dev/null @@ -1 +0,0 @@ -package service \ No newline at end of file