add
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package service
|
||||
Reference in New Issue
Block a user