Compare commits
	
		
			7 Commits
		
	
	
		
			7b50873de3
			...
			Branch_反向做
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b1cca5bec7 | |||
| 4b28684fe4 | |||
| 56a761e5ab | |||
| 771c617da4 | |||
| 3013486dd4 | |||
| e943a47121 | |||
| 0f9b966fdb | 
@ -120,6 +120,11 @@ func (e LineApiUser) Insert(c *gin.Context) {
 | 
				
			|||||||
	// 设置创建人
 | 
						// 设置创建人
 | 
				
			||||||
	req.SetCreateBy(user.GetUserId(c))
 | 
						req.SetCreateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := req.Valid(); err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = s.Insert(&req)
 | 
						err = s.Insert(&req)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		e.Error(500, err, fmt.Sprintf("创建api用户管理失败,\r\n失败信息 %s", err.Error()))
 | 
							e.Error(500, err, fmt.Sprintf("创建api用户管理失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
@ -153,6 +158,12 @@ func (e LineApiUser) Update(c *gin.Context) {
 | 
				
			|||||||
		e.Error(500, err, err.Error())
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := req.Valid(); err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	req.SetUpdateBy(user.GetUserId(c))
 | 
						req.SetUpdateBy(user.GetUserId(c))
 | 
				
			||||||
	p := actions.GetPermissionFromContext(c)
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -267,3 +278,74 @@ func (e LineApiUser) GetMainUser(c *gin.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	e.OK(list, "操作成功")
 | 
						e.OK(list, "操作成功")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUnBindApiUser 获取未绑定下反单api用户
 | 
				
			||||||
 | 
					func (e LineApiUser) GetUnBindReverseApiUser(c *gin.Context) {
 | 
				
			||||||
 | 
						s := service.LineApiUser{}
 | 
				
			||||||
 | 
						req := dto.GetUnBindReverseReq{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req, binding.Query, binding.Form).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						datas := make([]dto.UnBindReverseResp, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.GetUnBindApiUser(&req, &datas)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK(datas, "查询成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取反单api用户选项
 | 
				
			||||||
 | 
					func (e LineApiUser) GetReverseApiOptions(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.GetReverseApiOptionsReq{}
 | 
				
			||||||
 | 
						s := service.LineApiUser{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						list := make([]models.LineApiUser, 0)
 | 
				
			||||||
 | 
						err = s.GetReverseApiOptions(&req, &list)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK(list, "操作成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取所有启用的反单api用户
 | 
				
			||||||
 | 
					func (e LineApiUser) GetReverseApiOptionsAll(c *gin.Context) {
 | 
				
			||||||
 | 
						s := service.LineApiUser{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						list := make([]dto.LineApiUserOptionResp, 0)
 | 
				
			||||||
 | 
						err = s.GetReverseApiOptionsAll(&list)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK(list, "操作成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ package apis
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
	"github.com/go-admin-team/go-admin-core/sdk/api"
 | 
						"github.com/go-admin-team/go-admin-core/sdk/api"
 | 
				
			||||||
	"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
 | 
						"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
 | 
				
			||||||
@ -193,6 +194,7 @@ func (e LinePriceLimit) Delete(c *gin.Context) {
 | 
				
			|||||||
	e.OK(req.GetId(), "删除成功")
 | 
						e.OK(req.GetId(), "删除成功")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// aicoin数据同步
 | 
				
			||||||
func (e LinePriceLimit) UpRange(c *gin.Context) {
 | 
					func (e LinePriceLimit) UpRange(c *gin.Context) {
 | 
				
			||||||
	s := service.LinePriceLimit{}
 | 
						s := service.LinePriceLimit{}
 | 
				
			||||||
	err := e.MakeContext(c).
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										198
									
								
								app/admin/apis/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								app/admin/apis/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,198 @@
 | 
				
			|||||||
 | 
					package apis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
					    "fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/api"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
 | 
				
			||||||
 | 
						_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service/dto"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseOrder struct {
 | 
				
			||||||
 | 
						api.Api
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPage 获取反单下单列表列表
 | 
				
			||||||
 | 
					// @Summary 获取反单下单列表列表
 | 
				
			||||||
 | 
					// @Description 获取反单下单列表列表
 | 
				
			||||||
 | 
					// @Tags 反单下单列表
 | 
				
			||||||
 | 
					// @Param orderSn query string false "订单号"
 | 
				
			||||||
 | 
					// @Param orderId query string false "币安订单号"
 | 
				
			||||||
 | 
					// @Param followOrderSn query string false "跟随币安订单号"
 | 
				
			||||||
 | 
					// @Param orderType query int false "订单类型 0-主单 1-止损单 2-加仓 3-减仓"
 | 
				
			||||||
 | 
					// @Param positionSide query string false "持仓方向 LONG-多 SHORT-空"
 | 
				
			||||||
 | 
					// @Param side query string false "买卖方向 SELL-卖 BUY-买"
 | 
				
			||||||
 | 
					// @Param status query int false "状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"
 | 
				
			||||||
 | 
					// @Param pageSize query int false "页条数"
 | 
				
			||||||
 | 
					// @Param pageIndex query int false "页码"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineReverseOrder}} "{"code": 200, "data": [...]}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-order [get]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseOrder) GetPage(c *gin.Context) {
 | 
				
			||||||
 | 
					    req := dto.LineReverseOrderGetPageReq{}
 | 
				
			||||||
 | 
					    s := service.LineReverseOrder{}
 | 
				
			||||||
 | 
					    err := e.MakeContext(c).
 | 
				
			||||||
 | 
					        MakeOrm().
 | 
				
			||||||
 | 
					        Bind(&req).
 | 
				
			||||||
 | 
					        MakeService(&s.Service).
 | 
				
			||||||
 | 
					        Errors
 | 
				
			||||||
 | 
					   	if err != nil {
 | 
				
			||||||
 | 
					   		e.Logger.Error(err)
 | 
				
			||||||
 | 
					   		e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
					   		return
 | 
				
			||||||
 | 
					   	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
						list := make([]models.LineReverseOrder, 0)
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.GetPage(&req, p, &list, &count)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取反单下单列表失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get 获取反单下单列表
 | 
				
			||||||
 | 
					// @Summary 获取反单下单列表
 | 
				
			||||||
 | 
					// @Description 获取反单下单列表
 | 
				
			||||||
 | 
					// @Tags 反单下单列表
 | 
				
			||||||
 | 
					// @Param id path int false "id"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response{data=models.LineReverseOrder} "{"code": 200, "data": [...]}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-order/{id} [get]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseOrder) Get(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReverseOrderGetReq{}
 | 
				
			||||||
 | 
						s := service.LineReverseOrder{}
 | 
				
			||||||
 | 
					    err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var object models.LineReverseOrder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
						err = s.Get(&req, p, &object)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取反单下单列表失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK( object, "查询成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Insert 创建反单下单列表
 | 
				
			||||||
 | 
					// @Summary 创建反单下单列表
 | 
				
			||||||
 | 
					// @Description 创建反单下单列表
 | 
				
			||||||
 | 
					// @Tags 反单下单列表
 | 
				
			||||||
 | 
					// @Accept application/json
 | 
				
			||||||
 | 
					// @Product application/json
 | 
				
			||||||
 | 
					// @Param data body dto.LineReverseOrderInsertReq true "data"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-order [post]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseOrder) Insert(c *gin.Context) {
 | 
				
			||||||
 | 
					    req := dto.LineReverseOrderInsertReq{}
 | 
				
			||||||
 | 
					    s := service.LineReverseOrder{}
 | 
				
			||||||
 | 
					    err := e.MakeContext(c).
 | 
				
			||||||
 | 
					        MakeOrm().
 | 
				
			||||||
 | 
					        Bind(&req).
 | 
				
			||||||
 | 
					        MakeService(&s.Service).
 | 
				
			||||||
 | 
					        Errors
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					        e.Logger.Error(err)
 | 
				
			||||||
 | 
					        e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
						// 设置创建人
 | 
				
			||||||
 | 
						req.SetCreateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Insert(&req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("创建反单下单列表失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK(req.GetId(), "创建成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update 修改反单下单列表
 | 
				
			||||||
 | 
					// @Summary 修改反单下单列表
 | 
				
			||||||
 | 
					// @Description 修改反单下单列表
 | 
				
			||||||
 | 
					// @Tags 反单下单列表
 | 
				
			||||||
 | 
					// @Accept application/json
 | 
				
			||||||
 | 
					// @Product application/json
 | 
				
			||||||
 | 
					// @Param id path int true "id"
 | 
				
			||||||
 | 
					// @Param data body dto.LineReverseOrderUpdateReq true "body"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-order/{id} [put]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseOrder) Update(c *gin.Context) {
 | 
				
			||||||
 | 
					    req := dto.LineReverseOrderUpdateReq{}
 | 
				
			||||||
 | 
					    s := service.LineReverseOrder{}
 | 
				
			||||||
 | 
					    err := e.MakeContext(c).
 | 
				
			||||||
 | 
					        MakeOrm().
 | 
				
			||||||
 | 
					        Bind(&req).
 | 
				
			||||||
 | 
					        MakeService(&s.Service).
 | 
				
			||||||
 | 
					        Errors
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					        e.Logger.Error(err)
 | 
				
			||||||
 | 
					        e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
						req.SetUpdateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Update(&req, p)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("修改反单下单列表失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK( req.GetId(), "修改成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Delete 删除反单下单列表
 | 
				
			||||||
 | 
					// @Summary 删除反单下单列表
 | 
				
			||||||
 | 
					// @Description 删除反单下单列表
 | 
				
			||||||
 | 
					// @Tags 反单下单列表
 | 
				
			||||||
 | 
					// @Param data body dto.LineReverseOrderDeleteReq true "body"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-order [delete]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseOrder) Delete(c *gin.Context) {
 | 
				
			||||||
 | 
					    s := service.LineReverseOrder{}
 | 
				
			||||||
 | 
					    req := dto.LineReverseOrderDeleteReq{}
 | 
				
			||||||
 | 
					    err := e.MakeContext(c).
 | 
				
			||||||
 | 
					        MakeOrm().
 | 
				
			||||||
 | 
					        Bind(&req).
 | 
				
			||||||
 | 
					        MakeService(&s.Service).
 | 
				
			||||||
 | 
					        Errors
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					        e.Logger.Error(err)
 | 
				
			||||||
 | 
					        e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// req.SetUpdateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Remove(&req, p)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("删除反单下单列表失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK( req.GetId(), "删除成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										282
									
								
								app/admin/apis/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								app/admin/apis/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,282 @@
 | 
				
			|||||||
 | 
					package apis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/api"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
 | 
				
			||||||
 | 
						_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service/dto"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePosition struct {
 | 
				
			||||||
 | 
						api.Api
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPage 获取反单管理-仓位列表
 | 
				
			||||||
 | 
					// @Summary 获取反单管理-仓位列表
 | 
				
			||||||
 | 
					// @Description 获取反单管理-仓位列表
 | 
				
			||||||
 | 
					// @Tags 反单管理-仓位
 | 
				
			||||||
 | 
					// @Param apiId query int64 false "api_id"
 | 
				
			||||||
 | 
					// @Param reverseApiId query int64 false "反单api_id"
 | 
				
			||||||
 | 
					// @Param side query string false "买卖方向 BUY SELL"
 | 
				
			||||||
 | 
					// @Param positionSide query string false "持仓方向 LONG SHORT"
 | 
				
			||||||
 | 
					// @Param symbol query string false "交易对"
 | 
				
			||||||
 | 
					// @Param pageSize query int false "页条数"
 | 
				
			||||||
 | 
					// @Param pageIndex query int false "页码"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineReversePosition}} "{"code": 200, "data": [...]}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-position [get]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReversePosition) GetPage(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReversePositionGetPageReq{}
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
						list := make([]dto.LineReversePositionListResp, 0)
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.GetPage(&req, p, &list, &count)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取反单管理-仓位失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get 获取反单管理-仓位
 | 
				
			||||||
 | 
					// @Summary 获取反单管理-仓位
 | 
				
			||||||
 | 
					// @Description 获取反单管理-仓位
 | 
				
			||||||
 | 
					// @Tags 反单管理-仓位
 | 
				
			||||||
 | 
					// @Param id path int false "id"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response{data=models.LineReversePosition} "{"code": 200, "data": [...]}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-position/{id} [get]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReversePosition) Get(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReversePositionGetReq{}
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var object models.LineReversePosition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
						err = s.Get(&req, p, &object)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取反单管理-仓位失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK(object, "查询成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Insert 创建反单管理-仓位
 | 
				
			||||||
 | 
					// @Summary 创建反单管理-仓位
 | 
				
			||||||
 | 
					// @Description 创建反单管理-仓位
 | 
				
			||||||
 | 
					// @Tags 反单管理-仓位
 | 
				
			||||||
 | 
					// @Accept application/json
 | 
				
			||||||
 | 
					// @Product application/json
 | 
				
			||||||
 | 
					// @Param data body dto.LineReversePositionInsertReq true "data"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-position [post]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReversePosition) Insert(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReversePositionInsertReq{}
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 设置创建人
 | 
				
			||||||
 | 
						req.SetCreateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Insert(&req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("创建反单管理-仓位失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK(req.GetId(), "创建成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update 修改反单管理-仓位
 | 
				
			||||||
 | 
					// @Summary 修改反单管理-仓位
 | 
				
			||||||
 | 
					// @Description 修改反单管理-仓位
 | 
				
			||||||
 | 
					// @Tags 反单管理-仓位
 | 
				
			||||||
 | 
					// @Accept application/json
 | 
				
			||||||
 | 
					// @Product application/json
 | 
				
			||||||
 | 
					// @Param id path int true "id"
 | 
				
			||||||
 | 
					// @Param data body dto.LineReversePositionUpdateReq true "body"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-position/{id} [put]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReversePosition) Update(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReversePositionUpdateReq{}
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.SetUpdateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Update(&req, p)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("修改反单管理-仓位失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK(req.GetId(), "修改成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Delete 删除反单管理-仓位
 | 
				
			||||||
 | 
					// @Summary 删除反单管理-仓位
 | 
				
			||||||
 | 
					// @Description 删除反单管理-仓位
 | 
				
			||||||
 | 
					// @Tags 反单管理-仓位
 | 
				
			||||||
 | 
					// @Param data body dto.LineReversePositionDeleteReq true "body"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-position [delete]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReversePosition) Delete(c *gin.Context) {
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
						req := dto.LineReversePositionDeleteReq{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// req.SetUpdateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Remove(&req, p)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("删除反单管理-仓位失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK(req.GetId(), "删除成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClosePosition 平仓
 | 
				
			||||||
 | 
					func (e LineReversePosition) ClosePosition(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReversePositionCloseReq{}
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						userId := user.GetUserId(c)
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.ClosePosition(&req, p, userId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("平仓失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK(nil, "平仓成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClosePositionBatch 批量平仓
 | 
				
			||||||
 | 
					func (e LineReversePosition) ClosePositionBatch(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReversePositionCloseBatchReq{}
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						userId := user.GetUserId(c)
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
						errs := make([]string, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.ClosePositionBatch(&req, p, userId, &errs)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("批量平仓失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(errs) > 0 {
 | 
				
			||||||
 | 
							content := strings.Join(errs, "</br>")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							e.OK(content, "批量平仓部分失败")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK(nil, "批量平仓成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 清除所有
 | 
				
			||||||
 | 
					func (e LineReversePosition) CleanAll(c *gin.Context) {
 | 
				
			||||||
 | 
						s := service.LineReversePosition{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						userId := user.GetUserId(c)
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.CleanAll(p, userId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("清除所有仓位失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK(nil, "清除所有仓位成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										158
									
								
								app/admin/apis/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								app/admin/apis/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					package apis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/api"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
 | 
				
			||||||
 | 
						_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service/dto"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseSetting struct {
 | 
				
			||||||
 | 
						api.Api
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPage 获取反单下单配置列表
 | 
				
			||||||
 | 
					// @Summary 获取反单下单配置列表
 | 
				
			||||||
 | 
					// @Description 获取反单下单配置列表
 | 
				
			||||||
 | 
					// @Tags 反单下单配置
 | 
				
			||||||
 | 
					// @Param pageSize query int false "页条数"
 | 
				
			||||||
 | 
					// @Param pageIndex query int false "页码"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineReverseSetting}} "{"code": 200, "data": [...]}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-setting [get]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseSetting) GetPage(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReverseSettingGetPageReq{}
 | 
				
			||||||
 | 
						s := service.LineReverseSetting{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
						list := make([]models.LineReverseSetting, 0)
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.GetPage(&req, p, &list, &count)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取反单下单配置失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get 获取反单下单配置
 | 
				
			||||||
 | 
					// @Summary 获取反单下单配置
 | 
				
			||||||
 | 
					// @Description 获取反单下单配置
 | 
				
			||||||
 | 
					// @Tags 反单下单配置
 | 
				
			||||||
 | 
					// @Param id path int false "id"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response{data=models.LineReverseSetting} "{"code": 200, "data": [...]}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-setting/{id} [get]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseSetting) Get(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReverseSettingGetReq{}
 | 
				
			||||||
 | 
						s := service.LineReverseSetting{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var object models.LineReverseSetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
						err = s.Get(&req, p, &object)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("获取反单下单配置失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK(object, "查询成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Insert 创建反单下单配置
 | 
				
			||||||
 | 
					// @Summary 创建反单下单配置
 | 
				
			||||||
 | 
					// @Description 创建反单下单配置
 | 
				
			||||||
 | 
					// @Tags 反单下单配置
 | 
				
			||||||
 | 
					// @Accept application/json
 | 
				
			||||||
 | 
					// @Product application/json
 | 
				
			||||||
 | 
					// @Param data body dto.LineReverseSettingInsertReq true "data"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-setting [post]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseSetting) Insert(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReverseSettingInsertReq{}
 | 
				
			||||||
 | 
						s := service.LineReverseSetting{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 设置创建人
 | 
				
			||||||
 | 
						req.SetCreateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Insert(&req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("创建反单下单配置失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e.OK(req.GetId(), "创建成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update 修改反单下单配置
 | 
				
			||||||
 | 
					// @Summary 修改反单下单配置
 | 
				
			||||||
 | 
					// @Description 修改反单下单配置
 | 
				
			||||||
 | 
					// @Tags 反单下单配置
 | 
				
			||||||
 | 
					// @Accept application/json
 | 
				
			||||||
 | 
					// @Product application/json
 | 
				
			||||||
 | 
					// @Param id path int true "id"
 | 
				
			||||||
 | 
					// @Param data body dto.LineReverseSettingUpdateReq true "body"
 | 
				
			||||||
 | 
					// @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}"
 | 
				
			||||||
 | 
					// @Router /api/v1/line-reverse-setting/{id} [put]
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					func (e LineReverseSetting) Update(c *gin.Context) {
 | 
				
			||||||
 | 
						req := dto.LineReverseSettingUpdateReq{}
 | 
				
			||||||
 | 
						s := service.LineReverseSetting{}
 | 
				
			||||||
 | 
						err := e.MakeContext(c).
 | 
				
			||||||
 | 
							MakeOrm().
 | 
				
			||||||
 | 
							Bind(&req).
 | 
				
			||||||
 | 
							MakeService(&s.Service).
 | 
				
			||||||
 | 
							Errors
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Logger.Error(err)
 | 
				
			||||||
 | 
							e.Error(500, err, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.SetUpdateBy(user.GetUserId(c))
 | 
				
			||||||
 | 
						p := actions.GetPermissionFromContext(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.Update(&req, p)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Error(500, err, fmt.Sprintf("修改反单下单配置失败,\r\n失败信息 %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						e.OK(req.GetId(), "修改成功")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,29 +2,31 @@ package models
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"go-admin/common/models"
 | 
						"go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LineApiUser struct {
 | 
					type LineApiUser struct {
 | 
				
			||||||
	models.Model
 | 
						models.Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ExchangeType string `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型(字典 exchange_type)"`
 | 
						ExchangeType    string          `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型(字典 exchange_type)"`
 | 
				
			||||||
	UserId       int64  `json:"userId" gorm:"type:int unsigned;comment:用户id"`
 | 
						UserId          int64           `json:"userId" gorm:"type:int unsigned;comment:用户id"`
 | 
				
			||||||
	JysId        int64  `json:"jysId" gorm:"type:int;comment:关联交易所账号id"`
 | 
						ApiName         string          `json:"apiName" gorm:"type:varchar(255);comment:api用户名"`
 | 
				
			||||||
	ApiName      string `json:"apiName" gorm:"type:varchar(255);comment:api用户名"`
 | 
						ApiKey          string          `json:"apiKey" gorm:"type:varchar(255);comment:apiKey"`
 | 
				
			||||||
	ApiKey       string `json:"apiKey" gorm:"type:varchar(255);comment:apiKey"`
 | 
						ApiSecret       string          `json:"apiSecret" gorm:"type:varchar(255);comment:apiSecret"`
 | 
				
			||||||
	ApiSecret    string `json:"apiSecret" gorm:"type:varchar(255);comment:apiSecret"`
 | 
						IpAddress       string          `json:"ipAddress" gorm:"type:varchar(255);comment:代理地址"`
 | 
				
			||||||
	IpAddress    string `json:"ipAddress" gorm:"type:varchar(255);comment:代理地址"`
 | 
						UserPass        string          `json:"userPass" gorm:"type:varchar(255);comment:代码账号密码"`
 | 
				
			||||||
	UserPass     string `json:"userPass" gorm:"type:varchar(255);comment:代码账号密码"`
 | 
						Affiliation     int64           `json:"affiliation" gorm:"type:int;comment:归属:1=现货,2=合约,3=现货合约"`
 | 
				
			||||||
	AdminId      int64  `json:"adminId" gorm:"type:int unsigned;comment:管理员id"`
 | 
						AdminShow       int64           `json:"adminShow" gorm:"type:int;comment:是否超管可见:1=是,0=否"`
 | 
				
			||||||
	Affiliation  int64  `json:"affiliation" gorm:"type:int;comment:归属:1=现货,2=合约,3=现货合约"`
 | 
						Site            string          `json:"site" gorm:"type:enum('1','2','3');comment:允许下单的方向:1=多;2=空;3=多空"`
 | 
				
			||||||
	AdminShow    int64  `json:"adminShow" gorm:"type:int;comment:是否超管可见:1=是,0=否"`
 | 
						Subordinate     string          `json:"subordinate" gorm:"type:enum('0','1','2');comment:从属关系:0=未绑定关系;1=主账号;2=副帐号"`
 | 
				
			||||||
	Site         string `json:"site" gorm:"type:enum('1','2','3');comment:允许下单的方向:1=多;2=空;3=多空"`
 | 
						GroupId         int64           `json:"groupId" gorm:"type:int unsigned;comment:所属组id"`
 | 
				
			||||||
	Subordinate  string `json:"subordinate" gorm:"type:enum('0','1','2');comment:从属关系:0=未绑定关系;1=主账号;2=副帐号"`
 | 
						OpenStatus      int64           `json:"openStatus" gorm:"type:int unsigned;comment:开启状态 0=关闭 1=开启"`
 | 
				
			||||||
	GroupId      int64  `json:"groupId" gorm:"type:int unsigned;comment:所属组id"`
 | 
						ReverseStatus   int             `json:"reverseStatus" gorm:"type:tinyint unsigned;comment:反向开仓:1=开启;2=关闭"`
 | 
				
			||||||
	OpenStatus   int64  `json:"openStatus" gorm:"type:int unsigned;comment:开启状态 0=关闭 1=开启"`
 | 
						ReverseApiId    int             `json:"reverseApiId" gorm:"type:bigint;comment:反向apiId"`
 | 
				
			||||||
 | 
						OrderProportion decimal.Decimal `json:"orderProportion" gorm:"type:decimal(10,2);comment:委托比例"`
 | 
				
			||||||
	SpotLastTime    string `json:"spotLastTime" gorm:"-"`    //现货websocket最后通信时间
 | 
						SpotLastTime    string          `json:"spotLastTime" gorm:"-"`    //现货websocket最后通信时间
 | 
				
			||||||
	FuturesLastTime string `json:"futuresLastTime" gorm:"-"` //合约websocket最后通信时间
 | 
						FuturesLastTime string          `json:"futuresLastTime" gorm:"-"` //合约websocket最后通信时间
 | 
				
			||||||
	models.ModelTime
 | 
						models.ModelTime
 | 
				
			||||||
	models.ControlBy
 | 
						models.ControlBy
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										52
									
								
								app/admin/models/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/admin/models/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseOrder struct {
 | 
				
			||||||
 | 
						models.Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PositionId int `json:"positionId" gorm:"type:bigint;comment:仓位id"`
 | 
				
			||||||
 | 
						ApiId      int `json:"apiId" gorm:"type:bigint;comment:api id"`
 | 
				
			||||||
 | 
						Category   int `json:"category" gorm:"type:tinyint;comment:分类 0-原始订单 1-反单"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						OrderSn       string          `json:"orderSn" gorm:"type:varchar(50);comment:订单号 0-主单 1-止盈 2-止损"`
 | 
				
			||||||
 | 
						OrderId       string          `json:"orderId" gorm:"type:varchar(50);comment:币安订单号"`
 | 
				
			||||||
 | 
						FollowOrderSn string          `json:"followOrderSn" gorm:"type:varchar(50);comment:跟随币安订单号"`
 | 
				
			||||||
 | 
						Symbol        string          `json:"symbol" gorm:"type:varchar(20);comment:交易对"`
 | 
				
			||||||
 | 
						Type          string          `json:"type" gorm:"type:varchar(20);comment:交易类型"`
 | 
				
			||||||
 | 
						OrderType     int             `json:"orderType" gorm:"type:tinyint;comment:订单类型 0-主单 1-止盈  2-止损 3-减仓单 4-平仓单"`
 | 
				
			||||||
 | 
						BuyPrice      decimal.Decimal `json:"buyPrice" gorm:"type:decimal(18,8);comment:购买金额"`
 | 
				
			||||||
 | 
						Price         decimal.Decimal `json:"price" gorm:"type:decimal(18,8);comment:委托价格"`
 | 
				
			||||||
 | 
						TotalNum      decimal.Decimal `json:"totalNum" gorm:"type:decimal(18,8);comment:总成交数量"`
 | 
				
			||||||
 | 
						PriceU        decimal.Decimal `json:"priceU" gorm:"type:decimal(18,8);comment:委托价格(U)"`
 | 
				
			||||||
 | 
						FinalPrice    decimal.Decimal `json:"finalPrice" gorm:"type:decimal(18,8);comment:实际成交价"`
 | 
				
			||||||
 | 
						PositionSide  string          `json:"positionSide" gorm:"type:varchar(10);comment:持仓方向 LONG-多 SHORT-空"`
 | 
				
			||||||
 | 
						Side          string          `json:"side" gorm:"type:varchar(10);comment:买卖方向 SELL-卖 BUY-买"`
 | 
				
			||||||
 | 
						SignPrice     decimal.Decimal `json:"signPrice" gorm:"type:decimal(18,8);comment:行情价"`
 | 
				
			||||||
 | 
						TriggerTime   *time.Time      `json:"triggerTime" gorm:"type:datetime;comment:触发时间"`
 | 
				
			||||||
 | 
						Status        int             `json:"status" gorm:"type:tinyint;comment:状态 1-待下单 2-已下单 3-已成交 6-已取消 7-已过期 8-已失败"`
 | 
				
			||||||
 | 
						IsAddPosition int             `json:"isAddPosition" gorm:"type:tinyint;comment:是否增加仓位 1-否 2-是"`
 | 
				
			||||||
 | 
						IsReduce      int             `json:"isReduce" gorm:"type:tinyint;comment:是否减少仓位 1-否 2-是"`
 | 
				
			||||||
 | 
						Remark        string          `json:"remark" gorm:"type:varchar(255);comment:备注"`
 | 
				
			||||||
 | 
						models.ModelTime
 | 
				
			||||||
 | 
						models.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (LineReverseOrder) TableName() string {
 | 
				
			||||||
 | 
						return "line_reverse_order"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *LineReverseOrder) Generate() models.ActiveRecord {
 | 
				
			||||||
 | 
						o := *e
 | 
				
			||||||
 | 
						return &o
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *LineReverseOrder) GetId() interface{} {
 | 
				
			||||||
 | 
						return e.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								app/admin/models/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/admin/models/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePosition struct {
 | 
				
			||||||
 | 
						models.Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PositionNo          string          `json:"positionNo" gorm:"type:varchar(18);comment:仓位编号"`
 | 
				
			||||||
 | 
						ApiId               int             `json:"apiId" gorm:"type:bigint;comment:api_id"`
 | 
				
			||||||
 | 
						TotalAmount         decimal.Decimal `json:"totalAmount" gorm:"type:decimal(18,8);comment:总仓位"`
 | 
				
			||||||
 | 
						Amount              decimal.Decimal `json:"amount" gorm:"type:decimal(18,8);comment:仓位"`
 | 
				
			||||||
 | 
						ReverseApiId        int             `json:"reverseApiId" gorm:"type:bigint;comment:反单api_id"`
 | 
				
			||||||
 | 
						TotalReverseAmount  decimal.Decimal `json:"totalReverseAmount" gorm:"type:decimal(18,8);comment:总反单仓位"`
 | 
				
			||||||
 | 
						ReverseAmount       decimal.Decimal `json:"reverseAmount" gorm:"type:decimal(18,8);comment:反单仓位"`
 | 
				
			||||||
 | 
						Side                string          `json:"side" gorm:"type:varchar(10);comment:买卖方向 BUY SELL"`
 | 
				
			||||||
 | 
						PositionSide        string          `json:"positionSide" gorm:"type:varchar(10);comment:持仓方向 LONG SHORT"`
 | 
				
			||||||
 | 
						Symbol              string          `json:"symbol" gorm:"type:varchar(20);comment:交易对"`
 | 
				
			||||||
 | 
						Status              int             `json:"status" gorm:"type:tinyint;comment:仓位状态 1-已开仓 2-已平仓"`
 | 
				
			||||||
 | 
						ReverseStatus       int             `json:"reverseStatus" gorm:"type:tinyint;comment:反单仓位状态 1-已开仓 2-已平仓"`
 | 
				
			||||||
 | 
						AveragePrice        decimal.Decimal `json:"averagePrice" gorm:"type:decimal(18,8);comment:主单平均价格"`
 | 
				
			||||||
 | 
						ReverseAveragePrice decimal.Decimal `json:"reverseAveragePrice" gorm:"type:decimal(18,8);comment:反单平均价格"`
 | 
				
			||||||
 | 
						Version             int             `json:"version" gorm:"type:int;default:0;comment:版本号,用于乐观锁控制"`
 | 
				
			||||||
 | 
						models.ModelTime
 | 
				
			||||||
 | 
						models.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (LineReversePosition) TableName() string {
 | 
				
			||||||
 | 
						return "line_reverse_position"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *LineReversePosition) Generate() models.ActiveRecord {
 | 
				
			||||||
 | 
						o := *e
 | 
				
			||||||
 | 
						return &o
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *LineReversePosition) GetId() interface{} {
 | 
				
			||||||
 | 
						return e.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								app/admin/models/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/admin/models/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseSetting struct {
 | 
				
			||||||
 | 
						models.Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ReverseOrderType    string          `json:"reverseOrderType" redis:"reverseOrderType" gorm:"type:varchar(10);comment:反单下单类型 LIMIT-限价 MARKET-市价"`
 | 
				
			||||||
 | 
						ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" redis:"reversePremiumRatio" gorm:"type:decimal(10,2);comment:溢价百分比"`
 | 
				
			||||||
 | 
						TakeProfitRatio     decimal.Decimal `json:"takeProfitRatio" redis:"takeProfitRatio" gorm:"type:decimal(10,2);comment:止盈百分比"`
 | 
				
			||||||
 | 
						StopLossRatio       decimal.Decimal `json:"stopLossRatio" redis:"stopLossRatio" gorm:"type:decimal(10,2);comment:止损百分比"`
 | 
				
			||||||
 | 
						models.ModelTime
 | 
				
			||||||
 | 
						models.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (LineReverseSetting) TableName() string {
 | 
				
			||||||
 | 
						return "line_reverse_setting"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) Generate() models.ActiveRecord {
 | 
				
			||||||
 | 
						o := *e
 | 
				
			||||||
 | 
						return &o
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) GetId() interface{} {
 | 
				
			||||||
 | 
						return e.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -27,5 +27,11 @@ func registerLineApiUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMi
 | 
				
			|||||||
		r.POST("bind", api.Bind)               //绑定从属关系
 | 
							r.POST("bind", api.Bind)               //绑定从属关系
 | 
				
			||||||
		r.POST("getUser", api.GetUser)         //获取未绑定的用户
 | 
							r.POST("getUser", api.GetUser)         //获取未绑定的用户
 | 
				
			||||||
		r.POST("getMainUser", api.GetMainUser) //获取获取主账号的用户
 | 
							r.POST("getMainUser", api.GetMainUser) //获取获取主账号的用户
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r.GET("unbind-reverse", api.GetUnBindReverseApiUser) //获取未绑定下反单用户
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r.GET("reverse-options", api.GetReverseApiOptions) //获取可用反单api用户
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r.GET("reverse-options-all", api.GetReverseApiOptionsAll) //获取全部启用的反单api用户
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								app/admin/router/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/router/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/apis"
 | 
				
			||||||
 | 
						"go-admin/common/middleware"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						routerCheckRole = append(routerCheckRole, registerLineReverseOrderRouter)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerLineReverseOrderRouter
 | 
				
			||||||
 | 
					func registerLineReverseOrderRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
 | 
				
			||||||
 | 
						api := apis.LineReverseOrder{}
 | 
				
			||||||
 | 
						r := v1.Group("/line-reverse-order").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							r.GET("", actions.PermissionAction(), api.GetPage)
 | 
				
			||||||
 | 
							r.GET("/:id", actions.PermissionAction(), api.Get)
 | 
				
			||||||
 | 
							r.POST("", api.Insert)
 | 
				
			||||||
 | 
							r.PUT("/:id", actions.PermissionAction(), api.Update)
 | 
				
			||||||
 | 
							r.DELETE("", api.Delete)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								app/admin/router/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/admin/router/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/apis"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
						"go-admin/common/middleware"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						routerCheckRole = append(routerCheckRole, registerLineReversePositionRouter)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerLineReversePositionRouter
 | 
				
			||||||
 | 
					func registerLineReversePositionRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
 | 
				
			||||||
 | 
						api := apis.LineReversePosition{}
 | 
				
			||||||
 | 
						r := v1.Group("/line-reverse-position").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							r.GET("", actions.PermissionAction(), api.GetPage)
 | 
				
			||||||
 | 
							r.GET("/:id", actions.PermissionAction(), api.Get)
 | 
				
			||||||
 | 
							r.POST("", api.Insert)
 | 
				
			||||||
 | 
							r.PUT("/:id", actions.PermissionAction(), api.Update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							//清理所有
 | 
				
			||||||
 | 
							r.DELETE("/clean-all", actions.PermissionAction(), api.CleanAll)
 | 
				
			||||||
 | 
							r.DELETE("", api.Delete)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r.PUT("close/:id", actions.PermissionAction(), api.ClosePosition)
 | 
				
			||||||
 | 
							r.PUT("close-batch", actions.PermissionAction(), api.ClosePositionBatch)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								app/admin/router/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/router/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/apis"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
						"go-admin/common/middleware"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						routerCheckRole = append(routerCheckRole, registerLineReverseSettingRouter)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerLineReverseSettingRouter
 | 
				
			||||||
 | 
					func registerLineReverseSettingRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
 | 
				
			||||||
 | 
						api := apis.LineReverseSetting{}
 | 
				
			||||||
 | 
						r := v1.Group("/line-reverse-setting").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							r.GET("", actions.PermissionAction(), api.GetPage)
 | 
				
			||||||
 | 
							r.GET("/:id", actions.PermissionAction(), api.Get)
 | 
				
			||||||
 | 
							r.POST("", api.Insert)
 | 
				
			||||||
 | 
							r.PUT("/:id", actions.PermissionAction(), api.Update)
 | 
				
			||||||
 | 
							// r.DELETE("", api.Delete)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,9 +1,12 @@
 | 
				
			|||||||
package dto
 | 
					package dto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"go-admin/app/admin/models"
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
	"go-admin/common/dto"
 | 
						"go-admin/common/dto"
 | 
				
			||||||
	common "go-admin/common/models"
 | 
						common "go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LineApiUserGetPageReq struct {
 | 
					type LineApiUserGetPageReq struct {
 | 
				
			||||||
@ -41,44 +44,66 @@ func (m *LineApiUserGetPageReq) GetNeedSearch() interface{} {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LineApiUserInsertReq struct {
 | 
					type LineApiUserInsertReq struct {
 | 
				
			||||||
	Id           int    `json:"-" comment:"id"` // id
 | 
						Id              int             `json:"-" comment:"id"` // id
 | 
				
			||||||
	ExchangeType string `json:"exchangeType" comment:"交易所code"`
 | 
						ExchangeType    string          `json:"exchangeType" comment:"交易所code"`
 | 
				
			||||||
	UserId       int64  `json:"userId" comment:"用户id"`
 | 
						UserId          int64           `json:"userId" comment:"用户id"`
 | 
				
			||||||
	JysId        int64  `json:"jysId" comment:"关联交易所账号id"`
 | 
						JysId           int64           `json:"jysId" comment:"关联交易所账号id"`
 | 
				
			||||||
	ApiName      string `json:"apiName" comment:"api用户名"`
 | 
						ApiName         string          `json:"apiName" comment:"api用户名"`
 | 
				
			||||||
	ApiKey       string `json:"apiKey" comment:"apiKey"`
 | 
						ApiKey          string          `json:"apiKey" comment:"apiKey"`
 | 
				
			||||||
	ApiSecret    string `json:"apiSecret" comment:"apiSecret"`
 | 
						ApiSecret       string          `json:"apiSecret" comment:"apiSecret"`
 | 
				
			||||||
	IpAddress    string `json:"ipAddress" comment:"代理地址"`
 | 
						IpAddress       string          `json:"ipAddress" comment:"代理地址"`
 | 
				
			||||||
	UserPass     string `json:"userPass" comment:"代码账号密码"`
 | 
						UserPass        string          `json:"userPass" comment:"代码账号密码"`
 | 
				
			||||||
	AdminId      int64  `json:"adminId" comment:"管理员id"`
 | 
						Affiliation     int64           `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"`
 | 
				
			||||||
	Affiliation  int64  `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"`
 | 
						AdminShow       int64           `json:"adminShow" comment:"是否超管可见:1=是,0=否"`
 | 
				
			||||||
	AdminShow    int64  `json:"adminShow" comment:"是否超管可见:1=是,0=否"`
 | 
						Site            string          `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"`
 | 
				
			||||||
	Site         string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"`
 | 
						Subordinate     string          `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"`
 | 
				
			||||||
	Subordinate  string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"`
 | 
						GroupId         int64           `json:"groupId" comment:"所属组id"`
 | 
				
			||||||
	GroupId      int64  `json:"groupId" comment:"所属组id"`
 | 
						OpenStatus      int64           `json:"openStatus" comment:"开启状态 0=关闭 1=开启"`
 | 
				
			||||||
	OpenStatus   int64  `json:"openStatus" comment:"开启状态 0=关闭 1=开启"`
 | 
						ReverseStatus   int             `json:"reverseStatus" comment:"反向下单状态 1-开启 2-关闭"`
 | 
				
			||||||
 | 
						ReverseApiId    int             `json:"reverseApiId" comment:"反向下单apiId"`
 | 
				
			||||||
 | 
						OrderProportion decimal.Decimal `json:"orderProportion" comment:"委托比例"`
 | 
				
			||||||
	common.ControlBy
 | 
						common.ControlBy
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineApiUserInsertReq) Valid() error {
 | 
				
			||||||
 | 
						if s.ReverseStatus == 1 {
 | 
				
			||||||
 | 
							if s.OrderProportion.Cmp(decimal.Zero) <= 0 {
 | 
				
			||||||
 | 
								return errors.New("委托比例必须大于0")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if s.ReverseStatus == 1 && s.ReverseApiId == 0 {
 | 
				
			||||||
 | 
								return errors.New("反向下单api不能为空")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *LineApiUserInsertReq) Generate(model *models.LineApiUser) {
 | 
					func (s *LineApiUserInsertReq) Generate(model *models.LineApiUser) {
 | 
				
			||||||
	if s.Id == 0 {
 | 
						if s.Id == 0 {
 | 
				
			||||||
		model.Model = common.Model{Id: s.Id}
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	model.ExchangeType = s.ExchangeType
 | 
						model.ExchangeType = s.ExchangeType
 | 
				
			||||||
	model.UserId = s.UserId
 | 
						model.UserId = s.UserId
 | 
				
			||||||
	model.JysId = s.JysId
 | 
					 | 
				
			||||||
	model.ApiName = s.ApiName
 | 
						model.ApiName = s.ApiName
 | 
				
			||||||
	model.ApiKey = s.ApiKey
 | 
						model.ApiKey = s.ApiKey
 | 
				
			||||||
	model.ApiSecret = s.ApiSecret
 | 
						model.ApiSecret = s.ApiSecret
 | 
				
			||||||
	model.IpAddress = s.IpAddress
 | 
						model.IpAddress = s.IpAddress
 | 
				
			||||||
	model.UserPass = s.UserPass
 | 
						model.UserPass = s.UserPass
 | 
				
			||||||
	model.AdminId = s.AdminId
 | 
					 | 
				
			||||||
	model.Affiliation = s.Affiliation
 | 
						model.Affiliation = s.Affiliation
 | 
				
			||||||
	model.AdminShow = s.AdminShow
 | 
						model.AdminShow = s.AdminShow
 | 
				
			||||||
	model.Site = s.Site
 | 
						model.Site = s.Site
 | 
				
			||||||
	model.Subordinate = s.Subordinate
 | 
						model.Subordinate = s.Subordinate
 | 
				
			||||||
	model.GroupId = s.GroupId
 | 
						model.GroupId = s.GroupId
 | 
				
			||||||
	model.OpenStatus = s.OpenStatus
 | 
						model.OpenStatus = s.OpenStatus
 | 
				
			||||||
 | 
						model.ReverseStatus = s.ReverseStatus
 | 
				
			||||||
 | 
						model.OrderProportion = s.OrderProportion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if model.ReverseStatus == 1 {
 | 
				
			||||||
 | 
							model.ReverseApiId = s.ReverseApiId
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							model.ReverseApiId = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
 | 
						model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,44 +112,66 @@ func (s *LineApiUserInsertReq) GetId() interface{} {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LineApiUserUpdateReq struct {
 | 
					type LineApiUserUpdateReq struct {
 | 
				
			||||||
	Id           int    `uri:"id" comment:"id"` // id
 | 
						Id              int             `uri:"id" comment:"id"` // id
 | 
				
			||||||
	ExchangeType string `json:"exchangeType" comment:"交易所code"`
 | 
						ExchangeType    string          `json:"exchangeType" comment:"交易所code"`
 | 
				
			||||||
	UserId       int64  `json:"userId" comment:"用户id"`
 | 
						UserId          int64           `json:"userId" comment:"用户id"`
 | 
				
			||||||
	JysId        int64  `json:"jysId" comment:"关联交易所账号id"`
 | 
						JysId           int64           `json:"jysId" comment:"关联交易所账号id"`
 | 
				
			||||||
	ApiName      string `json:"apiName" comment:"api用户名"`
 | 
						ApiName         string          `json:"apiName" comment:"api用户名"`
 | 
				
			||||||
	ApiKey       string `json:"apiKey" comment:"apiKey"`
 | 
						ApiKey          string          `json:"apiKey" comment:"apiKey"`
 | 
				
			||||||
	ApiSecret    string `json:"apiSecret" comment:"apiSecret"`
 | 
						ApiSecret       string          `json:"apiSecret" comment:"apiSecret"`
 | 
				
			||||||
	IpAddress    string `json:"ipAddress" comment:"代理地址"`
 | 
						IpAddress       string          `json:"ipAddress" comment:"代理地址"`
 | 
				
			||||||
	UserPass     string `json:"userPass" comment:"代码账号密码"`
 | 
						UserPass        string          `json:"userPass" comment:"代码账号密码"`
 | 
				
			||||||
	AdminId      int64  `json:"adminId" comment:"管理员id"`
 | 
						Affiliation     int64           `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"`
 | 
				
			||||||
	Affiliation  int64  `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"`
 | 
						AdminShow       int64           `json:"adminShow" comment:"是否超管可见:1=是,0=否"`
 | 
				
			||||||
	AdminShow    int64  `json:"adminShow" comment:"是否超管可见:1=是,0=否"`
 | 
						Site            string          `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"`
 | 
				
			||||||
	Site         string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"`
 | 
						Subordinate     string          `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"`
 | 
				
			||||||
	Subordinate  string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"`
 | 
						GroupId         int64           `json:"groupId" comment:"所属组id"`
 | 
				
			||||||
	GroupId      int64  `json:"groupId" comment:"所属组id"`
 | 
						OpenStatus      int64           `json:"openStatus" comment:"开启状态 0=关闭 1=开启"`
 | 
				
			||||||
	OpenStatus   int64  `json:"openStatus" comment:"开启状态 0=关闭 1=开启"`
 | 
						ReverseStatus   int             `json:"reverseStatus" comment:"反向下单状态 1-开启 2-关闭"`
 | 
				
			||||||
 | 
						ReverseApiId    int             `json:"reverseApiId" comment:"反向下单apiId"`
 | 
				
			||||||
 | 
						OrderProportion decimal.Decimal `json:"orderProportion" comment:"委托比例"`
 | 
				
			||||||
	common.ControlBy
 | 
						common.ControlBy
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineApiUserUpdateReq) Valid() error {
 | 
				
			||||||
 | 
						if s.ReverseStatus == 1 {
 | 
				
			||||||
 | 
							if s.OrderProportion.Cmp(decimal.Zero) <= 0 {
 | 
				
			||||||
 | 
								return errors.New("委托比例必须大于0")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if s.ReverseStatus == 1 && s.ReverseApiId == 0 {
 | 
				
			||||||
 | 
								return errors.New("反向下单api不能为空")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *LineApiUserUpdateReq) Generate(model *models.LineApiUser) {
 | 
					func (s *LineApiUserUpdateReq) Generate(model *models.LineApiUser) {
 | 
				
			||||||
	if s.Id == 0 {
 | 
						if s.Id == 0 {
 | 
				
			||||||
		model.Model = common.Model{Id: s.Id}
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	model.ExchangeType = s.ExchangeType
 | 
						model.ExchangeType = s.ExchangeType
 | 
				
			||||||
	model.UserId = s.UserId
 | 
						model.UserId = s.UserId
 | 
				
			||||||
	model.JysId = s.JysId
 | 
					 | 
				
			||||||
	model.ApiName = s.ApiName
 | 
						model.ApiName = s.ApiName
 | 
				
			||||||
	model.ApiKey = s.ApiKey
 | 
						model.ApiKey = s.ApiKey
 | 
				
			||||||
	model.ApiSecret = s.ApiSecret
 | 
						model.ApiSecret = s.ApiSecret
 | 
				
			||||||
	model.IpAddress = s.IpAddress
 | 
						model.IpAddress = s.IpAddress
 | 
				
			||||||
	model.UserPass = s.UserPass
 | 
						model.UserPass = s.UserPass
 | 
				
			||||||
	model.AdminId = s.AdminId
 | 
					 | 
				
			||||||
	model.Affiliation = s.Affiliation
 | 
						model.Affiliation = s.Affiliation
 | 
				
			||||||
	model.AdminShow = s.AdminShow
 | 
						model.AdminShow = s.AdminShow
 | 
				
			||||||
	model.Site = s.Site
 | 
						model.Site = s.Site
 | 
				
			||||||
	model.Subordinate = s.Subordinate
 | 
						model.Subordinate = s.Subordinate
 | 
				
			||||||
	model.GroupId = s.GroupId
 | 
						model.GroupId = s.GroupId
 | 
				
			||||||
	model.OpenStatus = s.OpenStatus
 | 
						model.OpenStatus = s.OpenStatus
 | 
				
			||||||
 | 
						model.ReverseStatus = s.ReverseStatus
 | 
				
			||||||
 | 
						model.OrderProportion = s.OrderProportion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if model.ReverseStatus == 1 {
 | 
				
			||||||
 | 
							model.ReverseApiId = s.ReverseApiId
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							model.ReverseApiId = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
 | 
						model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -159,3 +206,26 @@ type LineApiUserBindSubordinateReq struct {
 | 
				
			|||||||
type GetMainUserReq struct {
 | 
					type GetMainUserReq struct {
 | 
				
			||||||
	ExchangeType string `json:"exchangeType"`
 | 
						ExchangeType string `json:"exchangeType"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetUnBindReverseReq struct {
 | 
				
			||||||
 | 
						ExchangeType string `json:"exchangeType" form:"exchangeType"`
 | 
				
			||||||
 | 
						ApiId        int    `json:"apiId" form:"apiId" comment:"当前apiId"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 未绑定反向下单查询返回结构体
 | 
				
			||||||
 | 
					type UnBindReverseResp struct {
 | 
				
			||||||
 | 
						Id       int    `json:"id"`
 | 
				
			||||||
 | 
						UserId   int64  `json:"userId"`
 | 
				
			||||||
 | 
						Disabled bool   `json:"disabled"`
 | 
				
			||||||
 | 
						ApiName  string `json:"apiName"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetReverseApiOptionsReq struct {
 | 
				
			||||||
 | 
						Id    int `json:"apiId" form:"id"`
 | 
				
			||||||
 | 
						ApiId int `json:"apiId" form:"apiId"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineApiUserOptionResp struct {
 | 
				
			||||||
 | 
						Id   int    `json:"id"`
 | 
				
			||||||
 | 
						Name string `json:"name"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										162
									
								
								app/admin/service/dto/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								app/admin/service/dto/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,162 @@
 | 
				
			|||||||
 | 
					package dto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/common/dto"
 | 
				
			||||||
 | 
						common "go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseOrderGetPageReq struct {
 | 
				
			||||||
 | 
						dto.Pagination `search:"-"`
 | 
				
			||||||
 | 
						OrderSn        string `form:"orderSn"  search:"type:contains;column:order_sn;table:line_reverse_order" comment:"订单号"`
 | 
				
			||||||
 | 
						OrderId        string `form:"orderId"  search:"type:contains;column:order_id;table:line_reverse_order" comment:"币安订单号"`
 | 
				
			||||||
 | 
						FollowOrderSn  string `form:"followOrderSn"  search:"type:contains;column:follow_order_sn;table:line_reverse_order" comment:"跟随币安订单号"`
 | 
				
			||||||
 | 
						OrderType      int    `form:"orderType"  search:"type:exact;column:order_type;table:line_reverse_order" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"`
 | 
				
			||||||
 | 
						PositionSide   string `form:"positionSide"  search:"type:exact;column:position_side;table:line_reverse_order" comment:"持仓方向 LONG-多 SHORT-空"`
 | 
				
			||||||
 | 
						Side           string `form:"side"  search:"type:exact;column:side;table:line_reverse_order" comment:"买卖方向 SELL-卖 BUY-买"`
 | 
				
			||||||
 | 
						Type           string `form:"type"  search:"type:exact;column:type;table:line_reverse_order" comment:"类型 LIMIT-限价 MARKET-市价 "`
 | 
				
			||||||
 | 
						Category       int    `form:"category" search:"-" comment:"类型 0-主单 1-反单"`
 | 
				
			||||||
 | 
						Status         int    `form:"status"  search:"type:exact;column:status;table:line_reverse_order" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"`
 | 
				
			||||||
 | 
						PositionId     int    `form:"positionId" search:"type:exact;column:position_id;table:line_reverse_order" comment:"持仓id"`
 | 
				
			||||||
 | 
						LineReverseOrderOrder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseOrderOrder struct {
 | 
				
			||||||
 | 
						Id            string `form:"idOrder"  search:"type:order;column:id;table:line_reverse_order"`
 | 
				
			||||||
 | 
						PId           string `form:"pIdOrder"  search:"type:order;column:p_id;table:line_reverse_order"`
 | 
				
			||||||
 | 
						OrderSn       string `form:"orderSnOrder"  search:"type:order;column:order_sn;table:line_reverse_order"`
 | 
				
			||||||
 | 
						OrderId       string `form:"orderIdOrder"  search:"type:order;column:order_id;table:line_reverse_order"`
 | 
				
			||||||
 | 
						FollowOrderSn string `form:"followOrderSnOrder"  search:"type:order;column:follow_order_sn;table:line_reverse_order"`
 | 
				
			||||||
 | 
						Symbol        string `form:"symbolOrder"  search:"type:order;column:symbol;table:line_reverse_order"`
 | 
				
			||||||
 | 
						OrderType     string `form:"orderTypeOrder"  search:"type:order;column:order_type;table:line_reverse_order"`
 | 
				
			||||||
 | 
						BuyPrice      string `form:"buyPriceOrder"  search:"type:order;column:buy_price;table:line_reverse_order"`
 | 
				
			||||||
 | 
						Price         string `form:"priceOrder"  search:"type:order;column:price;table:line_reverse_order"`
 | 
				
			||||||
 | 
						PriceU        string `form:"priceUOrder"  search:"type:order;column:price_u;table:line_reverse_order"`
 | 
				
			||||||
 | 
						FinalPrice    string `form:"finalPriceOrder"  search:"type:order;column:final_price;table:line_reverse_order"`
 | 
				
			||||||
 | 
						PositionSide  string `form:"positionSideOrder"  search:"type:order;column:position_side;table:line_reverse_order"`
 | 
				
			||||||
 | 
						Side          string `form:"sideOrder"  search:"type:order;column:side;table:line_reverse_order"`
 | 
				
			||||||
 | 
						SignPrice     string `form:"signPriceOrder"  search:"type:order;column:sign_price;table:line_reverse_order"`
 | 
				
			||||||
 | 
						TriggerTime   string `form:"triggerTimeOrder"  search:"type:order;column:trigger_time;table:line_reverse_order"`
 | 
				
			||||||
 | 
						Status        string `form:"statusOrder"  search:"type:order;column:status;table:line_reverse_order"`
 | 
				
			||||||
 | 
						CreatedAt     string `form:"createdAtOrder"  search:"type:order;column:created_at;table:line_reverse_order"`
 | 
				
			||||||
 | 
						UpdatedAt     string `form:"updatedAtOrder"  search:"type:order;column:updated_at;table:line_reverse_order"`
 | 
				
			||||||
 | 
						DeletedAt     string `form:"deletedAtOrder"  search:"type:order;column:deleted_at;table:line_reverse_order"`
 | 
				
			||||||
 | 
						CreateBy      string `form:"createByOrder"  search:"type:order;column:create_by;table:line_reverse_order"`
 | 
				
			||||||
 | 
						UpdateBy      string `form:"updateByOrder"  search:"type:order;column:update_by;table:line_reverse_order"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *LineReverseOrderGetPageReq) GetNeedSearch() interface{} {
 | 
				
			||||||
 | 
						return *m
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseOrderInsertReq struct {
 | 
				
			||||||
 | 
						Id            int             `json:"-" comment:"主键id"` // 主键id
 | 
				
			||||||
 | 
						PositionId    int             `json:"positionId" comment:"仓位id"`
 | 
				
			||||||
 | 
						OrderSn       string          `json:"orderSn" comment:"订单号"`
 | 
				
			||||||
 | 
						OrderId       string          `json:"orderId" comment:"币安订单号"`
 | 
				
			||||||
 | 
						FollowOrderSn string          `json:"followOrderSn" comment:"跟随币安订单号"`
 | 
				
			||||||
 | 
						Symbol        string          `json:"symbol" comment:"交易对"`
 | 
				
			||||||
 | 
						OrderType     int             `json:"orderType" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"`
 | 
				
			||||||
 | 
						BuyPrice      decimal.Decimal `json:"buyPrice" comment:"购买金额"`
 | 
				
			||||||
 | 
						Price         decimal.Decimal `json:"price" comment:"委托价格"`
 | 
				
			||||||
 | 
						PriceU        decimal.Decimal `json:"priceU" comment:"委托价格(U)"`
 | 
				
			||||||
 | 
						FinalPrice    decimal.Decimal `json:"finalPrice" comment:"实际成交价"`
 | 
				
			||||||
 | 
						PositionSide  string          `json:"positionSide" comment:"持仓方向 LONG-多 SHORT-空"`
 | 
				
			||||||
 | 
						Side          string          `json:"side" comment:"买卖方向 SELL-卖 BUY-买"`
 | 
				
			||||||
 | 
						SignPrice     decimal.Decimal `json:"signPrice" comment:"行情价"`
 | 
				
			||||||
 | 
						TriggerTime   time.Time       `json:"triggerTime" comment:"触发时间"`
 | 
				
			||||||
 | 
						Status        int             `json:"status" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"`
 | 
				
			||||||
 | 
						common.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseOrderInsertReq) Generate(model *models.LineReverseOrder) {
 | 
				
			||||||
 | 
						if s.Id == 0 {
 | 
				
			||||||
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						model.OrderSn = s.OrderSn
 | 
				
			||||||
 | 
						model.OrderId = s.OrderId
 | 
				
			||||||
 | 
						model.FollowOrderSn = s.FollowOrderSn
 | 
				
			||||||
 | 
						model.Symbol = s.Symbol
 | 
				
			||||||
 | 
						model.OrderType = s.OrderType
 | 
				
			||||||
 | 
						model.BuyPrice = s.BuyPrice
 | 
				
			||||||
 | 
						model.Price = s.Price
 | 
				
			||||||
 | 
						model.PriceU = s.PriceU
 | 
				
			||||||
 | 
						model.FinalPrice = s.FinalPrice
 | 
				
			||||||
 | 
						model.PositionSide = s.PositionSide
 | 
				
			||||||
 | 
						model.Side = s.Side
 | 
				
			||||||
 | 
						model.SignPrice = s.SignPrice
 | 
				
			||||||
 | 
						model.TriggerTime = &s.TriggerTime
 | 
				
			||||||
 | 
						model.Status = s.Status
 | 
				
			||||||
 | 
						model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseOrderInsertReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseOrderUpdateReq struct {
 | 
				
			||||||
 | 
						Id            int             `uri:"id" comment:"主键id"` // 主键id
 | 
				
			||||||
 | 
						PositionId    int             `json:"positionId" comment:"仓位id"`
 | 
				
			||||||
 | 
						OrderSn       string          `json:"orderSn" comment:"订单号"`
 | 
				
			||||||
 | 
						OrderId       string          `json:"orderId" comment:"币安订单号"`
 | 
				
			||||||
 | 
						FollowOrderSn string          `json:"followOrderSn" comment:"跟随币安订单号"`
 | 
				
			||||||
 | 
						Symbol        string          `json:"symbol" comment:"交易对"`
 | 
				
			||||||
 | 
						OrderType     int             `json:"orderType" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"`
 | 
				
			||||||
 | 
						BuyPrice      decimal.Decimal `json:"buyPrice" comment:"购买金额"`
 | 
				
			||||||
 | 
						Price         decimal.Decimal `json:"price" comment:"委托价格"`
 | 
				
			||||||
 | 
						PriceU        decimal.Decimal `json:"priceU" comment:"委托价格(U)"`
 | 
				
			||||||
 | 
						FinalPrice    decimal.Decimal `json:"finalPrice" comment:"实际成交价"`
 | 
				
			||||||
 | 
						PositionSide  string          `json:"positionSide" comment:"持仓方向 LONG-多 SHORT-空"`
 | 
				
			||||||
 | 
						Side          string          `json:"side" comment:"买卖方向 SELL-卖 BUY-买"`
 | 
				
			||||||
 | 
						SignPrice     decimal.Decimal `json:"signPrice" comment:"行情价"`
 | 
				
			||||||
 | 
						TriggerTime   time.Time       `json:"triggerTime" comment:"触发时间"`
 | 
				
			||||||
 | 
						Status        int             `json:"status" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"`
 | 
				
			||||||
 | 
						common.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseOrderUpdateReq) Generate(model *models.LineReverseOrder) {
 | 
				
			||||||
 | 
						if s.Id == 0 {
 | 
				
			||||||
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						model.OrderSn = s.OrderSn
 | 
				
			||||||
 | 
						model.OrderId = s.OrderId
 | 
				
			||||||
 | 
						model.FollowOrderSn = s.FollowOrderSn
 | 
				
			||||||
 | 
						model.Symbol = s.Symbol
 | 
				
			||||||
 | 
						model.OrderType = s.OrderType
 | 
				
			||||||
 | 
						model.BuyPrice = s.BuyPrice
 | 
				
			||||||
 | 
						model.Price = s.Price
 | 
				
			||||||
 | 
						model.PriceU = s.PriceU
 | 
				
			||||||
 | 
						model.FinalPrice = s.FinalPrice
 | 
				
			||||||
 | 
						model.PositionSide = s.PositionSide
 | 
				
			||||||
 | 
						model.Side = s.Side
 | 
				
			||||||
 | 
						model.SignPrice = s.SignPrice
 | 
				
			||||||
 | 
						model.TriggerTime = &s.TriggerTime
 | 
				
			||||||
 | 
						model.Status = s.Status
 | 
				
			||||||
 | 
						model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseOrderUpdateReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LineReverseOrderGetReq 功能获取请求参数
 | 
				
			||||||
 | 
					type LineReverseOrderGetReq struct {
 | 
				
			||||||
 | 
						Id int `uri:"id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseOrderGetReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LineReverseOrderDeleteReq 功能删除请求参数
 | 
				
			||||||
 | 
					type LineReverseOrderDeleteReq struct {
 | 
				
			||||||
 | 
						Ids []int `json:"ids"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseOrderDeleteReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Ids
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										155
									
								
								app/admin/service/dto/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								app/admin/service/dto/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					package dto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/common/dto"
 | 
				
			||||||
 | 
						common "go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePositionGetPageReq struct {
 | 
				
			||||||
 | 
						dto.Pagination `search:"-"`
 | 
				
			||||||
 | 
						ApiId          int64  `form:"apiId"  search:"type:exact;column:api_id;table:line_reverse_position" comment:"api_id"`
 | 
				
			||||||
 | 
						ReverseApiId   int64  `form:"reverseApiId"  search:"type:exact;column:reverse_api_id;table:line_reverse_position" comment:"反单api_id"`
 | 
				
			||||||
 | 
						Side           string `form:"side"  search:"type:exact;column:side;table:line_reverse_position" comment:"买卖方向 BUY SELL"`
 | 
				
			||||||
 | 
						PositionSide   string `form:"positionSide"  search:"type:exact;column:position_side;table:line_reverse_position" comment:"持仓方向 LONG SHORT"`
 | 
				
			||||||
 | 
						Symbol         string `form:"symbol"  search:"type:contains;column:symbol;table:line_reverse_position" comment:"交易对"`
 | 
				
			||||||
 | 
						LineReversePositionOrder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePositionOrder struct {
 | 
				
			||||||
 | 
						Id            string `form:"idOrder"  search:"type:order;column:id;table:line_reverse_position"`
 | 
				
			||||||
 | 
						ApiId         string `form:"apiIdOrder"  search:"type:order;column:api_id;table:line_reverse_position"`
 | 
				
			||||||
 | 
						ReverseApiId  string `form:"reverseApiIdOrder"  search:"type:order;column:reverse_api_id;table:line_reverse_position"`
 | 
				
			||||||
 | 
						ReverseAmount string `form:"reverseAmountOrder"  search:"type:order;column:reverse_amount;table:line_reverse_position"`
 | 
				
			||||||
 | 
						Side          string `form:"sideOrder"  search:"type:order;column:side;table:line_reverse_position"`
 | 
				
			||||||
 | 
						PositionSide  string `form:"positionSideOrder"  search:"type:order;column:position_side;table:line_reverse_position"`
 | 
				
			||||||
 | 
						Symbol        string `form:"symbolOrder"  search:"type:order;column:symbol;table:line_reverse_position"`
 | 
				
			||||||
 | 
						Status        string `form:"statusOrder"  search:"type:order;column:status;table:line_reverse_position"`
 | 
				
			||||||
 | 
						CreatedAt     string `form:"createdAtOrder"  search:"type:order;column:created_at;table:line_reverse_position"`
 | 
				
			||||||
 | 
						UpdatedAt     string `form:"updatedAtOrder"  search:"type:order;column:updated_at;table:line_reverse_position"`
 | 
				
			||||||
 | 
						DeletedAt     string `form:"deletedAtOrder"  search:"type:order;column:deleted_at;table:line_reverse_position"`
 | 
				
			||||||
 | 
						CreateBy      string `form:"createByOrder"  search:"type:order;column:create_by;table:line_reverse_position"`
 | 
				
			||||||
 | 
						UpdateBy      string `form:"updateByOrder"  search:"type:order;column:update_by;table:line_reverse_position"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *LineReversePositionGetPageReq) GetNeedSearch() interface{} {
 | 
				
			||||||
 | 
						return *m
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePositionInsertReq struct {
 | 
				
			||||||
 | 
						Id            int             `json:"-" comment:"主键"` // 主键
 | 
				
			||||||
 | 
						ApiId         int             `json:"apiId" comment:"api_id"`
 | 
				
			||||||
 | 
						ReverseApiId  int             `json:"reverseApiId" comment:"反单api_id"`
 | 
				
			||||||
 | 
						ReverseAmount decimal.Decimal `json:"reverseAmount" comment:"反单仓位"`
 | 
				
			||||||
 | 
						Side          string          `json:"side" comment:"买卖方向 BUY SELL"`
 | 
				
			||||||
 | 
						PositionSide  string          `json:"positionSide" comment:"持仓方向 LONG SHORT"`
 | 
				
			||||||
 | 
						Symbol        string          `json:"symbol" comment:"交易对"`
 | 
				
			||||||
 | 
						Status        int             `json:"status" comment:"仓位状态 1-已开仓 2-已平仓"`
 | 
				
			||||||
 | 
						common.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReversePositionInsertReq) Generate(model *models.LineReversePosition) {
 | 
				
			||||||
 | 
						if s.Id == 0 {
 | 
				
			||||||
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						model.ApiId = s.ApiId
 | 
				
			||||||
 | 
						model.ReverseApiId = s.ReverseApiId
 | 
				
			||||||
 | 
						model.ReverseAmount = s.ReverseAmount
 | 
				
			||||||
 | 
						model.Side = s.Side
 | 
				
			||||||
 | 
						model.PositionSide = s.PositionSide
 | 
				
			||||||
 | 
						model.Symbol = s.Symbol
 | 
				
			||||||
 | 
						model.Status = s.Status
 | 
				
			||||||
 | 
						model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReversePositionInsertReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePositionUpdateReq struct {
 | 
				
			||||||
 | 
						Id            int             `uri:"id" comment:"主键"` // 主键
 | 
				
			||||||
 | 
						ApiId         int             `json:"apiId" comment:"api_id"`
 | 
				
			||||||
 | 
						ReverseApiId  int             `json:"reverseApiId" comment:"反单api_id"`
 | 
				
			||||||
 | 
						ReverseAmount decimal.Decimal `json:"reverseAmount" comment:"反单仓位"`
 | 
				
			||||||
 | 
						Side          string          `json:"side" comment:"买卖方向 BUY SELL"`
 | 
				
			||||||
 | 
						PositionSide  string          `json:"positionSide" comment:"持仓方向 LONG SHORT"`
 | 
				
			||||||
 | 
						Symbol        string          `json:"symbol" comment:"交易对"`
 | 
				
			||||||
 | 
						Status        int             `json:"status" comment:"仓位状态 1-已开仓 2-已平仓"`
 | 
				
			||||||
 | 
						common.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReversePositionUpdateReq) Generate(model *models.LineReversePosition) {
 | 
				
			||||||
 | 
						if s.Id == 0 {
 | 
				
			||||||
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						model.ApiId = s.ApiId
 | 
				
			||||||
 | 
						model.ReverseApiId = s.ReverseApiId
 | 
				
			||||||
 | 
						model.ReverseAmount = s.ReverseAmount
 | 
				
			||||||
 | 
						model.Side = s.Side
 | 
				
			||||||
 | 
						model.PositionSide = s.PositionSide
 | 
				
			||||||
 | 
						model.Symbol = s.Symbol
 | 
				
			||||||
 | 
						model.Status = s.Status
 | 
				
			||||||
 | 
						model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReversePositionUpdateReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LineReversePositionGetReq 功能获取请求参数
 | 
				
			||||||
 | 
					type LineReversePositionGetReq struct {
 | 
				
			||||||
 | 
						Id int `uri:"id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReversePositionGetReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LineReversePositionDeleteReq 功能删除请求参数
 | 
				
			||||||
 | 
					type LineReversePositionDeleteReq struct {
 | 
				
			||||||
 | 
						Ids []int `json:"ids"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReversePositionDeleteReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Ids
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePositionListResp struct {
 | 
				
			||||||
 | 
						Id                  int             `json:"id"`
 | 
				
			||||||
 | 
						ApiName             string          `json:"apiName"`
 | 
				
			||||||
 | 
						ReverseApiName      string          `json:"reverseApiName"`
 | 
				
			||||||
 | 
						Amount              decimal.Decimal `json:"amount"`
 | 
				
			||||||
 | 
						TotalAmount         decimal.Decimal `json:"totalAmount"`
 | 
				
			||||||
 | 
						ReverseAmount       decimal.Decimal `json:"reverseAmount"`
 | 
				
			||||||
 | 
						TotalReverseAmount  decimal.Decimal `json:"totalReverseAmount"`
 | 
				
			||||||
 | 
						Side                string          `json:"side"`
 | 
				
			||||||
 | 
						PositionSide        string          `json:"positionSide"`
 | 
				
			||||||
 | 
						Symbol              string          `json:"symbol"`
 | 
				
			||||||
 | 
						Status              int             `json:"status"`
 | 
				
			||||||
 | 
						ReverseStatus       int             `json:"reverseStatus"`
 | 
				
			||||||
 | 
						AveragePrice        decimal.Decimal `json:"averagePrice"`
 | 
				
			||||||
 | 
						ReverseAveragePrice decimal.Decimal `json:"reverseAveragePrice"`
 | 
				
			||||||
 | 
						CreatedAt           string          `json:"createdAt"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePositionCloseReq struct {
 | 
				
			||||||
 | 
						PositionId int `uri:"id" form:"id" comment:"仓位id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePositionCloseBatchReq struct {
 | 
				
			||||||
 | 
						PositionSide  string `json:"positionSide"`
 | 
				
			||||||
 | 
						Symbol        string `json:"symbol"`
 | 
				
			||||||
 | 
						ReverseApiIds []int  `json:"reverseApiIds" form:"reverseApiIds" comment:"反单api_id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetPositionSymbolReq struct {
 | 
				
			||||||
 | 
						ReverseApiId int    `form:"reverseApiId" comment:"反单api_id"`
 | 
				
			||||||
 | 
						PositionSide string `form:"positionSide" comment:"持仓方向 LONG SHORT"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PositionSymbolResp struct {
 | 
				
			||||||
 | 
						Symbol string `json:"symbol"`
 | 
				
			||||||
 | 
						Code   string `json:"code"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										95
									
								
								app/admin/service/dto/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								app/admin/service/dto/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					package dto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/common/dto"
 | 
				
			||||||
 | 
						common "go-admin/common/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseSettingGetPageReq struct {
 | 
				
			||||||
 | 
						dto.Pagination `search:"-"`
 | 
				
			||||||
 | 
						LineReverseSettingOrder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseSettingOrder struct {
 | 
				
			||||||
 | 
						Id                  string `form:"idOrder"  search:"type:order;column:id;table:line_reverse_setting"`
 | 
				
			||||||
 | 
						ReverseOrderType    string `form:"reverseOrderTypeOrder"  search:"type:order;column:reverse_order_type;table:line_reverse_setting"`
 | 
				
			||||||
 | 
						ReversePremiumRatio string `form:"reversePremiumRatioOrder"  search:"type:order;column:reverse_premium_ratio;table:line_reverse_setting"`
 | 
				
			||||||
 | 
						CreatedAt           string `form:"createdAtOrder"  search:"type:order;column:created_at;table:line_reverse_setting"`
 | 
				
			||||||
 | 
						UpdatedAt           string `form:"updatedAtOrder"  search:"type:order;column:updated_at;table:line_reverse_setting"`
 | 
				
			||||||
 | 
						DeletedAt           string `form:"deletedAtOrder"  search:"type:order;column:deleted_at;table:line_reverse_setting"`
 | 
				
			||||||
 | 
						CreateBy            string `form:"createByOrder"  search:"type:order;column:create_by;table:line_reverse_setting"`
 | 
				
			||||||
 | 
						UpdateBy            string `form:"updateByOrder"  search:"type:order;column:update_by;table:line_reverse_setting"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *LineReverseSettingGetPageReq) GetNeedSearch() interface{} {
 | 
				
			||||||
 | 
						return *m
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseSettingInsertReq struct {
 | 
				
			||||||
 | 
						Id                  int             `json:"-" comment:"主键id"` // 主键id
 | 
				
			||||||
 | 
						ReverseOrderType    string          `json:"reverseOrderType" comment:"反单下单类型 LIMIT-限价 MARKET-市价"`
 | 
				
			||||||
 | 
						ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" comment:"溢价百分比"`
 | 
				
			||||||
 | 
						TakeProfitRatio     decimal.Decimal `json:"takeProfitRatio" comment:"止盈百分比"`
 | 
				
			||||||
 | 
						StopLossRatio       decimal.Decimal `json:"stopLossRatio" comment:"止损百分比"`
 | 
				
			||||||
 | 
						common.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseSettingInsertReq) Generate(model *models.LineReverseSetting) {
 | 
				
			||||||
 | 
						if s.Id == 0 {
 | 
				
			||||||
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						model.ReverseOrderType = s.ReverseOrderType
 | 
				
			||||||
 | 
						model.ReversePremiumRatio = s.ReversePremiumRatio
 | 
				
			||||||
 | 
						model.StopLossRatio = s.StopLossRatio
 | 
				
			||||||
 | 
						model.TakeProfitRatio = s.TakeProfitRatio
 | 
				
			||||||
 | 
						model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseSettingInsertReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseSettingUpdateReq struct {
 | 
				
			||||||
 | 
						Id                  int             `uri:"id" comment:"主键id"` // 主键id
 | 
				
			||||||
 | 
						ReverseOrderType    string          `json:"reverseOrderType" comment:"反单下单类型 LIMIT-限价 MARKET-市价"`
 | 
				
			||||||
 | 
						ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" comment:"溢价百分比"`
 | 
				
			||||||
 | 
						TakeProfitRatio     decimal.Decimal `json:"takeProfitRatio" comment:"止盈百分比"`
 | 
				
			||||||
 | 
						StopLossRatio       decimal.Decimal `json:"stopLossRatio" comment:"止损百分比"`
 | 
				
			||||||
 | 
						common.ControlBy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseSettingUpdateReq) Generate(model *models.LineReverseSetting) {
 | 
				
			||||||
 | 
						if s.Id == 0 {
 | 
				
			||||||
 | 
							model.Model = common.Model{Id: s.Id}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						model.ReverseOrderType = s.ReverseOrderType
 | 
				
			||||||
 | 
						model.ReversePremiumRatio = s.ReversePremiumRatio
 | 
				
			||||||
 | 
						model.StopLossRatio = s.StopLossRatio
 | 
				
			||||||
 | 
						model.TakeProfitRatio = s.TakeProfitRatio
 | 
				
			||||||
 | 
						model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseSettingUpdateReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LineReverseSettingGetReq 功能获取请求参数
 | 
				
			||||||
 | 
					type LineReverseSettingGetReq struct {
 | 
				
			||||||
 | 
						Id int `uri:"id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseSettingGetReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LineReverseSettingDeleteReq 功能删除请求参数
 | 
				
			||||||
 | 
					type LineReverseSettingDeleteReq struct {
 | 
				
			||||||
 | 
						Ids []int `json:"ids"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *LineReverseSettingDeleteReq) GetId() interface{} {
 | 
				
			||||||
 | 
						return s.Ids
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -8,6 +8,7 @@ import (
 | 
				
			|||||||
	"github.com/bytedance/sonic"
 | 
						"github.com/bytedance/sonic"
 | 
				
			||||||
	"github.com/go-admin-team/go-admin-core/logger"
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
	"github.com/go-admin-team/go-admin-core/sdk/service"
 | 
						"github.com/go-admin-team/go-admin-core/sdk/service"
 | 
				
			||||||
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"go-admin/app/admin/models"
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
@ -25,6 +26,43 @@ type LineApiUser struct {
 | 
				
			|||||||
	service.Service
 | 
						service.Service
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取所有启用的反单api用户
 | 
				
			||||||
 | 
					func (e LineApiUser) GetReverseApiOptionsAll(user *[]dto.LineApiUserOptionResp) error {
 | 
				
			||||||
 | 
						var data models.LineApiUser
 | 
				
			||||||
 | 
						var datas []models.LineApiUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := e.Orm.Model(&data).Where("open_status = 1 AND subordinate = '2'").Find(&datas).Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineApiUserService GetReverseApiOptionsAll error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range datas {
 | 
				
			||||||
 | 
							*user = append(*user, dto.LineApiUserOptionResp{
 | 
				
			||||||
 | 
								Id:   item.Id,
 | 
				
			||||||
 | 
								Name: item.ApiName,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取可以绑定的api列表
 | 
				
			||||||
 | 
					func (e LineApiUser) GetReverseApiOptions(req *dto.GetReverseApiOptionsReq, user *[]models.LineApiUser) error {
 | 
				
			||||||
 | 
						query := e.Orm.Model(models.LineApiUser{}).
 | 
				
			||||||
 | 
							Where("subordinate ='0' and id != ?", req.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if req.ApiId > 0 {
 | 
				
			||||||
 | 
							query = query.Or(" id =?", req.ApiId)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := query.Find(user).Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineApiUserService GetReverseApiOptions error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetPage 获取LineApiUser列表
 | 
					// GetPage 获取LineApiUser列表
 | 
				
			||||||
func (e *LineApiUser) GetPage(c *dto.LineApiUserGetPageReq, p *actions.DataPermission, list *[]models.LineApiUser, count *int64) error {
 | 
					func (e *LineApiUser) GetPage(c *dto.LineApiUserGetPageReq, p *actions.DataPermission, list *[]models.LineApiUser, count *int64) error {
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
@ -97,13 +135,37 @@ func (e *LineApiUser) Insert(c *dto.LineApiUserInsertReq) error {
 | 
				
			|||||||
	var err error
 | 
						var err error
 | 
				
			||||||
	var data models.LineApiUser
 | 
						var data models.LineApiUser
 | 
				
			||||||
	c.Generate(&data)
 | 
						c.Generate(&data)
 | 
				
			||||||
	err = e.Orm.Create(&data).Error
 | 
						err = e.Orm.Transaction(func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
							if c.ReverseApiId > 0 {
 | 
				
			||||||
 | 
								var count int64
 | 
				
			||||||
 | 
								if err2 := tx.Model(models.LineApiUser{}).Where("subordinate <> '0' AND id=?", c.ReverseApiId).Count(&count).Error; err2 != nil {
 | 
				
			||||||
 | 
									return err2
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if count > 0 {
 | 
				
			||||||
 | 
									return errors.New("反向api已被使用")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err2 := tx.Model(models.LineApiUser{}).Where("id =?", c.ReverseApiId).Update("subordinate", '2').Error; err2 != nil {
 | 
				
			||||||
 | 
									return err2
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								data.Subordinate = "1"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err2 := tx.Create(&data).Error; err2 != nil {
 | 
				
			||||||
 | 
								return err2
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		e.Log.Errorf("LineApiUserService Insert error:%s \r\n", err)
 | 
							e.Log.Errorf("LineApiUserService Insert error:%s \r\n", err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	e.saveCache(data)
 | 
						if err2 := e.CacheRelation(); err2 != nil {
 | 
				
			||||||
 | 
							return err2
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	val, _ := sonic.MarshalString(&data)
 | 
						val, _ := sonic.MarshalString(&data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if val != "" {
 | 
						if val != "" {
 | 
				
			||||||
@ -131,7 +193,7 @@ func (e *LineApiUser) restartWebsocket(data models.LineApiUser) {
 | 
				
			|||||||
		fuSocket.Stop()
 | 
							fuSocket.Stop()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	e.saveCache(data)
 | 
						e.CacheRelation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	OpenUserBinanceWebsocket(data)
 | 
						OpenUserBinanceWebsocket(data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -149,6 +211,23 @@ func (e *LineApiUser) saveCache(data models.LineApiUser) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 缓存关系
 | 
				
			||||||
 | 
					// cacheAll 是否缓存所有关系
 | 
				
			||||||
 | 
					func (e *LineApiUser) CacheRelation() error {
 | 
				
			||||||
 | 
						var datas *[]models.LineApiUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := e.Orm.Model(&models.LineApiUser{}).Where("open_status = 1").Find(&datas).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, data := range *datas {
 | 
				
			||||||
 | 
							// cacheStrs = append(cacheStrs, fmt.Sprintf(rediskey.API_USER, data.Id))
 | 
				
			||||||
 | 
							e.saveCache(data)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
打开用户websocket订阅
 | 
					打开用户websocket订阅
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
@ -197,6 +276,7 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss
 | 
				
			|||||||
		actions.Permission(data.TableName(), p),
 | 
							actions.Permission(data.TableName(), p),
 | 
				
			||||||
	).First(&data, c.GetId())
 | 
						).First(&data, c.GetId())
 | 
				
			||||||
	oldApiKey := data.ApiKey
 | 
						oldApiKey := data.ApiKey
 | 
				
			||||||
 | 
						oldReverseApiId := data.ReverseApiId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Generate(&data)
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -204,6 +284,33 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	//事务
 | 
						//事务
 | 
				
			||||||
	err = e.Orm.Transaction(func(tx *gorm.DB) error {
 | 
						err = e.Orm.Transaction(func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
							if data.ReverseApiId > 0 && data.ReverseApiId != oldReverseApiId {
 | 
				
			||||||
 | 
								var count int64
 | 
				
			||||||
 | 
								if err2 := tx.Model(models.LineApiUser{}).Where("subordinate <> '0' AND id=?", c.ReverseApiId).Count(&count).Error; err2 != nil {
 | 
				
			||||||
 | 
									return err2
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if count > 0 {
 | 
				
			||||||
 | 
									return errors.New("反向api已被使用")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err2 := tx.Model(models.LineApiUser{}).Where("id =?", c.ReverseApiId).Update("subordinate", "2").Error; err2 != nil {
 | 
				
			||||||
 | 
									return err2
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								data.Subordinate = "1"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if oldReverseApiId > 0 && oldReverseApiId != data.ReverseApiId {
 | 
				
			||||||
 | 
								if err2 := tx.Model(models.LineApiUser{}).Where("id =?", oldReverseApiId).Update("subordinate", "0").Error; err2 != nil {
 | 
				
			||||||
 | 
									e.Log.Errorf("解绑反向api失败 api:%d err:%v", oldReverseApiId, err2)
 | 
				
			||||||
 | 
									return fmt.Errorf("解绑反向api失败")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if data.ReverseApiId == 0 {
 | 
				
			||||||
 | 
									data.Subordinate = "0"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		db := tx.Save(&data)
 | 
							db := tx.Save(&data)
 | 
				
			||||||
		if err = db.Error; err != nil {
 | 
							if err = db.Error; err != nil {
 | 
				
			||||||
			e.Log.Errorf("LineApiUserService Save error:%s \r\n", err)
 | 
								e.Log.Errorf("LineApiUserService Save error:%s \r\n", err)
 | 
				
			||||||
@ -226,7 +333,9 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	e.saveCache(data)
 | 
						if err2 := e.CacheRelation(); err2 != nil {
 | 
				
			||||||
 | 
							return err2
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//旧key和新的key不一样,则关闭旧的websocket
 | 
						//旧key和新的key不一样,则关闭旧的websocket
 | 
				
			||||||
	if oldApiKey != data.ApiKey {
 | 
						if oldApiKey != data.ApiKey {
 | 
				
			||||||
@ -289,10 +398,14 @@ func (e *LineApiUser) Remove(d *dto.LineApiUserDeleteReq, p *actions.DataPermiss
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	_, err := helper.DefaultRedis.BatchDeleteKeys(delKeys)
 | 
						_, err := helper.DefaultRedis.BatchDeleteKeys(delKeys)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil && !errors.Is(err, redis.Nil) {
 | 
				
			||||||
		e.Log.Error("批量删除api_user key失败", err)
 | 
							e.Log.Error("批量删除api_user key失败", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err2 := e.CacheRelation(); err2 != nil {
 | 
				
			||||||
 | 
							return err2
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -414,3 +527,44 @@ func (e *LineApiUser) GetActiveApis(apiIds []int) ([]int, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return result, nil
 | 
						return result, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUnBindApiUser 获取未绑定反向下单的的api用户
 | 
				
			||||||
 | 
					func (e *LineApiUser) GetUnBindApiUser(req *dto.GetUnBindReverseReq, list *[]dto.UnBindReverseResp) error {
 | 
				
			||||||
 | 
						var datas []models.LineApiUser
 | 
				
			||||||
 | 
						var data models.LineApiUser
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if req.ApiId > 0 {
 | 
				
			||||||
 | 
							e.Orm.Model(data).Where("id =? AND subordinate = '2'", req.ApiId).Count(&count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if count > 0 {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Where("open_status =1 AND exchange_type = ? ", req.ExchangeType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := query.
 | 
				
			||||||
 | 
							Find(&datas).Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Error("获取未绑定用户失败:", err)
 | 
				
			||||||
 | 
							return fmt.Errorf("获取未绑定用户失败")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range datas {
 | 
				
			||||||
 | 
							if item.Id == req.ApiId {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							listItem := dto.UnBindReverseResp{
 | 
				
			||||||
 | 
								Id:       item.Id,
 | 
				
			||||||
 | 
								UserId:   item.UserId,
 | 
				
			||||||
 | 
								ApiName:  item.ApiName,
 | 
				
			||||||
 | 
								Disabled: item.Subordinate != "0",
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							(*list) = append((*list), listItem)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1078,8 +1078,6 @@ func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreO
 | 
				
			|||||||
		mainId = preOrder.MainId
 | 
							mainId = preOrder.MainId
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fmt.Println("take", ext.TakeProfitRatio.String())
 | 
					 | 
				
			||||||
	fmt.Println("boo", ext.TakeProfitRatio.Cmp(decimal.Zero))
 | 
					 | 
				
			||||||
	if ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 {
 | 
						if ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 {
 | 
				
			||||||
		// 止盈单
 | 
							// 止盈单
 | 
				
			||||||
		profitOrder := models.LinePreOrder{}
 | 
							profitOrder := models.LinePreOrder{}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										114
									
								
								app/admin/service/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								app/admin/service/line_reverse_order.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/service"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service/dto"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
						cDto "go-admin/common/dto"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseOrder struct {
 | 
				
			||||||
 | 
						service.Service
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPage 获取LineReverseOrder列表
 | 
				
			||||||
 | 
					func (e *LineReverseOrder) GetPage(c *dto.LineReverseOrderGetPageReq, p *actions.DataPermission, list *[]models.LineReverseOrder, count *int64) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data models.LineReverseOrder
 | 
				
			||||||
 | 
						query := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								cDto.MakeCondition(c.GetNeedSearch()),
 | 
				
			||||||
 | 
								cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.Category >= 0 {
 | 
				
			||||||
 | 
							query = query.Where("category =?", c.Category)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = query.
 | 
				
			||||||
 | 
							Find(list).Limit(-1).Offset(-1).
 | 
				
			||||||
 | 
							Count(count).Error
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReverseOrderService GetPage error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get 获取LineReverseOrder对象
 | 
				
			||||||
 | 
					func (e *LineReverseOrder) Get(d *dto.LineReverseOrderGetReq, p *actions.DataPermission, model *models.LineReverseOrder) error {
 | 
				
			||||||
 | 
						var data models.LineReverseOrder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							First(model, d.GetId()).Error
 | 
				
			||||||
 | 
						if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							err = errors.New("查看对象不存在或无权查看")
 | 
				
			||||||
 | 
							e.Log.Errorf("Service GetLineReverseOrder error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("db error:%s", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Insert 创建LineReverseOrder对象
 | 
				
			||||||
 | 
					func (e *LineReverseOrder) Insert(c *dto.LineReverseOrderInsertReq) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data models.LineReverseOrder
 | 
				
			||||||
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
						err = e.Orm.Create(&data).Error
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReverseOrderService Insert error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update 修改LineReverseOrder对象
 | 
				
			||||||
 | 
					func (e *LineReverseOrder) Update(c *dto.LineReverseOrderUpdateReq, p *actions.DataPermission) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data = models.LineReverseOrder{}
 | 
				
			||||||
 | 
						e.Orm.Scopes(
 | 
				
			||||||
 | 
							actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
						).First(&data, c.GetId())
 | 
				
			||||||
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db := e.Orm.Save(&data)
 | 
				
			||||||
 | 
						if err = db.Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReverseOrderService Save error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
 | 
							return errors.New("无权更新该数据")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Remove 删除LineReverseOrder
 | 
				
			||||||
 | 
					func (e *LineReverseOrder) Remove(d *dto.LineReverseOrderDeleteReq, p *actions.DataPermission) error {
 | 
				
			||||||
 | 
						var data models.LineReverseOrder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).Delete(&data, d.GetId())
 | 
				
			||||||
 | 
						if err := db.Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("Service RemoveLineReverseOrder error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
 | 
							return errors.New("无权删除该数据")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										345
									
								
								app/admin/service/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								app/admin/service/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,345 @@
 | 
				
			|||||||
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/service"
 | 
				
			||||||
 | 
						"github.com/jinzhu/copier"
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service/dto"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
						cDto "go-admin/common/dto"
 | 
				
			||||||
 | 
						"go-admin/common/global"
 | 
				
			||||||
 | 
						"go-admin/pkg/utility"
 | 
				
			||||||
 | 
						"go-admin/pkg/utility/snowflakehelper"
 | 
				
			||||||
 | 
						"go-admin/services/binanceservice"
 | 
				
			||||||
 | 
						"go-admin/services/cacheservice"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReversePosition struct {
 | 
				
			||||||
 | 
						service.Service
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 清除仓位记录
 | 
				
			||||||
 | 
					func (e LineReversePosition) CleanAll(p *actions.DataPermission, userId int) error {
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := e.Orm.Model(&models.LineReversePosition{}).
 | 
				
			||||||
 | 
							Where("status =1 or reverse_status = 1").Count(&count).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if count > 0 {
 | 
				
			||||||
 | 
							e.Log.Errorf("还有仓位无法清除")
 | 
				
			||||||
 | 
							return errors.New("有仓位正在进行中,不能清除所有")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := e.Orm.Exec("TRUNCATE TABLE line_reverse_position").Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 批量关闭仓位
 | 
				
			||||||
 | 
					func (e LineReversePosition) ClosePositionBatch(req *dto.LineReversePositionCloseBatchReq, p *actions.DataPermission, userId int, errs *[]string) error {
 | 
				
			||||||
 | 
						var positions []models.LineReversePosition
 | 
				
			||||||
 | 
						var entity models.LineReversePosition
 | 
				
			||||||
 | 
						query := e.Orm.Model(&entity).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(entity.TableName(), p),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Where("reverse_status = 1 and position_side =?", req.PositionSide)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(req.Symbol) > 0 {
 | 
				
			||||||
 | 
							query = query.Where("symbol =?", req.Symbol)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(req.ReverseApiIds) > 0 {
 | 
				
			||||||
 | 
							query = query.Where("reverse_api_id in (?)", req.ReverseApiIds)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := query.Find(&positions).Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReversePositionService ClosePositionBatch error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(positions) == 0 {
 | 
				
			||||||
 | 
							return errors.New("没有需要关闭的仓位")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, position := range positions {
 | 
				
			||||||
 | 
							if err1 := e.Close(position); err1 != nil {
 | 
				
			||||||
 | 
								*errs = append(*errs, err1.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClosePosition 关闭单个仓位
 | 
				
			||||||
 | 
					func (e LineReversePosition) ClosePosition(req *dto.LineReversePositionCloseReq, p *actions.DataPermission, userId int) error {
 | 
				
			||||||
 | 
						var data models.LineReversePosition
 | 
				
			||||||
 | 
						err := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Where("id =?", req.PositionId).First(&data).Error
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReversePositionService ClosePosition error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = e.Close(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *LineReversePosition) Close(data models.LineReversePosition) error {
 | 
				
			||||||
 | 
						if data.ReverseStatus != 1 {
 | 
				
			||||||
 | 
							return fmt.Errorf("%s-%s 仓位无法关闭", data.Symbol, data.PositionSide)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						apiInfo, err := binanceservice.GetApiInfo(data.ReverseApiId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("api %d不存在", data.ReverseApiId)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						futApi := binanceservice.FutRestApi{}
 | 
				
			||||||
 | 
						futApiV2 := binanceservice.FuturesResetV2{}
 | 
				
			||||||
 | 
						holdData := binanceservice.HoldeData{}
 | 
				
			||||||
 | 
						err = futApi.GetPositionData(&apiInfo, data.Symbol, data.PositionSide, &holdData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("获取币安持仓失败 %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						setting, err := cacheservice.GetReverseSetting(e.Orm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("获取反单设置失败")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, data.Symbol, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("获取%s的交易对信息失败", data.Symbol)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastPrice, err := decimal.NewFromString(symbol.LastPrice)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("最新价格失败,%v", lastPrice)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						side := ""
 | 
				
			||||||
 | 
						var price decimal.Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if data.PositionSide == "LONG" {
 | 
				
			||||||
 | 
							side = "SELL"
 | 
				
			||||||
 | 
							price = decimal.NewFromInt(100).Sub(setting.ReversePremiumRatio).Div(decimal.NewFromInt(100)).Mul(lastPrice).Round(int32(symbol.PriceDigit))
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							side = "BUY"
 | 
				
			||||||
 | 
							price = decimal.NewFromInt(100).Add(setting.ReversePremiumRatio).Div(decimal.NewFromInt(100)).Mul(lastPrice).Round(int32(symbol.PriceDigit))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
						order := models.LineReverseOrder{
 | 
				
			||||||
 | 
							OrderSn:      snowflakehelper.GetOrderNo(),
 | 
				
			||||||
 | 
							PositionId:   data.Id,
 | 
				
			||||||
 | 
							PositionSide: data.PositionSide,
 | 
				
			||||||
 | 
							Symbol:       data.Symbol,
 | 
				
			||||||
 | 
							TotalNum:     holdData.TotalQuantity,
 | 
				
			||||||
 | 
							Category:     1,
 | 
				
			||||||
 | 
							OrderType:    4,
 | 
				
			||||||
 | 
							Side:         side,
 | 
				
			||||||
 | 
							Price:        price,
 | 
				
			||||||
 | 
							SignPrice:    lastPrice,
 | 
				
			||||||
 | 
							Type:         "LIMIT",
 | 
				
			||||||
 | 
							TriggerTime:  &now,
 | 
				
			||||||
 | 
							Status:       1,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if holdData.TotalQuantity.Cmp(decimal.Zero) > 0 {
 | 
				
			||||||
 | 
							if err := e.Orm.Create(&order).Error; err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							params := binanceservice.FutOrderPlace{
 | 
				
			||||||
 | 
								ApiId:            data.ReverseApiId,
 | 
				
			||||||
 | 
								Symbol:           data.Symbol,
 | 
				
			||||||
 | 
								Side:             order.Side,
 | 
				
			||||||
 | 
								PositionSide:     order.PositionSide,
 | 
				
			||||||
 | 
								Quantity:         order.TotalNum,
 | 
				
			||||||
 | 
								Price:            order.Price,
 | 
				
			||||||
 | 
								SideType:         order.Type,
 | 
				
			||||||
 | 
								OpenOrder:        0,
 | 
				
			||||||
 | 
								OrderType:        "LIMIT",
 | 
				
			||||||
 | 
								NewClientOrderId: order.OrderSn,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := futApiV2.OrderPlaceLoop(&apiInfo, params); err != nil {
 | 
				
			||||||
 | 
								e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", params.Symbol, order.OrderSn, err)
 | 
				
			||||||
 | 
								if err1 := e.Orm.Model(&order).Where("status = 1").Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}).Error; err1 != nil {
 | 
				
			||||||
 | 
									e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", params.Symbol, order.OrderSn, err1)
 | 
				
			||||||
 | 
									return err1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							order.Status = 8
 | 
				
			||||||
 | 
							order.Remark = "已经没有持仓"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = e.Orm.Transaction(func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
								if err1 := tx.Create(&order).Error; err1 != nil {
 | 
				
			||||||
 | 
									return err1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err1 := tx.Model(&data).Where("reverse_status =1").Updates(map[string]interface{}{"reverse_status": 2, "updated_at": time.Now(), "reverse_amount": 0}).Error; err1 != nil {
 | 
				
			||||||
 | 
									return err1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								e.Log.Errorf("修改失败 %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPage 获取LineReversePosition列表
 | 
				
			||||||
 | 
					func (e *LineReversePosition) GetPage(c *dto.LineReversePositionGetPageReq, p *actions.DataPermission, list *[]dto.LineReversePositionListResp, count *int64) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data models.LineReversePosition
 | 
				
			||||||
 | 
						var datas []models.LineReversePosition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								cDto.MakeCondition(c.GetNeedSearch()),
 | 
				
			||||||
 | 
								cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Find(&datas).Limit(-1).Offset(-1).
 | 
				
			||||||
 | 
							Count(count).Error
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReversePositionService GetPage error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userIds := make([]int, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range datas {
 | 
				
			||||||
 | 
							if !utility.ContainsInt(userIds, item.ApiId) {
 | 
				
			||||||
 | 
								userIds = append(userIds, item.ApiId)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !utility.ContainsInt(userIds, item.ReverseApiId) {
 | 
				
			||||||
 | 
								userIds = append(userIds, item.ReverseApiId)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userMap := make(map[int]string, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(userIds) > 0 {
 | 
				
			||||||
 | 
							var users []models.LineApiUser
 | 
				
			||||||
 | 
							e.Orm.Model(&models.LineApiUser{}).
 | 
				
			||||||
 | 
								Where("id IN (?)", userIds).
 | 
				
			||||||
 | 
								Find(&users)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, item := range users {
 | 
				
			||||||
 | 
								userMap[int(item.Id)] = item.ApiName
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range datas {
 | 
				
			||||||
 | 
							var resp dto.LineReversePositionListResp
 | 
				
			||||||
 | 
							copier.Copy(&resp, &item)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if userName, ok := userMap[item.ApiId]; ok {
 | 
				
			||||||
 | 
								resp.ApiName = userName
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if userName, ok := userMap[item.ReverseApiId]; ok {
 | 
				
			||||||
 | 
								resp.ReverseApiName = userName
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							*list = append(*list, resp)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get 获取LineReversePosition对象
 | 
				
			||||||
 | 
					func (e *LineReversePosition) Get(d *dto.LineReversePositionGetReq, p *actions.DataPermission, model *models.LineReversePosition) error {
 | 
				
			||||||
 | 
						var data models.LineReversePosition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							First(model, d.GetId()).Error
 | 
				
			||||||
 | 
						if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							err = errors.New("查看对象不存在或无权查看")
 | 
				
			||||||
 | 
							e.Log.Errorf("Service GetLineReversePosition error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("db error:%s", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Insert 创建LineReversePosition对象
 | 
				
			||||||
 | 
					func (e *LineReversePosition) Insert(c *dto.LineReversePositionInsertReq) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data models.LineReversePosition
 | 
				
			||||||
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
						err = e.Orm.Create(&data).Error
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReversePositionService Insert error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update 修改LineReversePosition对象
 | 
				
			||||||
 | 
					func (e *LineReversePosition) Update(c *dto.LineReversePositionUpdateReq, p *actions.DataPermission) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data = models.LineReversePosition{}
 | 
				
			||||||
 | 
						e.Orm.Scopes(
 | 
				
			||||||
 | 
							actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
						).First(&data, c.GetId())
 | 
				
			||||||
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db := e.Orm.Save(&data)
 | 
				
			||||||
 | 
						if err = db.Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReversePositionService Save error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
 | 
							return errors.New("无权更新该数据")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Remove 删除LineReversePosition
 | 
				
			||||||
 | 
					func (e *LineReversePosition) Remove(d *dto.LineReversePositionDeleteReq, p *actions.DataPermission) error {
 | 
				
			||||||
 | 
						var data models.LineReversePosition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).Delete(&data, d.GetId())
 | 
				
			||||||
 | 
						if err := db.Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("Service RemoveLineReversePosition error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
 | 
							return errors.New("无权删除该数据")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										136
									
								
								app/admin/service/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								app/admin/service/line_reverse_setting.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/service"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/app/admin/service/dto"
 | 
				
			||||||
 | 
						"go-admin/common/actions"
 | 
				
			||||||
 | 
						"go-admin/common/const/rediskey"
 | 
				
			||||||
 | 
						cDto "go-admin/common/dto"
 | 
				
			||||||
 | 
						"go-admin/common/helper"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LineReverseSetting struct {
 | 
				
			||||||
 | 
						service.Service
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPage 获取LineReverseSetting列表
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) GetPage(c *dto.LineReverseSettingGetPageReq, p *actions.DataPermission, list *[]models.LineReverseSetting, count *int64) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data models.LineReverseSetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								cDto.MakeCondition(c.GetNeedSearch()),
 | 
				
			||||||
 | 
								cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Find(list).Limit(-1).Offset(-1).
 | 
				
			||||||
 | 
							Count(count).Error
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReverseSettingService GetPage error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get 获取LineReverseSetting对象
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) Get(d *dto.LineReverseSettingGetReq, p *actions.DataPermission, model *models.LineReverseSetting) error {
 | 
				
			||||||
 | 
						var data models.LineReverseSetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							First(model, d.GetId()).Error
 | 
				
			||||||
 | 
						if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							err = errors.New("查看对象不存在或无权查看")
 | 
				
			||||||
 | 
							e.Log.Errorf("Service GetLineReverseSetting error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("db error:%s", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Insert 创建LineReverseSetting对象
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) Insert(c *dto.LineReverseSettingInsertReq) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data models.LineReverseSetting
 | 
				
			||||||
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
						err = e.Orm.Create(&data).Error
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReverseSettingService Insert error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = e.SaveCache(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 缓存数据
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) SaveCache(data models.LineReverseSetting) error {
 | 
				
			||||||
 | 
						if data.Id == 0 {
 | 
				
			||||||
 | 
							if err := e.Orm.First(&data).Error; err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := helper.DefaultRedis.SetHashWithTags(rediskey.ReverseSetting, &data); err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("redis error:%s", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update 修改LineReverseSetting对象
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) Update(c *dto.LineReverseSettingUpdateReq, p *actions.DataPermission) error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var data = models.LineReverseSetting{}
 | 
				
			||||||
 | 
						e.Orm.Scopes(
 | 
				
			||||||
 | 
							actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
						).First(&data, c.GetId())
 | 
				
			||||||
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db := e.Orm.Save(&data)
 | 
				
			||||||
 | 
						if err = db.Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("LineReverseSettingService Save error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
 | 
							return errors.New("无权更新该数据")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err = helper.DefaultRedis.SetHashWithTags(rediskey.ReverseSetting, data); err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("redis error:%s", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Remove 删除LineReverseSetting
 | 
				
			||||||
 | 
					func (e *LineReverseSetting) Remove(d *dto.LineReverseSettingDeleteReq, p *actions.DataPermission) error {
 | 
				
			||||||
 | 
						var data models.LineReverseSetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db := e.Orm.Model(&data).
 | 
				
			||||||
 | 
							Scopes(
 | 
				
			||||||
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
 | 
							).Delete(&data, d.GetId())
 | 
				
			||||||
 | 
						if err := db.Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("Service RemoveLineReverseSetting error:%s \r\n", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
 | 
							return errors.New("无权删除该数据")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,7 +3,7 @@ package service
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    "github.com/go-admin-team/go-admin-core/sdk/service"
 | 
						"github.com/go-admin-team/go-admin-core/sdk/service"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"go-admin/app/admin/models"
 | 
						"go-admin/app/admin/models"
 | 
				
			||||||
@ -59,9 +59,9 @@ func (e *LineUserSetting) Get(d *dto.LineUserSettingGetReq, p *actions.DataPermi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Insert 创建LineUserSetting对象
 | 
					// Insert 创建LineUserSetting对象
 | 
				
			||||||
func (e *LineUserSetting) Insert(c *dto.LineUserSettingInsertReq) error {
 | 
					func (e *LineUserSetting) Insert(c *dto.LineUserSettingInsertReq) error {
 | 
				
			||||||
    var err error
 | 
						var err error
 | 
				
			||||||
    var data models.LineUserSetting
 | 
						var data models.LineUserSetting
 | 
				
			||||||
    c.Generate(&data)
 | 
						c.Generate(&data)
 | 
				
			||||||
	err = e.Orm.Create(&data).Error
 | 
						err = e.Orm.Create(&data).Error
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		e.Log.Errorf("LineUserSettingService Insert error:%s \r\n", err)
 | 
							e.Log.Errorf("LineUserSettingService Insert error:%s \r\n", err)
 | 
				
			||||||
@ -72,22 +72,22 @@ func (e *LineUserSetting) Insert(c *dto.LineUserSettingInsertReq) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Update 修改LineUserSetting对象
 | 
					// Update 修改LineUserSetting对象
 | 
				
			||||||
func (e *LineUserSetting) Update(c *dto.LineUserSettingUpdateReq, p *actions.DataPermission) error {
 | 
					func (e *LineUserSetting) Update(c *dto.LineUserSettingUpdateReq, p *actions.DataPermission) error {
 | 
				
			||||||
    var err error
 | 
						var err error
 | 
				
			||||||
    var data = models.LineUserSetting{}
 | 
						var data = models.LineUserSetting{}
 | 
				
			||||||
    e.Orm.Scopes(
 | 
						e.Orm.Scopes(
 | 
				
			||||||
            actions.Permission(data.TableName(), p),
 | 
							actions.Permission(data.TableName(), p),
 | 
				
			||||||
        ).First(&data, c.GetId())
 | 
						).First(&data, c.GetId())
 | 
				
			||||||
    c.Generate(&data)
 | 
						c.Generate(&data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    db := e.Orm.Save(&data)
 | 
						db := e.Orm.Save(&data)
 | 
				
			||||||
    if err = db.Error; err != nil {
 | 
						if err = db.Error; err != nil {
 | 
				
			||||||
        e.Log.Errorf("LineUserSettingService Save error:%s \r\n", err)
 | 
							e.Log.Errorf("LineUserSettingService Save error:%s \r\n", err)
 | 
				
			||||||
        return err
 | 
							return err
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    if db.RowsAffected == 0 {
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
        return errors.New("无权更新该数据")
 | 
							return errors.New("无权更新该数据")
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Remove 删除LineUserSetting
 | 
					// Remove 删除LineUserSetting
 | 
				
			||||||
@ -99,11 +99,23 @@ func (e *LineUserSetting) Remove(d *dto.LineUserSettingDeleteReq, p *actions.Dat
 | 
				
			|||||||
			actions.Permission(data.TableName(), p),
 | 
								actions.Permission(data.TableName(), p),
 | 
				
			||||||
		).Delete(&data, d.GetId())
 | 
							).Delete(&data, d.GetId())
 | 
				
			||||||
	if err := db.Error; err != nil {
 | 
						if err := db.Error; err != nil {
 | 
				
			||||||
        e.Log.Errorf("Service RemoveLineUserSetting error:%s \r\n", err)
 | 
							e.Log.Errorf("Service RemoveLineUserSetting error:%s \r\n", err)
 | 
				
			||||||
        return err
 | 
							return err
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    if db.RowsAffected == 0 {
 | 
						if db.RowsAffected == 0 {
 | 
				
			||||||
        return errors.New("无权删除该数据")
 | 
							return errors.New("无权删除该数据")
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetDefault 获取默认LineUserSetting对象
 | 
				
			||||||
 | 
					func (e *LineUserSetting) GetDefault() (models.LineUserSetting, error) {
 | 
				
			||||||
 | 
						var data models.LineUserSetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := e.Orm.Model(&data).First(&data).Error; err != nil {
 | 
				
			||||||
 | 
							e.Log.Errorf("GetDefault LineUserSetting error:%s \r\n", err)
 | 
				
			||||||
 | 
							return data, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return data, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,11 @@ func run() error {
 | 
				
			|||||||
		clearLogJob(db, ctx)
 | 
							clearLogJob(db, ctx)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//自动重启websocket
 | 
				
			||||||
 | 
						utility.SafeGo(func() {
 | 
				
			||||||
 | 
							reconnect(ctx)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
 | 
						// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
 | 
				
			||||||
	quit := make(chan os.Signal, 1)
 | 
						quit := make(chan os.Signal, 1)
 | 
				
			||||||
	signal.Notify(quit, os.Interrupt)
 | 
						signal.Notify(quit, os.Interrupt)
 | 
				
			||||||
@ -133,3 +138,33 @@ func clearLogJob(db *gorm.DB, ctx context.Context) {
 | 
				
			|||||||
		fileservice.ClearLogs(db)
 | 
							fileservice.ClearLogs(db)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 定时重连websocket
 | 
				
			||||||
 | 
					func reconnect(ctx context.Context) error {
 | 
				
			||||||
 | 
						ticker := time.NewTicker(time.Hour * 1)
 | 
				
			||||||
 | 
						defer ticker.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-ctx.Done():
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						case <-ticker.C:
 | 
				
			||||||
 | 
							serverinit.RestartConnect()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StartDeadCheck 假死检测
 | 
				
			||||||
 | 
					func StartDeadCheck(ctx context.Context) {
 | 
				
			||||||
 | 
						ticker := time.NewTicker(1 * time.Minute)
 | 
				
			||||||
 | 
						defer ticker.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								serverinit.DeadCheck()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								common/const/binancecode/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								common/const/binancecode/event.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package binancecode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NEW
 | 
				
			||||||
 | 
					// CANCELED 已撤
 | 
				
			||||||
 | 
					// CALCULATED 订单 ADL 或爆仓
 | 
				
			||||||
 | 
					// EXPIRED 订单失效
 | 
				
			||||||
 | 
					// TRADE 交易
 | 
				
			||||||
 | 
					// AMENDMENT 订单修改
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						//新开单
 | 
				
			||||||
 | 
						EVENT_NEW = "NEW"
 | 
				
			||||||
 | 
						//已撤单
 | 
				
			||||||
 | 
						EVENT_CANCELED = "CANCELED"
 | 
				
			||||||
 | 
						//订单 ADL 或爆仓
 | 
				
			||||||
 | 
						EVENT_CALCULATED = "CALCULATED"
 | 
				
			||||||
 | 
						//订单失效
 | 
				
			||||||
 | 
						EVENT_EXPIRED = "EXPIRED"
 | 
				
			||||||
 | 
						//交易中
 | 
				
			||||||
 | 
						EVENT_TRADE = "TRADE"
 | 
				
			||||||
 | 
						//订单修改
 | 
				
			||||||
 | 
						EVENT_AMENDMENT = "AMENDMENT"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										20
									
								
								common/const/binancecode/order_type.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								common/const/binancecode/order_type.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package binancecode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						//限价单
 | 
				
			||||||
 | 
						ORDER_TYPE_LIMIT = "LIMIT"
 | 
				
			||||||
 | 
						//市价单
 | 
				
			||||||
 | 
						ORDER_TYPE_MARKET = "MARKET"
 | 
				
			||||||
 | 
						//止损限价单
 | 
				
			||||||
 | 
						ORDER_TYPE_STOP = "STOP"
 | 
				
			||||||
 | 
						//止损市价单
 | 
				
			||||||
 | 
						ORDER_TYPE_STOP_MARKET = "STOP_MARKET"
 | 
				
			||||||
 | 
						//止盈限价单
 | 
				
			||||||
 | 
						ORDER_TYPE_TAKE_PROFIT = "TAKE_PROFIT"
 | 
				
			||||||
 | 
						//止盈市价单
 | 
				
			||||||
 | 
						ORDER_TYPE_TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET"
 | 
				
			||||||
 | 
						//跟踪止损单
 | 
				
			||||||
 | 
						ORDER_TYPE_TRAILING_STOP_MARKET = "TRAILING_STOP_MARKET"
 | 
				
			||||||
 | 
						//爆仓
 | 
				
			||||||
 | 
						ORDER_TYPE_LIQUIDATION = "LIQUIDATION"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										6
									
								
								common/const/binancecode/side.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/const/binancecode/side.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package binancecode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						SideBuy  = "BUY"
 | 
				
			||||||
 | 
						SideSell = "SELL"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										6
									
								
								common/const/rediskey/api_user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/const/rediskey/api_user.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package rediskey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						//主单api和反单关系 List    {apiId}:{reverseApiId}
 | 
				
			||||||
 | 
						ApiReverseRelation = "api_reverse_relation"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										11
									
								
								common/const/rediskey/reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								common/const/rediskey/reverse_position.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package rediskey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						//反单仓位管理 {apiKey} hash key
 | 
				
			||||||
 | 
						ReversePositionKey = "reverse_position:%s"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						//反单配置 hash {order_type} {take_profit_ratio} {stop_loss_ratio} {reverse_premium_ratio}
 | 
				
			||||||
 | 
						ReverseSetting = "reverse_setting"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/bytedance/sonic"
 | 
						"github.com/bytedance/sonic"
 | 
				
			||||||
	"github.com/go-redis/redis/v8"
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RedisHelper 结构体封装了 Redis 客户端及上下文
 | 
					// RedisHelper 结构体封装了 Redis 客户端及上下文
 | 
				
			||||||
@ -460,44 +461,76 @@ func (r *RedisHelper) SetNX(key string, value interface{}, expiration time.Durat
 | 
				
			|||||||
	return result == "OK", nil
 | 
						return result == "OK", nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getFieldsFromStruct(obj interface{}) map[string]interface{} {
 | 
					// SetHashWithTags 改进版:支持 struct 或 map 输入
 | 
				
			||||||
 | 
					func (r *RedisHelper) SetHashWithTags(key string, obj interface{}) error {
 | 
				
			||||||
 | 
						fields, err := getFieldsFromStruct(obj)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = r.client.HSet(r.ctx, key, fields).Result()
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getFieldsFromStruct 改进版:处理指针并进行类型检查,返回 error
 | 
				
			||||||
 | 
					func getFieldsFromStruct(obj interface{}) (map[string]interface{}, error) {
 | 
				
			||||||
	fields := make(map[string]interface{})
 | 
						fields := make(map[string]interface{})
 | 
				
			||||||
	val := reflect.ValueOf(obj)
 | 
						val := reflect.ValueOf(obj)
 | 
				
			||||||
	typ := reflect.TypeOf(obj)
 | 
					
 | 
				
			||||||
 | 
						// 如果 obj 是指针,则解引用
 | 
				
			||||||
 | 
						if val.Kind() == reflect.Ptr {
 | 
				
			||||||
 | 
							val = val.Elem()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 确保我们正在处理的是一个结构体
 | 
				
			||||||
 | 
						if val.Kind() != reflect.Struct {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("期望一个结构体或结构体指针,但得到的是 %s", val.Kind())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						typ := val.Type() // 在解引用后获取类型
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < val.NumField(); i++ {
 | 
						for i := 0; i < val.NumField(); i++ {
 | 
				
			||||||
		field := typ.Field(i)
 | 
							field := typ.Field(i)
 | 
				
			||||||
		tag := field.Tag.Get("redis")
 | 
							tag := field.Tag.Get("redis") // 获取 redis tag
 | 
				
			||||||
		if tag != "" {
 | 
							if tag != "" {
 | 
				
			||||||
			fieldVal := val.Field(i)
 | 
								fieldVal := val.Field(i)
 | 
				
			||||||
			if fieldVal.Kind() == reflect.Slice || fieldVal.Kind() == reflect.Map {
 | 
								// 检查字段是否可导出,不可导出的字段无法直接通过 Interface() 访问
 | 
				
			||||||
 | 
								if !fieldVal.CanInterface() {
 | 
				
			||||||
 | 
									continue // 跳过不可导出字段
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch fieldVal.Kind() {
 | 
				
			||||||
 | 
								case reflect.Slice, reflect.Map:
 | 
				
			||||||
				// 处理切片或映射类型
 | 
									// 处理切片或映射类型
 | 
				
			||||||
				// 对于切片,使用索引作为字段名
 | 
					 | 
				
			||||||
				if fieldVal.Kind() == reflect.Slice {
 | 
									if fieldVal.Kind() == reflect.Slice {
 | 
				
			||||||
					for j := 0; j < fieldVal.Len(); j++ {
 | 
										for j := 0; j < fieldVal.Len(); j++ {
 | 
				
			||||||
						elem := fieldVal.Index(j).Interface()
 | 
											elem := fieldVal.Index(j).Interface()
 | 
				
			||||||
						fields[fmt.Sprintf("%s_%d", tag, j)] = elem
 | 
											fields[fmt.Sprintf("%s_%d", tag, j)] = elem
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				} else if fieldVal.Kind() == reflect.Map {
 | 
									} else if fieldVal.Kind() == reflect.Map {
 | 
				
			||||||
					// 对于映射,使用键作为字段名
 | 
					 | 
				
			||||||
					for _, key := range fieldVal.MapKeys() {
 | 
										for _, key := range fieldVal.MapKeys() {
 | 
				
			||||||
						elem := fieldVal.MapIndex(key).Interface()
 | 
											elem := fieldVal.MapIndex(key).Interface()
 | 
				
			||||||
						fields[fmt.Sprintf("%s_%v", tag, key.Interface())] = elem
 | 
											fields[fmt.Sprintf("%s_%v", tag, key.Interface())] = elem
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} else {
 | 
								case reflect.Struct:
 | 
				
			||||||
 | 
									if fieldVal.Type() == reflect.TypeOf(decimal.Decimal{}) {
 | 
				
			||||||
 | 
										if decVal, ok := fieldVal.Interface().(decimal.Decimal); ok {
 | 
				
			||||||
 | 
											// 将 decimal.Decimal 直接转换为字符串来保留精度
 | 
				
			||||||
 | 
											fields[tag] = decVal.String()
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											// 理论上不应该发生,但作为回退
 | 
				
			||||||
 | 
											fields[tag] = fieldVal.Interface()
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										fields[tag] = fieldVal.Interface()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
				fields[tag] = fieldVal.Interface()
 | 
									fields[tag] = fieldVal.Interface()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return fields, nil
 | 
				
			||||||
	return fields
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *RedisHelper) SetHashWithTags(key string, obj interface{}) error {
 | 
					 | 
				
			||||||
	fields := getFieldsFromStruct(obj)
 | 
					 | 
				
			||||||
	_, err := r.client.HSet(r.ctx, key, fields).Result()
 | 
					 | 
				
			||||||
	return err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HSetField 设置哈希中的一个字段
 | 
					// HSetField 设置哈希中的一个字段
 | 
				
			||||||
@ -543,6 +576,82 @@ func (r *RedisHelper) HGetAllFields(key string) (map[string]string, error) {
 | 
				
			|||||||
	return fields, nil
 | 
						return fields, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HGetAsObject 获取哈希中所有字段的值并反序列化为对象
 | 
				
			||||||
 | 
					func (r *RedisHelper) HGetAsObject(key string, out interface{}) error {
 | 
				
			||||||
 | 
						data, err := r.HGetAllFields(key)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val := reflect.ValueOf(out)
 | 
				
			||||||
 | 
						if val.Kind() != reflect.Ptr || val.IsNil() {
 | 
				
			||||||
 | 
							return fmt.Errorf("output must be a non-nil pointer to a struct")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						val = val.Elem()
 | 
				
			||||||
 | 
						typ := val.Type()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < typ.NumField(); i++ {
 | 
				
			||||||
 | 
							field := typ.Field(i)
 | 
				
			||||||
 | 
							tag := field.Tag.Get("redis")
 | 
				
			||||||
 | 
							if tag == "" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							strVal, ok := data[tag]
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fieldVal := val.Field(i)
 | 
				
			||||||
 | 
							if !fieldVal.CanSet() {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch fieldVal.Kind() {
 | 
				
			||||||
 | 
							case reflect.String:
 | 
				
			||||||
 | 
								fieldVal.SetString(strVal)
 | 
				
			||||||
 | 
							case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
				
			||||||
 | 
								if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil {
 | 
				
			||||||
 | 
									fieldVal.SetInt(intVal)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | 
				
			||||||
 | 
								if uintVal, err := strconv.ParseUint(strVal, 10, 64); err == nil {
 | 
				
			||||||
 | 
									fieldVal.SetUint(uintVal)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case reflect.Float32, reflect.Float64:
 | 
				
			||||||
 | 
								if floatVal, err := strconv.ParseFloat(strVal, 64); err == nil {
 | 
				
			||||||
 | 
									fieldVal.SetFloat(floatVal)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case reflect.Bool:
 | 
				
			||||||
 | 
								if boolVal, err := strconv.ParseBool(strVal); err == nil {
 | 
				
			||||||
 | 
									fieldVal.SetBool(boolVal)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case reflect.Struct:
 | 
				
			||||||
 | 
								// 针对 time.Time 特别处理
 | 
				
			||||||
 | 
								if fieldVal.Type() == reflect.TypeOf(time.Time{}) {
 | 
				
			||||||
 | 
									if t, err := time.Parse(time.RFC3339, strVal); err == nil {
 | 
				
			||||||
 | 
										fieldVal.Set(reflect.ValueOf(t))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else if fieldVal.Type() == reflect.TypeOf(decimal.Decimal{}) {
 | 
				
			||||||
 | 
									if t, err := decimal.NewFromString(strVal); err == nil {
 | 
				
			||||||
 | 
										fieldVal.Set(reflect.ValueOf(t))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// 其他 struct 尝试用 json 反序列化
 | 
				
			||||||
 | 
									ptr := reflect.New(fieldVal.Type()).Interface()
 | 
				
			||||||
 | 
									if err := sonic.Unmarshal([]byte(strVal), ptr); err == nil {
 | 
				
			||||||
 | 
										fieldVal.Set(reflect.ValueOf(ptr).Elem())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case reflect.Slice, reflect.Map:
 | 
				
			||||||
 | 
								ptr := reflect.New(fieldVal.Type()).Interface()
 | 
				
			||||||
 | 
								if err := sonic.Unmarshal([]byte(strVal), ptr); err == nil {
 | 
				
			||||||
 | 
									fieldVal.Set(reflect.ValueOf(ptr).Elem())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HDelField 删除哈希中的某个字段
 | 
					// HDelField 删除哈希中的某个字段
 | 
				
			||||||
func (r *RedisHelper) HDelField(key, field string) error {
 | 
					func (r *RedisHelper) HDelField(key, field string) error {
 | 
				
			||||||
	_, err := r.client.HDel(r.ctx, key, field).Result()
 | 
						_, err := r.client.HDel(r.ctx, key, field).Result()
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,24 @@ func BusinessInit(db *gorm.DB) {
 | 
				
			|||||||
		os.Exit(-1)
 | 
							os.Exit(-1)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lineApiService := service.LineApiUser{}
 | 
				
			||||||
 | 
						lineApiService.Orm = db
 | 
				
			||||||
 | 
						lineApiService.Log = logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := lineApiService.CacheRelation(); err != nil {
 | 
				
			||||||
 | 
							lineApiService.Log.Errorf("初始化api关系失败 err:%v", err)
 | 
				
			||||||
 | 
							os.Exit(-1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reverseSettingService := service.LineReverseSetting{}
 | 
				
			||||||
 | 
						reverseSettingService.Orm = db
 | 
				
			||||||
 | 
						reverseSettingService.Log = lineApiService.Log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := reverseSettingService.SaveCache(models.LineReverseSetting{}); err != nil {
 | 
				
			||||||
 | 
							reverseSettingService.Log.Errorf("初始化反单设置失败 err:%v", err)
 | 
				
			||||||
 | 
							os.Exit(-1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//初始化参数配置
 | 
						//初始化参数配置
 | 
				
			||||||
	cacheservice.InitConfigCache(db)
 | 
						cacheservice.InitConfigCache(db)
 | 
				
			||||||
	//初始化可缓存价格交易对
 | 
						//初始化可缓存价格交易对
 | 
				
			||||||
 | 
				
			|||||||
@ -84,3 +84,44 @@ func UserSubscribeInit(orm *gorm.DB, ctx context.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 重启连接
 | 
				
			||||||
 | 
					func RestartConnect() error {
 | 
				
			||||||
 | 
						spotSockets := excservice.SpotSockets
 | 
				
			||||||
 | 
						futuresSockets := excservice.FutureSockets
 | 
				
			||||||
 | 
						timeOut := 22 * time.Hour
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range spotSockets {
 | 
				
			||||||
 | 
							//超过22小时,重新连接
 | 
				
			||||||
 | 
							if time.Since(item.ConnectTime) > timeOut {
 | 
				
			||||||
 | 
								if err := item.ReplaceConnection(); err != nil {
 | 
				
			||||||
 | 
									log.Errorf("现货重启连接失败 key:%s,error:%s", item.GetKey(), err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range futuresSockets {
 | 
				
			||||||
 | 
							//超过22小时,重新连接
 | 
				
			||||||
 | 
							if time.Since(item.ConnectTime) > timeOut {
 | 
				
			||||||
 | 
								if err := item.ReplaceConnection(); err != nil {
 | 
				
			||||||
 | 
									log.Errorf("合约重启连接失败 key:%s,error:%s", item.GetKey(), err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 假死重启
 | 
				
			||||||
 | 
					func DeadCheck() {
 | 
				
			||||||
 | 
						spotSockets := excservice.SpotSockets
 | 
				
			||||||
 | 
						futuresSockets := excservice.FutureSockets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range spotSockets {
 | 
				
			||||||
 | 
							item.DeadCheck()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range futuresSockets {
 | 
				
			||||||
 | 
							item.DeadCheck()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							@ -39,6 +39,7 @@ require (
 | 
				
			|||||||
	github.com/shirou/gopsutil/v3 v3.23.10
 | 
						github.com/shirou/gopsutil/v3 v3.23.10
 | 
				
			||||||
	github.com/shopspring/decimal v1.2.0
 | 
						github.com/shopspring/decimal v1.2.0
 | 
				
			||||||
	github.com/spf13/cobra v1.7.0
 | 
						github.com/spf13/cobra v1.7.0
 | 
				
			||||||
 | 
						github.com/stretchr/testify v1.9.0
 | 
				
			||||||
	github.com/swaggo/files v1.0.1
 | 
						github.com/swaggo/files v1.0.1
 | 
				
			||||||
	github.com/swaggo/gin-swagger v1.6.0
 | 
						github.com/swaggo/gin-swagger v1.6.0
 | 
				
			||||||
	github.com/swaggo/swag v1.16.2
 | 
						github.com/swaggo/swag v1.16.2
 | 
				
			||||||
@ -82,6 +83,7 @@ require (
 | 
				
			|||||||
	github.com/cloudwego/iasm v0.2.0 // indirect
 | 
						github.com/cloudwego/iasm v0.2.0 // indirect
 | 
				
			||||||
	github.com/coreos/go-semver v0.3.1 // indirect
 | 
						github.com/coreos/go-semver v0.3.1 // indirect
 | 
				
			||||||
	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 | 
						github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 | 
				
			||||||
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
						github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
				
			||||||
	github.com/fatih/color v1.15.0 // indirect
 | 
						github.com/fatih/color v1.15.0 // indirect
 | 
				
			||||||
	github.com/fatih/structs v1.1.0 // indirect
 | 
						github.com/fatih/structs v1.1.0 // indirect
 | 
				
			||||||
@ -143,6 +145,7 @@ require (
 | 
				
			|||||||
	github.com/nsqio/go-nsq v1.1.0 // indirect
 | 
						github.com/nsqio/go-nsq v1.1.0 // indirect
 | 
				
			||||||
	github.com/nyaruka/phonenumbers v1.0.55 // indirect
 | 
						github.com/nyaruka/phonenumbers v1.0.55 // indirect
 | 
				
			||||||
	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 | 
						github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 | 
				
			||||||
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
 | 
						github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
 | 
				
			||||||
	github.com/prometheus/client_model v0.5.0 // indirect
 | 
						github.com/prometheus/client_model v0.5.0 // indirect
 | 
				
			||||||
	github.com/prometheus/common v0.45.0 // indirect
 | 
						github.com/prometheus/common v0.45.0 // indirect
 | 
				
			||||||
 | 
				
			|||||||
@ -37,6 +37,10 @@ type TradeSet struct {
 | 
				
			|||||||
	E            int64   `json:"-"`                            //推送时间
 | 
						E            int64   `json:"-"`                            //推送时间
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *TradeSet) GetSymbol() string {
 | 
				
			||||||
 | 
						return e.Coin + e.Currency
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//CommissionType  int     `db:"commissiontype"`                 //手续费:1买,2卖,3双向
 | 
					//CommissionType  int     `db:"commissiontype"`                 //手续费:1买,2卖,3双向
 | 
				
			||||||
//DepositNum      float64 `db:"depositnum" json:"depositnum"`     //保证金规模(手)
 | 
					//DepositNum      float64 `db:"depositnum" json:"depositnum"`     //保证金规模(手)
 | 
				
			||||||
//ForceRate       float64 `db:"forcerate" json:"forcerate"`       //维持保证金率1%
 | 
					//ForceRate       float64 `db:"forcerate" json:"forcerate"`       //维持保证金率1%
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										46
									
								
								pkg/maphelper/maphelper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								pkg/maphelper/maphelper.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					package maphelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetString(m map[string]interface{}, key string) (string, error) {
 | 
				
			||||||
 | 
						val, ok := m[key]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("参数错误,缺少字段 %s", key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						strVal, ok := val.(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("字段 %s 类型错误", key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return strVal, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetFloat64(m map[string]interface{}, key string) (float64, error) {
 | 
				
			||||||
 | 
						val, ok := m[key]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return 0, fmt.Errorf("参数错误,缺少字段 %s", key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						f, ok := val.(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return 0, fmt.Errorf("字段 %s 类型错误", key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return f, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetBool(m map[string]interface{}, key string) bool {
 | 
				
			||||||
 | 
						if val, ok := m[key].(bool); ok {
 | 
				
			||||||
 | 
							return val
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetDecimal(m map[string]interface{}, key string) decimal.Decimal {
 | 
				
			||||||
 | 
						if val, ok := m[key].(string); ok {
 | 
				
			||||||
 | 
							d, _ := decimal.NewFromString(val)
 | 
				
			||||||
 | 
							return d
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return decimal.Zero
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										53
									
								
								pkg/retryhelper/retryhelper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								pkg/retryhelper/retryhelper.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					package retryhelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RetryOptions 定义重试的配置项
 | 
				
			||||||
 | 
					type RetryOptions struct {
 | 
				
			||||||
 | 
						MaxRetries      int              // 最大重试次数
 | 
				
			||||||
 | 
						InitialInterval time.Duration    // 初始重试间隔
 | 
				
			||||||
 | 
						MaxInterval     time.Duration    // 最大重试间隔
 | 
				
			||||||
 | 
						BackoffFactor   float64          // 指数退避的增长因子
 | 
				
			||||||
 | 
						RetryableErrFn  func(error) bool // 用于判断是否为可重试错误的函数(可选)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DefaultRetryOptions() RetryOptions {
 | 
				
			||||||
 | 
						return RetryOptions{
 | 
				
			||||||
 | 
							MaxRetries:      3,
 | 
				
			||||||
 | 
							InitialInterval: 150 * time.Millisecond,
 | 
				
			||||||
 | 
							MaxInterval:     3 * time.Second,
 | 
				
			||||||
 | 
							BackoffFactor:   2.0,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Retry 重试通用函数(用于没有返回值的函数)
 | 
				
			||||||
 | 
					func Retry(op func() error, opts RetryOptions) error {
 | 
				
			||||||
 | 
						_, err := RetryWithResult(func() (struct{}, error) {
 | 
				
			||||||
 | 
							return struct{}{}, op()
 | 
				
			||||||
 | 
						}, opts)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RetryWithResult 重试通用函数(用于带返回值的函数)
 | 
				
			||||||
 | 
					func RetryWithResult[T any](op func() (T, error), opts RetryOptions) (result T, err error) {
 | 
				
			||||||
 | 
						interval := opts.InitialInterval
 | 
				
			||||||
 | 
						for attempt := 0; attempt <= opts.MaxRetries; attempt++ {
 | 
				
			||||||
 | 
							result, err = op()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return result, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if opts.RetryableErrFn != nil && !opts.RetryableErrFn(err) {
 | 
				
			||||||
 | 
								return result, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if attempt < opts.MaxRetries {
 | 
				
			||||||
 | 
								time.Sleep(interval)
 | 
				
			||||||
 | 
								interval = time.Duration(math.Min(float64(opts.MaxInterval), float64(interval)*opts.BackoffFactor))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -28,3 +28,7 @@ func init() {
 | 
				
			|||||||
func GetOrderId() int64 {
 | 
					func GetOrderId() int64 {
 | 
				
			||||||
	return snowNode.Generate().Int64()
 | 
						return snowNode.Generate().Int64()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetOrderNo() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%d", GetOrderId())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -467,6 +467,21 @@ func GetApiInfo(apiId int) (DbModels.LineApiUser, error) {
 | 
				
			|||||||
	return api, nil
 | 
						return api, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetReverseApiInfo 根据apiKey获取api用户信息
 | 
				
			||||||
 | 
					func GetApiInfoByKey(apiKey string) DbModels.LineApiUser {
 | 
				
			||||||
 | 
						result := DbModels.LineApiUser{}
 | 
				
			||||||
 | 
						keys, _ := helper.DefaultRedis.GetAllKeysAndValues(fmt.Sprintf(rediskey.API_USER, "*"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, key := range keys {
 | 
				
			||||||
 | 
							if strings.Contains(key, apiKey) {
 | 
				
			||||||
 | 
								sonic.UnmarshalString(key, &result)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
根据A账户获取B账号信息
 | 
					根据A账户获取B账号信息
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCancelFutClosePosition(t *testing.T) {
 | 
					func TestCancelFutClosePosition(t *testing.T) {
 | 
				
			||||||
	dsn := "root:root@tcp(192.168.123.216:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
 | 
						dsn := "root:123456@tcp(127.0.0.1:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
 | 
				
			||||||
	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
 | 
						db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
 | 
				
			||||||
	var apiUserInfo models.LineApiUser
 | 
						var apiUserInfo models.LineApiUser
 | 
				
			||||||
	db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
 | 
						db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
 | 
				
			||||||
 | 
				
			|||||||
@ -479,3 +479,13 @@ func GetOpenOrderSns(db *gorm.DB, mainIds []int) ([]string, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return result, nil
 | 
						return result, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 回去反单默认配置
 | 
				
			||||||
 | 
					func GetReverseSetting(db *gorm.DB) (DbModels.LineReverseSetting, error) {
 | 
				
			||||||
 | 
						var setting DbModels.LineReverseSetting
 | 
				
			||||||
 | 
						if err := db.Model(&DbModels.LineReverseSetting{}).First(&setting).Error; err != nil {
 | 
				
			||||||
 | 
							return setting, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return setting, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										358
									
								
								services/binanceservice/config_optimized.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								services/binanceservice/config_optimized.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,358 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OptimizedConfig 优化配置结构
 | 
				
			||||||
 | 
					type OptimizedConfig struct {
 | 
				
			||||||
 | 
						// 锁配置
 | 
				
			||||||
 | 
						LockConfig LockConfig `json:"lock_config"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 重试配置
 | 
				
			||||||
 | 
						RetryConfig RetryConfig `json:"retry_config"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 熔断器配置
 | 
				
			||||||
 | 
						CircuitBreakerConfig CircuitBreakerConfig `json:"circuit_breaker_config"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 同步配置
 | 
				
			||||||
 | 
						SyncConfig SyncConfig `json:"sync_config"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 性能配置
 | 
				
			||||||
 | 
						PerformanceConfig PerformanceConfig `json:"performance_config"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 监控配置
 | 
				
			||||||
 | 
						MonitorConfig MonitorConfig `json:"monitor_config"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LockConfig 锁配置
 | 
				
			||||||
 | 
					type LockConfig struct {
 | 
				
			||||||
 | 
						// Redis分布式锁超时时间
 | 
				
			||||||
 | 
						RedisLockTimeout time.Duration `json:"redis_lock_timeout"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 获取锁等待时间
 | 
				
			||||||
 | 
						LockWaitTimeout time.Duration `json:"lock_wait_timeout"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 订单处理锁前缀
 | 
				
			||||||
 | 
						OrderLockPrefix string `json:"order_lock_prefix"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 持仓更新锁前缀
 | 
				
			||||||
 | 
						PositionLockPrefix string `json:"position_lock_prefix"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 止盈止损锁前缀
 | 
				
			||||||
 | 
						TakeProfitLockPrefix string `json:"take_profit_lock_prefix"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CircuitBreakerConfig 熔断器配置
 | 
				
			||||||
 | 
					type CircuitBreakerConfig struct {
 | 
				
			||||||
 | 
						Enabled           bool          `json:"enabled"`
 | 
				
			||||||
 | 
						FailureThreshold  int           `json:"failure_threshold"`
 | 
				
			||||||
 | 
						SuccessThreshold  int           `json:"success_threshold"`
 | 
				
			||||||
 | 
						Timeout           time.Duration `json:"timeout"`
 | 
				
			||||||
 | 
						HalfOpenMaxCalls  int           `json:"half_open_max_calls"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RetryConfig 重试配置
 | 
				
			||||||
 | 
					type RetryConfig struct {
 | 
				
			||||||
 | 
						// 最大重试次数
 | 
				
			||||||
 | 
						MaxRetries int `json:"max_retries"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 重试延迟
 | 
				
			||||||
 | 
						RetryDelay time.Duration `json:"retry_delay"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 指数退避因子
 | 
				
			||||||
 | 
						BackoffFactor float64 `json:"backoff_factor"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 最大重试延迟
 | 
				
			||||||
 | 
						MaxRetryDelay time.Duration `json:"max_retry_delay"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// API调用重试次数
 | 
				
			||||||
 | 
						ApiRetryCount int `json:"api_retry_count"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 数据库操作重试次数
 | 
				
			||||||
 | 
						DbRetryCount int `json:"db_retry_count"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SyncConfig 同步配置
 | 
				
			||||||
 | 
					type SyncConfig struct {
 | 
				
			||||||
 | 
						// 持仓同步检查间隔
 | 
				
			||||||
 | 
						PositionSyncInterval time.Duration `json:"position_sync_interval"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 持仓差异阈值
 | 
				
			||||||
 | 
						PositionDiffThreshold decimal.Decimal `json:"position_diff_threshold"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 强制同步阈值
 | 
				
			||||||
 | 
						ForceSyncThreshold decimal.Decimal `json:"force_sync_threshold"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 同步超时时间
 | 
				
			||||||
 | 
						SyncTimeout time.Duration `json:"sync_timeout"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 是否启用自动同步
 | 
				
			||||||
 | 
						AutoSyncEnabled bool `json:"auto_sync_enabled"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PerformanceConfig 性能配置
 | 
				
			||||||
 | 
					type PerformanceConfig struct {
 | 
				
			||||||
 | 
						// 批量操作大小
 | 
				
			||||||
 | 
						BatchSize int `json:"batch_size"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 异步处理队列大小
 | 
				
			||||||
 | 
						AsyncQueueSize int `json:"async_queue_size"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 工作协程数量
 | 
				
			||||||
 | 
						WorkerCount int `json:"worker_count"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 数据库连接池大小
 | 
				
			||||||
 | 
						DbPoolSize int `json:"db_pool_size"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 缓存过期时间
 | 
				
			||||||
 | 
						CacheExpiration time.Duration `json:"cache_expiration"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 是否启用缓存
 | 
				
			||||||
 | 
						CacheEnabled bool `json:"cache_enabled"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MonitorConfig 监控配置
 | 
				
			||||||
 | 
					type MonitorConfig struct {
 | 
				
			||||||
 | 
						// 是否启用监控
 | 
				
			||||||
 | 
						Enabled bool `json:"enabled"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 监控数据收集间隔
 | 
				
			||||||
 | 
						CollectInterval time.Duration `json:"collect_interval"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 告警阈值配置
 | 
				
			||||||
 | 
						AlertThresholds AlertThresholds `json:"alert_thresholds"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 日志级别
 | 
				
			||||||
 | 
						LogLevel string `json:"log_level"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 是否启用性能分析
 | 
				
			||||||
 | 
						ProfileEnabled bool `json:"profile_enabled"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AlertThresholds 告警阈值配置
 | 
				
			||||||
 | 
					type AlertThresholds struct {
 | 
				
			||||||
 | 
						// 订单处理失败率阈值 (百分比)
 | 
				
			||||||
 | 
						OrderFailureRate float64 `json:"order_failure_rate"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 持仓同步失败率阈值 (百分比)
 | 
				
			||||||
 | 
						PositionSyncFailureRate float64 `json:"position_sync_failure_rate"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// API调用失败率阈值 (百分比)
 | 
				
			||||||
 | 
						ApiFailureRate float64 `json:"api_failure_rate"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 响应时间阈值 (毫秒)
 | 
				
			||||||
 | 
						ResponseTimeThreshold time.Duration `json:"response_time_threshold"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 内存使用率阈值 (百分比)
 | 
				
			||||||
 | 
						MemoryUsageThreshold float64 `json:"memory_usage_threshold"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// CPU使用率阈值 (百分比)
 | 
				
			||||||
 | 
						CpuUsageThreshold float64 `json:"cpu_usage_threshold"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetDefaultOptimizedConfig 获取默认优化配置
 | 
				
			||||||
 | 
					func GetDefaultOptimizedConfig() *OptimizedConfig {
 | 
				
			||||||
 | 
						return &OptimizedConfig{
 | 
				
			||||||
 | 
							LockConfig: LockConfig{
 | 
				
			||||||
 | 
								RedisLockTimeout:     30 * time.Second,
 | 
				
			||||||
 | 
								LockWaitTimeout:      5 * time.Second,
 | 
				
			||||||
 | 
								OrderLockPrefix:      "reverse_order_lock:",
 | 
				
			||||||
 | 
								PositionLockPrefix:   "position_update_lock:",
 | 
				
			||||||
 | 
								TakeProfitLockPrefix: "take_profit_lock:",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							RetryConfig: RetryConfig{
 | 
				
			||||||
 | 
								MaxRetries:    3,
 | 
				
			||||||
 | 
								RetryDelay:    time.Second,
 | 
				
			||||||
 | 
								BackoffFactor: 2.0,
 | 
				
			||||||
 | 
								MaxRetryDelay: 10 * time.Second,
 | 
				
			||||||
 | 
								ApiRetryCount: 3,
 | 
				
			||||||
 | 
								DbRetryCount:  3,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							CircuitBreakerConfig: CircuitBreakerConfig{
 | 
				
			||||||
 | 
								Enabled:          true,
 | 
				
			||||||
 | 
								FailureThreshold: 5,
 | 
				
			||||||
 | 
								SuccessThreshold: 3,
 | 
				
			||||||
 | 
								Timeout:          60 * time.Second,
 | 
				
			||||||
 | 
								HalfOpenMaxCalls: 3,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							SyncConfig: SyncConfig{
 | 
				
			||||||
 | 
								PositionSyncInterval:  30 * time.Second,
 | 
				
			||||||
 | 
								PositionDiffThreshold: decimal.NewFromFloat(0.001),
 | 
				
			||||||
 | 
								ForceSyncThreshold:    decimal.NewFromFloat(0.01),
 | 
				
			||||||
 | 
								SyncTimeout:           10 * time.Second,
 | 
				
			||||||
 | 
								AutoSyncEnabled:       true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							PerformanceConfig: PerformanceConfig{
 | 
				
			||||||
 | 
								BatchSize:       10,
 | 
				
			||||||
 | 
								AsyncQueueSize:  1000,
 | 
				
			||||||
 | 
								WorkerCount:     5,
 | 
				
			||||||
 | 
								DbPoolSize:      20,
 | 
				
			||||||
 | 
								CacheExpiration: 5 * time.Minute,
 | 
				
			||||||
 | 
								CacheEnabled:    true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							MonitorConfig: MonitorConfig{
 | 
				
			||||||
 | 
								Enabled:         true,
 | 
				
			||||||
 | 
								CollectInterval: time.Minute,
 | 
				
			||||||
 | 
								AlertThresholds: AlertThresholds{
 | 
				
			||||||
 | 
									OrderFailureRate:        5.0,  // 5%
 | 
				
			||||||
 | 
									PositionSyncFailureRate: 2.0,  // 2%
 | 
				
			||||||
 | 
									ApiFailureRate:          10.0, // 10%
 | 
				
			||||||
 | 
									ResponseTimeThreshold:   5 * time.Second,
 | 
				
			||||||
 | 
									MemoryUsageThreshold:    80.0, // 80%
 | 
				
			||||||
 | 
									CpuUsageThreshold:       70.0, // 70%
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								LogLevel:       "info",
 | 
				
			||||||
 | 
								ProfileEnabled: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ValidateConfig 验证配置有效性
 | 
				
			||||||
 | 
					func (c *OptimizedConfig) ValidateConfig() error {
 | 
				
			||||||
 | 
						// 验证锁配置
 | 
				
			||||||
 | 
						if c.LockConfig.RedisLockTimeout <= 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("Redis锁超时时间必须大于0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.LockConfig.LockWaitTimeout <= 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("锁等待超时时间必须大于0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 验证重试配置
 | 
				
			||||||
 | 
						if c.RetryConfig.MaxRetries < 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("最大重试次数不能为负数")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.RetryConfig.RetryDelay <= 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("重试延迟必须大于0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.RetryConfig.BackoffFactor <= 1.0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("退避因子必须大于1.0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 验证同步配置
 | 
				
			||||||
 | 
						if c.SyncConfig.PositionSyncInterval <= 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("持仓同步间隔必须大于0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.SyncConfig.PositionDiffThreshold.IsNegative() {
 | 
				
			||||||
 | 
							return fmt.Errorf("持仓差异阈值不能为负数")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 验证性能配置
 | 
				
			||||||
 | 
						if c.PerformanceConfig.BatchSize <= 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("批量操作大小必须大于0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.PerformanceConfig.WorkerCount <= 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("工作协程数量必须大于0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.PerformanceConfig.DbPoolSize <= 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("数据库连接池大小必须大于0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 验证监控配置
 | 
				
			||||||
 | 
						if c.MonitorConfig.Enabled {
 | 
				
			||||||
 | 
							if c.MonitorConfig.CollectInterval <= 0 {
 | 
				
			||||||
 | 
								return fmt.Errorf("监控数据收集间隔必须大于0")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if c.MonitorConfig.AlertThresholds.OrderFailureRate < 0 || c.MonitorConfig.AlertThresholds.OrderFailureRate > 100 {
 | 
				
			||||||
 | 
								return fmt.Errorf("订单失败率阈值必须在0-100之间")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if c.MonitorConfig.AlertThresholds.MemoryUsageThreshold < 0 || c.MonitorConfig.AlertThresholds.MemoryUsageThreshold > 100 {
 | 
				
			||||||
 | 
								return fmt.Errorf("内存使用率阈值必须在0-100之间")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if c.MonitorConfig.AlertThresholds.CpuUsageThreshold < 0 || c.MonitorConfig.AlertThresholds.CpuUsageThreshold > 100 {
 | 
				
			||||||
 | 
								return fmt.Errorf("CPU使用率阈值必须在0-100之间")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLockKey 生成锁键名
 | 
				
			||||||
 | 
					func (c *OptimizedConfig) GetLockKey(lockType, identifier string) string {
 | 
				
			||||||
 | 
						switch lockType {
 | 
				
			||||||
 | 
						case "order":
 | 
				
			||||||
 | 
							return c.LockConfig.OrderLockPrefix + identifier
 | 
				
			||||||
 | 
						case "position":
 | 
				
			||||||
 | 
							return c.LockConfig.PositionLockPrefix + identifier
 | 
				
			||||||
 | 
						case "take_profit":
 | 
				
			||||||
 | 
							return c.LockConfig.TakeProfitLockPrefix + identifier
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return "unknown_lock:" + identifier
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRetryableError 判断错误是否可重试
 | 
				
			||||||
 | 
					func (c *OptimizedConfig) IsRetryableError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						errStr := err.Error()
 | 
				
			||||||
 | 
						// 定义不可重试的错误类型
 | 
				
			||||||
 | 
						nonRetryableErrors := []string{
 | 
				
			||||||
 | 
							"余额不足",
 | 
				
			||||||
 | 
							"订单重复",
 | 
				
			||||||
 | 
							"API-key",
 | 
				
			||||||
 | 
							"无效",
 | 
				
			||||||
 | 
							"权限",
 | 
				
			||||||
 | 
							"签名",
 | 
				
			||||||
 | 
							"参数错误",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						for _, nonRetryable := range nonRetryableErrors {
 | 
				
			||||||
 | 
							if contains(errStr, []string{nonRetryable}) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRetryDelay 计算重试延迟时间
 | 
				
			||||||
 | 
					func (c *OptimizedConfig) GetRetryDelay(attempt int) time.Duration {
 | 
				
			||||||
 | 
						if attempt <= 0 {
 | 
				
			||||||
 | 
							return c.RetryConfig.RetryDelay
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 指数退避算法
 | 
				
			||||||
 | 
						delay := c.RetryConfig.RetryDelay
 | 
				
			||||||
 | 
						for i := 0; i < attempt; i++ {
 | 
				
			||||||
 | 
							delay = time.Duration(float64(delay) * c.RetryConfig.BackoffFactor)
 | 
				
			||||||
 | 
							if delay > c.RetryConfig.MaxRetryDelay {
 | 
				
			||||||
 | 
								return c.RetryConfig.MaxRetryDelay
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return delay
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShouldSync 判断是否需要同步持仓
 | 
				
			||||||
 | 
					func (c *OptimizedConfig) ShouldSync(exchangePosition, systemPosition decimal.Decimal) bool {
 | 
				
			||||||
 | 
						diff := exchangePosition.Sub(systemPosition).Abs()
 | 
				
			||||||
 | 
						return diff.GreaterThan(c.SyncConfig.PositionDiffThreshold)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ShouldForceSync 判断是否需要强制同步
 | 
				
			||||||
 | 
					func (c *OptimizedConfig) ShouldForceSync(exchangePosition, systemPosition decimal.Decimal) bool {
 | 
				
			||||||
 | 
						diff := exchangePosition.Sub(systemPosition).Abs()
 | 
				
			||||||
 | 
						return diff.GreaterThan(c.SyncConfig.ForceSyncThreshold)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 全局配置实例
 | 
				
			||||||
 | 
					var GlobalOptimizedConfig *OptimizedConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InitOptimizedConfig 初始化优化配置
 | 
				
			||||||
 | 
					func InitOptimizedConfig() error {
 | 
				
			||||||
 | 
						GlobalOptimizedConfig = GetDefaultOptimizedConfig()
 | 
				
			||||||
 | 
						return GlobalOptimizedConfig.ValidateConfig()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetOptimizedConfig 获取全局优化配置
 | 
				
			||||||
 | 
					func GetOptimizedConfig() *OptimizedConfig {
 | 
				
			||||||
 | 
						if GlobalOptimizedConfig == nil {
 | 
				
			||||||
 | 
							InitOptimizedConfig()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return GlobalOptimizedConfig
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										676
									
								
								services/binanceservice/error_handler_optimized.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										676
									
								
								services/binanceservice/error_handler_optimized.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,676 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorType 错误类型
 | 
				
			||||||
 | 
					type ErrorType string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ErrorTypeNetwork    ErrorType = "network"
 | 
				
			||||||
 | 
						ErrorTypeDatabase   ErrorType = "database"
 | 
				
			||||||
 | 
						ErrorTypeBusiness   ErrorType = "business"
 | 
				
			||||||
 | 
						ErrorTypeSystem     ErrorType = "system"
 | 
				
			||||||
 | 
						ErrorTypeValidation ErrorType = "validation"
 | 
				
			||||||
 | 
						ErrorTypeTimeout    ErrorType = "timeout"
 | 
				
			||||||
 | 
						ErrorTypeRateLimit  ErrorType = "rate_limit"
 | 
				
			||||||
 | 
						ErrorTypeAuth       ErrorType = "auth"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorSeverity 错误严重程度
 | 
				
			||||||
 | 
					type ErrorSeverity string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						SeverityLow      ErrorSeverity = "low"
 | 
				
			||||||
 | 
						SeverityMedium   ErrorSeverity = "medium"
 | 
				
			||||||
 | 
						SeverityHigh     ErrorSeverity = "high"
 | 
				
			||||||
 | 
						SeverityCritical ErrorSeverity = "critical"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorAction 错误处理动作
 | 
				
			||||||
 | 
					type ErrorAction string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ActionRetry     ErrorAction = "retry"
 | 
				
			||||||
 | 
						ActionFallback  ErrorAction = "fallback"
 | 
				
			||||||
 | 
						ActionAlert     ErrorAction = "alert"
 | 
				
			||||||
 | 
						ActionIgnore    ErrorAction = "ignore"
 | 
				
			||||||
 | 
						ActionCircuit   ErrorAction = "circuit"
 | 
				
			||||||
 | 
						ActionDegrade   ErrorAction = "degrade"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorInfo 错误信息
 | 
				
			||||||
 | 
					type ErrorInfo struct {
 | 
				
			||||||
 | 
						Type        ErrorType     `json:"type"`
 | 
				
			||||||
 | 
						Severity    ErrorSeverity `json:"severity"`
 | 
				
			||||||
 | 
						Message     string        `json:"message"`
 | 
				
			||||||
 | 
						OriginalErr error         `json:"-"`
 | 
				
			||||||
 | 
						Context     string        `json:"context"`
 | 
				
			||||||
 | 
						Timestamp   time.Time     `json:"timestamp"`
 | 
				
			||||||
 | 
						Retryable   bool          `json:"retryable"`
 | 
				
			||||||
 | 
						Action      ErrorAction   `json:"action"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RetryConfig 和 CircuitBreakerConfig 已在 config_optimized.go 中定义
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorHandler 错误处理器
 | 
				
			||||||
 | 
					type ErrorHandler struct {
 | 
				
			||||||
 | 
						config           *OptimizedConfig
 | 
				
			||||||
 | 
						metricsCollector *MetricsCollector
 | 
				
			||||||
 | 
						circuitBreakers  map[string]*CircuitBreaker
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CircuitBreaker 熔断器
 | 
				
			||||||
 | 
					type CircuitBreaker struct {
 | 
				
			||||||
 | 
						name         string
 | 
				
			||||||
 | 
						config       CircuitBreakerConfig
 | 
				
			||||||
 | 
						state        CircuitState
 | 
				
			||||||
 | 
						failureCount int
 | 
				
			||||||
 | 
						successCount int
 | 
				
			||||||
 | 
						lastFailTime time.Time
 | 
				
			||||||
 | 
						nextRetry    time.Time
 | 
				
			||||||
 | 
						halfOpenCalls int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CircuitState 熔断器状态
 | 
				
			||||||
 | 
					type CircuitState string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						StateClosed   CircuitState = "closed"
 | 
				
			||||||
 | 
						StateOpen     CircuitState = "open"
 | 
				
			||||||
 | 
						StateHalfOpen CircuitState = "half_open"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewErrorHandler 创建错误处理器
 | 
				
			||||||
 | 
					func NewErrorHandler(config *OptimizedConfig, metricsCollector *MetricsCollector) *ErrorHandler {
 | 
				
			||||||
 | 
						return &ErrorHandler{
 | 
				
			||||||
 | 
							config:           config,
 | 
				
			||||||
 | 
							metricsCollector: metricsCollector,
 | 
				
			||||||
 | 
							circuitBreakers:  make(map[string]*CircuitBreaker),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClassifyError 分类错误
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) ClassifyError(err error, context string) *ErrorInfo {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorInfo := &ErrorInfo{
 | 
				
			||||||
 | 
							OriginalErr: err,
 | 
				
			||||||
 | 
							Message:     err.Error(),
 | 
				
			||||||
 | 
							Context:     context,
 | 
				
			||||||
 | 
							Timestamp:   time.Now(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 根据错误类型分类
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case isNetworkError(err):
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeNetwork
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityMedium
 | 
				
			||||||
 | 
							errorInfo.Retryable = true
 | 
				
			||||||
 | 
							errorInfo.Action = ActionRetry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case isDatabaseError(err):
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeDatabase
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityHigh
 | 
				
			||||||
 | 
							errorInfo.Retryable = isRetryableDatabaseError(err)
 | 
				
			||||||
 | 
							errorInfo.Action = ActionRetry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case isTimeoutError(err):
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeTimeout
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityMedium
 | 
				
			||||||
 | 
							errorInfo.Retryable = true
 | 
				
			||||||
 | 
							errorInfo.Action = ActionRetry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case isRateLimitError(err):
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeRateLimit
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityMedium
 | 
				
			||||||
 | 
							errorInfo.Retryable = true
 | 
				
			||||||
 | 
							errorInfo.Action = ActionRetry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case isAuthError(err):
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeAuth
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityHigh
 | 
				
			||||||
 | 
							errorInfo.Retryable = false
 | 
				
			||||||
 | 
							errorInfo.Action = ActionAlert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case isValidationError(err):
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeValidation
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityLow
 | 
				
			||||||
 | 
							errorInfo.Retryable = false
 | 
				
			||||||
 | 
							errorInfo.Action = ActionIgnore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case isBusinessError(err):
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeBusiness
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityMedium
 | 
				
			||||||
 | 
							errorInfo.Retryable = false
 | 
				
			||||||
 | 
							errorInfo.Action = ActionFallback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							errorInfo.Type = ErrorTypeSystem
 | 
				
			||||||
 | 
							errorInfo.Severity = SeverityHigh
 | 
				
			||||||
 | 
							errorInfo.Retryable = true
 | 
				
			||||||
 | 
							errorInfo.Action = ActionRetry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errorInfo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HandleError 处理错误
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) HandleError(err error, context string) *ErrorInfo {
 | 
				
			||||||
 | 
						errorInfo := eh.ClassifyError(err, context)
 | 
				
			||||||
 | 
						if errorInfo == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 记录错误指标
 | 
				
			||||||
 | 
						if eh.metricsCollector != nil {
 | 
				
			||||||
 | 
							eh.metricsCollector.RecordError(string(errorInfo.Type), errorInfo.Message, context)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 根据错误动作处理
 | 
				
			||||||
 | 
						switch errorInfo.Action {
 | 
				
			||||||
 | 
						case ActionAlert:
 | 
				
			||||||
 | 
							eh.sendAlert(errorInfo)
 | 
				
			||||||
 | 
						case ActionCircuit:
 | 
				
			||||||
 | 
							eh.triggerCircuitBreaker(context)
 | 
				
			||||||
 | 
						case ActionDegrade:
 | 
				
			||||||
 | 
							eh.enableDegradedMode(context)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 记录日志
 | 
				
			||||||
 | 
						eh.logError(errorInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errorInfo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RetryWithBackoff 带退避的重试
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) RetryWithBackoff(ctx context.Context, operation func() error, config RetryConfig, context string) error {
 | 
				
			||||||
 | 
						var lastErr error
 | 
				
			||||||
 | 
						delay := config.RetryDelay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for attempt := 1; attempt <= config.MaxRetries; attempt++ {
 | 
				
			||||||
 | 
							// 检查上下文是否已取消
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return ctx.Err()
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 检查熔断器状态
 | 
				
			||||||
 | 
							if !eh.canExecute(context) {
 | 
				
			||||||
 | 
								return fmt.Errorf("circuit breaker is open for %s", context)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 执行操作
 | 
				
			||||||
 | 
							// 注意:config中没有TimeoutPerTry字段,这里暂时注释掉
 | 
				
			||||||
 | 
							// if config.TimeoutPerTry > 0 {
 | 
				
			||||||
 | 
							//	var cancel context.CancelFunc
 | 
				
			||||||
 | 
							//	operationCtx, cancel = context.WithTimeout(ctx, config.TimeoutPerTry)
 | 
				
			||||||
 | 
							//	defer cancel()
 | 
				
			||||||
 | 
							// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := operation()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								// 成功,记录成功指标
 | 
				
			||||||
 | 
								if eh.metricsCollector != nil {
 | 
				
			||||||
 | 
									eh.metricsCollector.RecordRetry(true)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								eh.recordSuccess(context)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lastErr = err
 | 
				
			||||||
 | 
							errorInfo := eh.ClassifyError(err, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 记录失败
 | 
				
			||||||
 | 
							eh.recordFailure(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 如果不可重试,直接返回
 | 
				
			||||||
 | 
							if errorInfo != nil && !errorInfo.Retryable {
 | 
				
			||||||
 | 
								logger.Warnf("不可重试的错误 [%s]: %v", context, err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 最后一次尝试,不再等待
 | 
				
			||||||
 | 
							if attempt == config.MaxRetries {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 计算下次重试延迟
 | 
				
			||||||
 | 
							nextDelay := eh.calculateDelay(delay, config)
 | 
				
			||||||
 | 
							logger.Infof("重试 %d/%d [%s] 在 %v 后,错误: %v", attempt, config.MaxRetries, context, nextDelay, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 等待重试
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return ctx.Err()
 | 
				
			||||||
 | 
							case <-time.After(nextDelay):
 | 
				
			||||||
 | 
								delay = nextDelay
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 记录重试失败指标
 | 
				
			||||||
 | 
						if eh.metricsCollector != nil {
 | 
				
			||||||
 | 
							eh.metricsCollector.RecordRetry(false)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Errorf("重试 %d 次后仍然失败 [%s]: %w", config.MaxRetries, context, lastErr)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// calculateDelay 计算重试延迟
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) calculateDelay(currentDelay time.Duration, config RetryConfig) time.Duration {
 | 
				
			||||||
 | 
						nextDelay := time.Duration(float64(currentDelay) * config.BackoffFactor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 限制最大延迟
 | 
				
			||||||
 | 
						if nextDelay > config.MaxRetryDelay {
 | 
				
			||||||
 | 
							nextDelay = config.MaxRetryDelay
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 添加10%抖动(简化版本,不依赖JitterEnabled字段)
 | 
				
			||||||
 | 
						jitter := time.Duration(float64(nextDelay) * 0.1)
 | 
				
			||||||
 | 
						if jitter > 0 {
 | 
				
			||||||
 | 
							nextDelay += time.Duration(time.Now().UnixNano() % int64(jitter))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nextDelay
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// canExecute 检查是否可以执行操作(熔断器检查)
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) canExecute(context string) bool {
 | 
				
			||||||
 | 
						cb := eh.getCircuitBreaker(context)
 | 
				
			||||||
 | 
						if cb == nil || !cb.config.Enabled {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch cb.state {
 | 
				
			||||||
 | 
						case StateClosed:
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case StateOpen:
 | 
				
			||||||
 | 
							if now.After(cb.nextRetry) {
 | 
				
			||||||
 | 
								cb.state = StateHalfOpen
 | 
				
			||||||
 | 
								cb.halfOpenCalls = 0
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case StateHalfOpen:
 | 
				
			||||||
 | 
							return cb.halfOpenCalls < cb.config.HalfOpenMaxCalls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// recordSuccess 记录成功
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) recordSuccess(context string) {
 | 
				
			||||||
 | 
						cb := eh.getCircuitBreaker(context)
 | 
				
			||||||
 | 
						if cb == nil || !cb.config.Enabled {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch cb.state {
 | 
				
			||||||
 | 
						case StateHalfOpen:
 | 
				
			||||||
 | 
							cb.successCount++
 | 
				
			||||||
 | 
							if cb.successCount >= cb.config.SuccessThreshold {
 | 
				
			||||||
 | 
								cb.state = StateClosed
 | 
				
			||||||
 | 
								cb.failureCount = 0
 | 
				
			||||||
 | 
								cb.successCount = 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case StateClosed:
 | 
				
			||||||
 | 
							cb.failureCount = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// recordFailure 记录失败
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) recordFailure(context string) {
 | 
				
			||||||
 | 
						cb := eh.getCircuitBreaker(context)
 | 
				
			||||||
 | 
						if cb == nil || !cb.config.Enabled {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cb.failureCount++
 | 
				
			||||||
 | 
						cb.lastFailTime = time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch cb.state {
 | 
				
			||||||
 | 
						case StateClosed:
 | 
				
			||||||
 | 
							if cb.failureCount >= cb.config.FailureThreshold {
 | 
				
			||||||
 | 
								cb.state = StateOpen
 | 
				
			||||||
 | 
								cb.nextRetry = time.Now().Add(cb.config.Timeout)
 | 
				
			||||||
 | 
								logger.Warnf("熔断器开启 [%s]: 失败次数 %d", context, cb.failureCount)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case StateHalfOpen:
 | 
				
			||||||
 | 
							cb.state = StateOpen
 | 
				
			||||||
 | 
							cb.nextRetry = time.Now().Add(cb.config.Timeout)
 | 
				
			||||||
 | 
							cb.successCount = 0
 | 
				
			||||||
 | 
							logger.Warnf("熔断器重新开启 [%s]", context)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getCircuitBreaker 获取熔断器
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) getCircuitBreaker(context string) *CircuitBreaker {
 | 
				
			||||||
 | 
						cb, exists := eh.circuitBreakers[context]
 | 
				
			||||||
 | 
						if !exists {
 | 
				
			||||||
 | 
							cb = &CircuitBreaker{
 | 
				
			||||||
 | 
								name:   context,
 | 
				
			||||||
 | 
								config: eh.config.CircuitBreakerConfig,
 | 
				
			||||||
 | 
								state:  StateClosed,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							eh.circuitBreakers[context] = cb
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cb
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// triggerCircuitBreaker 触发熔断器
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) triggerCircuitBreaker(context string) {
 | 
				
			||||||
 | 
						cb := eh.getCircuitBreaker(context)
 | 
				
			||||||
 | 
						if cb != nil && cb.config.Enabled {
 | 
				
			||||||
 | 
							cb.state = StateOpen
 | 
				
			||||||
 | 
							cb.nextRetry = time.Now().Add(cb.config.Timeout)
 | 
				
			||||||
 | 
							logger.Warnf("手动触发熔断器 [%s]", context)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// sendAlert 发送告警
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) sendAlert(errorInfo *ErrorInfo) {
 | 
				
			||||||
 | 
						// 这里可以集成告警系统,如钉钉、邮件等
 | 
				
			||||||
 | 
						logger.Errorf("告警: [%s] %s - %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// enableDegradedMode 启用降级模式
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) enableDegradedMode(context string) {
 | 
				
			||||||
 | 
						logger.Warnf("启用降级模式 [%s]", context)
 | 
				
			||||||
 | 
						// 这里可以实现具体的降级逻辑
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// logError 记录错误日志
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) logError(errorInfo *ErrorInfo) {
 | 
				
			||||||
 | 
						switch errorInfo.Severity {
 | 
				
			||||||
 | 
						case SeverityLow:
 | 
				
			||||||
 | 
							logger.Infof("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
 | 
				
			||||||
 | 
						case SeverityMedium:
 | 
				
			||||||
 | 
							logger.Warnf("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
 | 
				
			||||||
 | 
						case SeverityHigh, SeverityCritical:
 | 
				
			||||||
 | 
							logger.Errorf("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCircuitBreakerStatus 获取熔断器状态
 | 
				
			||||||
 | 
					func (eh *ErrorHandler) GetCircuitBreakerStatus() map[string]interface{} {
 | 
				
			||||||
 | 
						status := make(map[string]interface{})
 | 
				
			||||||
 | 
						for name, cb := range eh.circuitBreakers {
 | 
				
			||||||
 | 
							status[name] = map[string]interface{}{
 | 
				
			||||||
 | 
								"state":         cb.state,
 | 
				
			||||||
 | 
								"failure_count": cb.failureCount,
 | 
				
			||||||
 | 
								"success_count": cb.successCount,
 | 
				
			||||||
 | 
								"last_fail_time": cb.lastFailTime,
 | 
				
			||||||
 | 
								"next_retry":     cb.nextRetry,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return status
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 错误分类辅助函数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isNetworkError 判断是否为网络错误
 | 
				
			||||||
 | 
					func isNetworkError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 检查网络相关错误
 | 
				
			||||||
 | 
						var netErr net.Error
 | 
				
			||||||
 | 
						if errors.As(err, &netErr) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 检查常见网络错误消息
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						networkKeywords := []string{
 | 
				
			||||||
 | 
							"connection refused",
 | 
				
			||||||
 | 
							"connection reset",
 | 
				
			||||||
 | 
							"connection timeout",
 | 
				
			||||||
 | 
							"network unreachable",
 | 
				
			||||||
 | 
							"no route to host",
 | 
				
			||||||
 | 
							"dns",
 | 
				
			||||||
 | 
							"socket",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, keyword := range networkKeywords {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, keyword) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isDatabaseError 判断是否为数据库错误
 | 
				
			||||||
 | 
					func isDatabaseError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 检查GORM错误
 | 
				
			||||||
 | 
						if errors.Is(err, gorm.ErrRecordNotFound) ||
 | 
				
			||||||
 | 
							errors.Is(err, gorm.ErrInvalidTransaction) ||
 | 
				
			||||||
 | 
							errors.Is(err, gorm.ErrNotImplemented) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 检查数据库相关错误消息
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						dbKeywords := []string{
 | 
				
			||||||
 | 
							"database",
 | 
				
			||||||
 | 
							"sql",
 | 
				
			||||||
 | 
							"mysql",
 | 
				
			||||||
 | 
							"postgres",
 | 
				
			||||||
 | 
							"connection pool",
 | 
				
			||||||
 | 
							"deadlock",
 | 
				
			||||||
 | 
							"constraint",
 | 
				
			||||||
 | 
							"duplicate key",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, keyword := range dbKeywords {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, keyword) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isRetryableDatabaseError 判断是否为可重试的数据库错误
 | 
				
			||||||
 | 
					func isRetryableDatabaseError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 不可重试的错误
 | 
				
			||||||
 | 
						if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						nonRetryableKeywords := []string{
 | 
				
			||||||
 | 
							"constraint",
 | 
				
			||||||
 | 
							"duplicate key",
 | 
				
			||||||
 | 
							"foreign key",
 | 
				
			||||||
 | 
							"syntax error",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, keyword := range nonRetryableKeywords {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, keyword) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isTimeoutError 判断是否为超时错误
 | 
				
			||||||
 | 
					func isTimeoutError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 检查超时错误
 | 
				
			||||||
 | 
						var netErr net.Error
 | 
				
			||||||
 | 
						if errors.As(err, &netErr) && netErr.Timeout() {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if errors.Is(err, context.DeadlineExceeded) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						return strings.Contains(errorMsg, "timeout") ||
 | 
				
			||||||
 | 
							strings.Contains(errorMsg, "deadline exceeded")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isRateLimitError 判断是否为限流错误
 | 
				
			||||||
 | 
					func isRateLimitError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						rateLimitKeywords := []string{
 | 
				
			||||||
 | 
							"rate limit",
 | 
				
			||||||
 | 
							"too many requests",
 | 
				
			||||||
 | 
							"429",
 | 
				
			||||||
 | 
							"quota exceeded",
 | 
				
			||||||
 | 
							"throttle",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, keyword := range rateLimitKeywords {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, keyword) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isAuthError 判断是否为认证错误
 | 
				
			||||||
 | 
					func isAuthError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						authKeywords := []string{
 | 
				
			||||||
 | 
							"unauthorized",
 | 
				
			||||||
 | 
							"authentication",
 | 
				
			||||||
 | 
							"invalid signature",
 | 
				
			||||||
 | 
							"api key",
 | 
				
			||||||
 | 
							"401",
 | 
				
			||||||
 | 
							"403",
 | 
				
			||||||
 | 
							"forbidden",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, keyword := range authKeywords {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, keyword) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isValidationError 判断是否为验证错误
 | 
				
			||||||
 | 
					func isValidationError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						validationKeywords := []string{
 | 
				
			||||||
 | 
							"validation",
 | 
				
			||||||
 | 
							"invalid parameter",
 | 
				
			||||||
 | 
							"bad request",
 | 
				
			||||||
 | 
							"400",
 | 
				
			||||||
 | 
							"missing required",
 | 
				
			||||||
 | 
							"invalid format",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, keyword := range validationKeywords {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, keyword) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isBusinessError 判断是否为业务错误
 | 
				
			||||||
 | 
					func isBusinessError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorMsg := strings.ToLower(err.Error())
 | 
				
			||||||
 | 
						businessKeywords := []string{
 | 
				
			||||||
 | 
							"insufficient balance",
 | 
				
			||||||
 | 
							"order not found",
 | 
				
			||||||
 | 
							"position not found",
 | 
				
			||||||
 | 
							"invalid order status",
 | 
				
			||||||
 | 
							"market closed",
 | 
				
			||||||
 | 
							"symbol not found",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, keyword := range businessKeywords {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, keyword) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 全局错误处理器实例
 | 
				
			||||||
 | 
					var GlobalErrorHandler *ErrorHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InitErrorHandler 初始化错误处理器
 | 
				
			||||||
 | 
					func InitErrorHandler(config *OptimizedConfig, metricsCollector *MetricsCollector) {
 | 
				
			||||||
 | 
						GlobalErrorHandler = NewErrorHandler(config, metricsCollector)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetErrorHandler 获取全局错误处理器
 | 
				
			||||||
 | 
					func GetErrorHandler() *ErrorHandler {
 | 
				
			||||||
 | 
						return GlobalErrorHandler
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HandleErrorWithRetry 处理错误并重试的便捷函数
 | 
				
			||||||
 | 
					func HandleErrorWithRetry(ctx context.Context, operation func() error, context string) error {
 | 
				
			||||||
 | 
						if GlobalErrorHandler == nil {
 | 
				
			||||||
 | 
							return operation()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := RetryConfig{
 | 
				
			||||||
 | 
							MaxRetries:     3,
 | 
				
			||||||
 | 
							RetryDelay:     100 * time.Millisecond,
 | 
				
			||||||
 | 
							MaxRetryDelay:  5 * time.Second,
 | 
				
			||||||
 | 
							BackoffFactor:  2.0,
 | 
				
			||||||
 | 
							ApiRetryCount:  3,
 | 
				
			||||||
 | 
							DbRetryCount:   3,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return GlobalErrorHandler.RetryWithBackoff(ctx, operation, config, context)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										125
									
								
								services/binanceservice/futures_reset_v2.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								services/binanceservice/futures_reset_v2.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DbModels "go-admin/app/admin/models"
 | 
				
			||||||
 | 
						"go-admin/pkg/retryhelper"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/bytedance/sonic"
 | 
				
			||||||
 | 
						log "github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk/service"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FuturesResetV2 struct {
 | 
				
			||||||
 | 
						service.Service
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 带重试机制的合约下单
 | 
				
			||||||
 | 
					func (e *FuturesResetV2) OrderPlaceLoop(apiUserInfo *DbModels.LineApiUser, params FutOrderPlace) error {
 | 
				
			||||||
 | 
						opts := retryhelper.DefaultRetryOptions()
 | 
				
			||||||
 | 
						opts.RetryableErrFn = func(err error) bool {
 | 
				
			||||||
 | 
							if strings.Contains(err.Error(), "LOT_SIZE") {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							//重试
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := retryhelper.Retry(func() error {
 | 
				
			||||||
 | 
							return e.OrderPlace(apiUserInfo, params)
 | 
				
			||||||
 | 
						}, opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 合约下单
 | 
				
			||||||
 | 
					func (e *FuturesResetV2) OrderPlace(apiUserInfo *DbModels.LineApiUser, params FutOrderPlace) error {
 | 
				
			||||||
 | 
						if err := params.CheckParams(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						orderType := strings.ToUpper(params.OrderType)
 | 
				
			||||||
 | 
						side := strings.ToUpper(params.Side)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						paramsMaps := map[string]string{
 | 
				
			||||||
 | 
							"symbol":           params.Symbol,
 | 
				
			||||||
 | 
							"side":             side,
 | 
				
			||||||
 | 
							"type":             orderType,
 | 
				
			||||||
 | 
							"newClientOrderId": params.NewClientOrderId,
 | 
				
			||||||
 | 
							"positionSide":     params.PositionSide,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 设置市价和限价等类型的参数
 | 
				
			||||||
 | 
						switch orderType {
 | 
				
			||||||
 | 
						case "LIMIT":
 | 
				
			||||||
 | 
							paramsMaps["price"] = params.Price.String()
 | 
				
			||||||
 | 
							paramsMaps["timeInForce"] = "GTC"
 | 
				
			||||||
 | 
						case "TAKE_PROFIT_MARKET":
 | 
				
			||||||
 | 
							paramsMaps["timeInForce"] = "GTC"
 | 
				
			||||||
 | 
							paramsMaps["stopprice"] = params.Profit.String()
 | 
				
			||||||
 | 
							paramsMaps["workingType"] = "MARK_PRICE"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if params.ClosePosition {
 | 
				
			||||||
 | 
								paramsMaps["closePosition"] = "true"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case "TAKE_PROFIT":
 | 
				
			||||||
 | 
							paramsMaps["price"] = params.Price.String()
 | 
				
			||||||
 | 
							paramsMaps["stopprice"] = params.Profit.String()
 | 
				
			||||||
 | 
							paramsMaps["timeInForce"] = "GTC"
 | 
				
			||||||
 | 
						case "STOP_MARKET":
 | 
				
			||||||
 | 
							paramsMaps["stopprice"] = params.StopPrice.String()
 | 
				
			||||||
 | 
							paramsMaps["workingType"] = "MARK_PRICE"
 | 
				
			||||||
 | 
							paramsMaps["timeInForce"] = "GTC"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if params.ClosePosition {
 | 
				
			||||||
 | 
								paramsMaps["closePosition"] = "true"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case "STOP":
 | 
				
			||||||
 | 
							paramsMaps["price"] = params.Price.String()
 | 
				
			||||||
 | 
							paramsMaps["stopprice"] = params.StopPrice.String()
 | 
				
			||||||
 | 
							paramsMaps["workingType"] = "MARK_PRICE"
 | 
				
			||||||
 | 
							paramsMaps["timeInForce"] = "GTC"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//不是平仓
 | 
				
			||||||
 | 
						if !params.ClosePosition {
 | 
				
			||||||
 | 
							paramsMaps["quantity"] = params.Quantity.String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 获取 API 信息和发送下单请求
 | 
				
			||||||
 | 
						client := GetClient(apiUserInfo)
 | 
				
			||||||
 | 
						_, statusCode, err := client.SendFuturesRequestAuth("/fapi/v1/order", "POST", paramsMaps)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return parseOrderError(err, paramsMaps, statusCode, apiUserInfo)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 拆出的错误解析函数
 | 
				
			||||||
 | 
					func parseOrderError(err error, paramsMaps map[string]string, statusCode int, apiUserInfo *DbModels.LineApiUser) error {
 | 
				
			||||||
 | 
						var dataMap map[string]interface{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 == nil {
 | 
				
			||||||
 | 
							if code, ok := dataMap["code"]; ok {
 | 
				
			||||||
 | 
								paramsVal, _ := sonic.MarshalString(¶msMaps)
 | 
				
			||||||
 | 
								log.Error("下单失败 参数:", paramsVal)
 | 
				
			||||||
 | 
								errContent := FutErrorMaps[code.(float64)]
 | 
				
			||||||
 | 
								if errContent == "" {
 | 
				
			||||||
 | 
									errContent, _ = dataMap["msg"].(string)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, paramsMaps["symbol"], errContent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 特殊处理保证金不足
 | 
				
			||||||
 | 
						if strings.Contains(err.Error(), "Margin is insufficient.") {
 | 
				
			||||||
 | 
							return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, paramsMaps["symbol"], FutErrorMaps[-2019])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Errorf("api_id:%d 交易对:%s statusCode:%v 下单失败:%s", apiUserInfo.Id, paramsMaps["symbol"], statusCode, err.Error())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"go-admin/models/binancedto"
 | 
						"go-admin/models/binancedto"
 | 
				
			||||||
	"go-admin/models/futuresdto"
 | 
						"go-admin/models/futuresdto"
 | 
				
			||||||
	"go-admin/pkg/httputils"
 | 
						"go-admin/pkg/httputils"
 | 
				
			||||||
 | 
						"go-admin/pkg/retryhelper"
 | 
				
			||||||
	"go-admin/pkg/utility"
 | 
						"go-admin/pkg/utility"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@ -598,6 +599,49 @@ func (e FutRestApi) GetHoldeData(apiInfo *DbModels.LineApiUser, symbol, side str
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取合约 持仓价格、数量
 | 
				
			||||||
 | 
					// symbol:交易对
 | 
				
			||||||
 | 
					// positionSide:持仓方向
 | 
				
			||||||
 | 
					// holdeData:持仓数据
 | 
				
			||||||
 | 
					func (e FutRestApi) GetPositionData(apiInfo *DbModels.LineApiUser, symbol, positionSide string, holdeData *HoldeData) error {
 | 
				
			||||||
 | 
						opts := retryhelper.DefaultRetryOptions()
 | 
				
			||||||
 | 
						opts.RetryableErrFn = func(err error) bool {
 | 
				
			||||||
 | 
							if strings.Contains(err.Error(), "LOT_SIZE") {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							//重试
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						holdes, err := retryhelper.RetryWithResult(func() ([]PositionRisk, error) {
 | 
				
			||||||
 | 
							return e.GetPositionV3(apiInfo, symbol)
 | 
				
			||||||
 | 
						}, opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range holdes {
 | 
				
			||||||
 | 
							positionAmount, _ := decimal.NewFromString(item.PositionAmt)
 | 
				
			||||||
 | 
							if (positionSide == "LONG" && item.PositionSide == "BOTH" && positionAmount.Cmp(decimal.Zero) > 0) || item.PositionSide == positionSide { //多
 | 
				
			||||||
 | 
								holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice)
 | 
				
			||||||
 | 
								holdeData.TotalQuantity = positionAmount.Abs()
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							} else if (positionSide == "SHORT" && item.PositionSide == "BOTH" && positionAmount.Cmp(decimal.Zero) < 0) || item.PositionSide == positionSide { //空
 | 
				
			||||||
 | 
								holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice)
 | 
				
			||||||
 | 
								holdeData.TotalQuantity = positionAmount.Abs()
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if holdeData.AveragePrice.Cmp(decimal.Zero) == 0 {
 | 
				
			||||||
 | 
							holdesVal, _ := sonic.MarshalString(&holdes)
 | 
				
			||||||
 | 
							log.Error("均价错误 symbol:", symbol, " 数据:", holdesVal)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获取代币持仓信息
 | 
					// 获取代币持仓信息
 | 
				
			||||||
func getSymbolHolde(e FutRestApi, apiInfo *DbModels.LineApiUser, symbol string, side string, holdeData *HoldeData) ([]PositionRisk, error) {
 | 
					func getSymbolHolde(e FutRestApi, apiInfo *DbModels.LineApiUser, symbol string, side string, holdeData *HoldeData) ([]PositionRisk, error) {
 | 
				
			||||||
	holdes, err := e.GetPositionV3(apiInfo, symbol)
 | 
						holdes, err := e.GetPositionV3(apiInfo, symbol)
 | 
				
			||||||
@ -715,6 +759,15 @@ func (e FutRestApi) ClosePosition(symbol string, orderSn string, quantity decima
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 带重试机制的取消订单
 | 
				
			||||||
 | 
					func (e FutRestApi) CancelFutOrderRetry(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderId string) error {
 | 
				
			||||||
 | 
						opts := retryhelper.DefaultRetryOptions()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return retryhelper.Retry(func() error {
 | 
				
			||||||
 | 
							return e.CancelFutOrder(apiUserInfo, symbol, newClientOrderId)
 | 
				
			||||||
 | 
						}, opts)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CancelFutOrder 通过单个订单号取消合约委托
 | 
					// CancelFutOrder 通过单个订单号取消合约委托
 | 
				
			||||||
// symbol 交易对
 | 
					// symbol 交易对
 | 
				
			||||||
// newClientOrderId 系统自定义订单号
 | 
					// newClientOrderId 系统自定义订单号
 | 
				
			||||||
@ -774,6 +827,27 @@ func (e FutRestApi) CancelAllFutOrder(apiUserInfo DbModels.LineApiUser, symbol s
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 带重试的批量撤销订单
 | 
				
			||||||
 | 
					func (e FutRestApi) CancelBatchFutOrderLoop(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderIdList []string) error {
 | 
				
			||||||
 | 
						opts := retryhelper.DefaultRetryOptions()
 | 
				
			||||||
 | 
						opts.RetryableErrFn = func(err error) bool {
 | 
				
			||||||
 | 
							if strings.Contains(err.Error(), "LOT_SIZE") {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							//重试
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := retryhelper.Retry(func() error {
 | 
				
			||||||
 | 
							return e.CancelBatchFutOrder(apiUserInfo, symbol, newClientOrderIdList)
 | 
				
			||||||
 | 
						}, opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CancelBatchFutOrder 批量撤销订单
 | 
					// CancelBatchFutOrder 批量撤销订单
 | 
				
			||||||
// symbol 交易对
 | 
					// symbol 交易对
 | 
				
			||||||
// newClientOrderIdList 系统自定义的订单号, 最多支持10个订单
 | 
					// newClientOrderIdList 系统自定义的订单号, 最多支持10个订单
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ import (
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
修改订单信息
 | 
					修改订单信息
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
func ChangeFutureOrder(mapData map[string]interface{}) {
 | 
					func ChangeFutureOrder(mapData map[string]interface{}, apiKey string) {
 | 
				
			||||||
	// 检查订单号是否存在
 | 
						// 检查订单号是否存在
 | 
				
			||||||
	orderSn, ok := mapData["c"]
 | 
						orderSn, ok := mapData["c"]
 | 
				
			||||||
	originOrderSn := mapData["C"] //取消操作 代表原始订单号
 | 
						originOrderSn := mapData["C"] //取消操作 代表原始订单号
 | 
				
			||||||
@ -61,34 +61,15 @@ func ChangeFutureOrder(mapData map[string]interface{}) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer lock.Release()
 | 
						defer lock.Release()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 查询订单
 | 
						//反单逻辑
 | 
				
			||||||
	preOrder, err := getPreOrder(db, orderSn)
 | 
						reverseService := ReverseService{}
 | 
				
			||||||
 | 
						reverseService.Orm = db
 | 
				
			||||||
 | 
						reverseService.Log = logger.NewHelper(logger.DefaultLogger)
 | 
				
			||||||
 | 
						_, err = reverseService.ReverseOrder(apiKey, mapData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logger.Error("合约订单回调失败,查询订单失败:", orderSn, " err:", err)
 | 
							logger.Errorf("合约订单回调失败,反单失败:%v", err)
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 解析订单状态
 | 
					 | 
				
			||||||
	status, ok := mapData["X"].(string)
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		mapStr, _ := sonic.Marshal(&mapData)
 | 
					 | 
				
			||||||
		logger.Error("订单回调失败,没有状态:", string(mapStr))
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// 更新订单状态
 | 
					 | 
				
			||||||
	orderStatus, reason := parseOrderStatus(status, mapData)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if orderStatus == 0 {
 | 
					 | 
				
			||||||
		logger.Error("订单回调失败,状态错误:", orderSn, " status:", status, " reason:", reason)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := updateOrderStatus(db, preOrder, orderStatus, reason, true, mapData); err != nil {
 | 
					 | 
				
			||||||
		logger.Error("修改订单状态失败:", orderSn, " err:", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	handleFutOrderByType(db, preOrder, orderStatus)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 合约回调
 | 
					// 合约回调
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										587
									
								
								services/binanceservice/integration_test_optimized.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										587
									
								
								services/binanceservice/integration_test_optimized.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,587 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
						"gorm.io/driver/mysql"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OptimizedSystemTestSuite 优化系统测试套件
 | 
				
			||||||
 | 
					type OptimizedSystemTestSuite struct {
 | 
				
			||||||
 | 
						suite.Suite
 | 
				
			||||||
 | 
						db              *gorm.DB
 | 
				
			||||||
 | 
						redisClient     *redis.Client
 | 
				
			||||||
 | 
						config          *OptimizedConfig
 | 
				
			||||||
 | 
						metricsCollector *MetricsCollector
 | 
				
			||||||
 | 
						errorHandler    *ErrorHandler
 | 
				
			||||||
 | 
						lockManager     *LockManager
 | 
				
			||||||
 | 
						txManager       *TransactionManager
 | 
				
			||||||
 | 
						reverseService  *ReverseServiceOptimized
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetupSuite 设置测试套件
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) SetupSuite() {
 | 
				
			||||||
 | 
						// 初始化数据库连接(测试环境)
 | 
				
			||||||
 | 
						dsn := "root:password@tcp(localhost:3306)/test_exchange?charset=utf8mb4&parseTime=True&loc=Local"
 | 
				
			||||||
 | 
						db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							suite.T().Skip("跳过集成测试:无法连接数据库")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						suite.db = db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化Redis连接(测试环境)
 | 
				
			||||||
 | 
						suite.redisClient = redis.NewClient(&redis.Options{
 | 
				
			||||||
 | 
							Addr:     "localhost:6379",
 | 
				
			||||||
 | 
							Password: "",
 | 
				
			||||||
 | 
							DB:       1, // 使用测试数据库
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试Redis连接
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						if err := suite.redisClient.Ping(ctx).Err(); err != nil {
 | 
				
			||||||
 | 
							suite.T().Skip("跳过集成测试:无法连接Redis")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化配置
 | 
				
			||||||
 | 
						suite.config = GetDefaultOptimizedConfig()
 | 
				
			||||||
 | 
						suite.config.MonitorConfig.Enabled = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化组件
 | 
				
			||||||
 | 
						suite.metricsCollector = NewMetricsCollector(suite.config)
 | 
				
			||||||
 | 
						suite.errorHandler = NewErrorHandler(suite.config, suite.metricsCollector)
 | 
				
			||||||
 | 
						suite.lockManager = NewLockManager(suite.config, suite.redisClient, suite.metricsCollector)
 | 
				
			||||||
 | 
						suite.txManager = NewTransactionManager(suite.db, suite.metricsCollector, suite.config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 启动指标收集
 | 
				
			||||||
 | 
						suite.metricsCollector.Start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 创建测试表
 | 
				
			||||||
 | 
						suite.createTestTables()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TearDownSuite 清理测试套件
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TearDownSuite() {
 | 
				
			||||||
 | 
						if suite.metricsCollector != nil {
 | 
				
			||||||
 | 
							suite.metricsCollector.Stop()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if suite.redisClient != nil {
 | 
				
			||||||
 | 
							suite.redisClient.Close()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetupTest 设置每个测试
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) SetupTest() {
 | 
				
			||||||
 | 
						// 清理Redis测试数据
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						suite.redisClient.FlushDB(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 清理数据库测试数据
 | 
				
			||||||
 | 
						suite.db.Exec("DELETE FROM test_orders")
 | 
				
			||||||
 | 
						suite.db.Exec("DELETE FROM test_positions")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// createTestTables 创建测试表
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) createTestTables() {
 | 
				
			||||||
 | 
						// 创建测试订单表
 | 
				
			||||||
 | 
						suite.db.Exec(`
 | 
				
			||||||
 | 
							CREATE TABLE IF NOT EXISTS test_orders (
 | 
				
			||||||
 | 
								id BIGINT PRIMARY KEY AUTO_INCREMENT,
 | 
				
			||||||
 | 
								order_id VARCHAR(100) UNIQUE NOT NULL,
 | 
				
			||||||
 | 
								user_id VARCHAR(50) NOT NULL,
 | 
				
			||||||
 | 
								symbol VARCHAR(20) NOT NULL,
 | 
				
			||||||
 | 
								side VARCHAR(10) NOT NULL,
 | 
				
			||||||
 | 
								quantity DECIMAL(20,8) NOT NULL,
 | 
				
			||||||
 | 
								price DECIMAL(20,8),
 | 
				
			||||||
 | 
								status VARCHAR(20) NOT NULL,
 | 
				
			||||||
 | 
								version INT DEFAULT 0,
 | 
				
			||||||
 | 
								created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
								updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
								INDEX idx_user_symbol (user_id, symbol),
 | 
				
			||||||
 | 
								INDEX idx_status (status),
 | 
				
			||||||
 | 
								INDEX idx_version (version)
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 创建测试持仓表
 | 
				
			||||||
 | 
						suite.db.Exec(`
 | 
				
			||||||
 | 
							CREATE TABLE IF NOT EXISTS test_positions (
 | 
				
			||||||
 | 
								id BIGINT PRIMARY KEY AUTO_INCREMENT,
 | 
				
			||||||
 | 
								user_id VARCHAR(50) NOT NULL,
 | 
				
			||||||
 | 
								symbol VARCHAR(20) NOT NULL,
 | 
				
			||||||
 | 
								side VARCHAR(10) NOT NULL,
 | 
				
			||||||
 | 
								quantity DECIMAL(20,8) NOT NULL,
 | 
				
			||||||
 | 
								avg_price DECIMAL(20,8),
 | 
				
			||||||
 | 
								status VARCHAR(20) NOT NULL,
 | 
				
			||||||
 | 
								version INT DEFAULT 0,
 | 
				
			||||||
 | 
								created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
								updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
								UNIQUE KEY uk_user_symbol_side (user_id, symbol, side),
 | 
				
			||||||
 | 
								INDEX idx_version (version)
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						`)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestLockManager 测试锁管理器
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TestLockManager() {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试分布式锁
 | 
				
			||||||
 | 
						suite.Run("DistributedLock", func() {
 | 
				
			||||||
 | 
							config := LockManagerConfig{
 | 
				
			||||||
 | 
								Type:       LockTypeDistributed,
 | 
				
			||||||
 | 
								Scope:      ScopeOrder,
 | 
				
			||||||
 | 
								Key:        "test_order_123",
 | 
				
			||||||
 | 
								Expiration: 10 * time.Second,
 | 
				
			||||||
 | 
								Timeout:    5 * time.Second,
 | 
				
			||||||
 | 
								RetryDelay: 100 * time.Millisecond,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 获取锁
 | 
				
			||||||
 | 
							lock, err := suite.lockManager.AcquireLock(ctx, config)
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
							assert.NotNil(suite.T(), lock)
 | 
				
			||||||
 | 
							assert.True(suite.T(), lock.IsLocked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 尝试获取同一个锁(应该失败)
 | 
				
			||||||
 | 
							config.Timeout = 1 * time.Second
 | 
				
			||||||
 | 
							_, err = suite.lockManager.AcquireLock(ctx, config)
 | 
				
			||||||
 | 
							assert.Error(suite.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 释放锁
 | 
				
			||||||
 | 
							err = suite.lockManager.ReleaseLock(ctx, lock)
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
							assert.False(suite.T(), lock.IsLocked())
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试本地锁
 | 
				
			||||||
 | 
						suite.Run("LocalLock", func() {
 | 
				
			||||||
 | 
							config := LockManagerConfig{
 | 
				
			||||||
 | 
								Type:       LockTypeLocal,
 | 
				
			||||||
 | 
								Scope:      ScopeUser,
 | 
				
			||||||
 | 
								Key:        "test_user_456",
 | 
				
			||||||
 | 
								Expiration: 10 * time.Second,
 | 
				
			||||||
 | 
								Timeout:    5 * time.Second,
 | 
				
			||||||
 | 
								RetryDelay: 100 * time.Millisecond,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lock, err := suite.lockManager.AcquireLock(ctx, config)
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
							assert.NotNil(suite.T(), lock)
 | 
				
			||||||
 | 
							assert.True(suite.T(), lock.IsLocked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = suite.lockManager.ReleaseLock(ctx, lock)
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试并发锁
 | 
				
			||||||
 | 
						suite.Run("ConcurrentLocks", func() {
 | 
				
			||||||
 | 
							var wg sync.WaitGroup
 | 
				
			||||||
 | 
							successCount := 0
 | 
				
			||||||
 | 
							var mu sync.Mutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i := 0; i < 10; i++ {
 | 
				
			||||||
 | 
								wg.Add(1)
 | 
				
			||||||
 | 
								go func(id int) {
 | 
				
			||||||
 | 
									defer wg.Done()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									config := LockManagerConfig{
 | 
				
			||||||
 | 
										Type:       LockTypeDistributed,
 | 
				
			||||||
 | 
										Scope:      ScopeGlobal,
 | 
				
			||||||
 | 
										Key:        "concurrent_test",
 | 
				
			||||||
 | 
										Expiration: 2 * time.Second,
 | 
				
			||||||
 | 
										Timeout:    1 * time.Second,
 | 
				
			||||||
 | 
										RetryDelay: 50 * time.Millisecond,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									lock, err := suite.lockManager.AcquireLock(ctx, config)
 | 
				
			||||||
 | 
									if err == nil {
 | 
				
			||||||
 | 
										mu.Lock()
 | 
				
			||||||
 | 
										successCount++
 | 
				
			||||||
 | 
										mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										time.Sleep(100 * time.Millisecond)
 | 
				
			||||||
 | 
										suite.lockManager.ReleaseLock(ctx, lock)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}(i)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wg.Wait()
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), 1, successCount, "只有一个goroutine应该获得锁")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestTransactionManager 测试事务管理器
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TestTransactionManager() {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试基本事务
 | 
				
			||||||
 | 
						suite.Run("BasicTransaction", func() {
 | 
				
			||||||
 | 
							config := GetDefaultTransactionConfig()
 | 
				
			||||||
 | 
							result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
								// 插入测试数据
 | 
				
			||||||
 | 
								return txCtx.Tx.Exec(`
 | 
				
			||||||
 | 
									INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) 
 | 
				
			||||||
 | 
									VALUES (?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
								`, "order_001", "user_001", "BTCUSDT", "BUY", 1.0, "NEW").Error
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.True(suite.T(), result.Success)
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), result.Error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 验证数据已插入
 | 
				
			||||||
 | 
							var count int64
 | 
				
			||||||
 | 
							suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id = ?", "order_001").Scan(&count)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), count)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试事务回滚
 | 
				
			||||||
 | 
						suite.Run("TransactionRollback", func() {
 | 
				
			||||||
 | 
							config := GetDefaultTransactionConfig()
 | 
				
			||||||
 | 
							result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
								// 插入数据
 | 
				
			||||||
 | 
								if err := txCtx.Tx.Exec(`
 | 
				
			||||||
 | 
									INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) 
 | 
				
			||||||
 | 
									VALUES (?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
								`, "order_002", "user_002", "ETHUSDT", "SELL", 2.0, "NEW").Error; err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// 故意返回错误以触发回滚
 | 
				
			||||||
 | 
								return fmt.Errorf("测试回滚")
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.False(suite.T(), result.Success)
 | 
				
			||||||
 | 
							assert.Error(suite.T(), result.Error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 验证数据未插入
 | 
				
			||||||
 | 
							var count int64
 | 
				
			||||||
 | 
							suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id = ?", "order_002").Scan(&count)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(0), count)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试乐观锁
 | 
				
			||||||
 | 
						suite.Run("OptimisticLock", func() {
 | 
				
			||||||
 | 
							// 先插入测试数据
 | 
				
			||||||
 | 
							suite.db.Exec(`
 | 
				
			||||||
 | 
								INSERT INTO test_positions (user_id, symbol, side, quantity, avg_price, status, version) 
 | 
				
			||||||
 | 
								VALUES (?, ?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
							`, "user_003", "BTCUSDT", "LONG", 1.0, 50000.0, "OPEN", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 测试正常更新
 | 
				
			||||||
 | 
							updates := map[string]interface{}{
 | 
				
			||||||
 | 
								"quantity": 2.0,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := suite.txManager.UpdateWithOptimisticLock(ctx, nil, updates, 
 | 
				
			||||||
 | 
								"user_id = ? AND symbol = ? AND side = ? AND version = ?", 
 | 
				
			||||||
 | 
								"user_003", "BTCUSDT", "LONG", 0)
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 测试版本冲突
 | 
				
			||||||
 | 
							updates["quantity"] = 3.0
 | 
				
			||||||
 | 
							err = suite.txManager.UpdateWithOptimisticLock(ctx, nil, updates, 
 | 
				
			||||||
 | 
								"user_id = ? AND symbol = ? AND side = ? AND version = ?", 
 | 
				
			||||||
 | 
								"user_003", "BTCUSDT", "LONG", 0) // 使用旧版本号
 | 
				
			||||||
 | 
							assert.Error(suite.T(), err)
 | 
				
			||||||
 | 
							assert.Contains(suite.T(), err.Error(), "乐观锁冲突")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestErrorHandler 测试错误处理器
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TestErrorHandler() {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试错误分类
 | 
				
			||||||
 | 
						suite.Run("ErrorClassification", func() {
 | 
				
			||||||
 | 
							testCases := []struct {
 | 
				
			||||||
 | 
								error    error
 | 
				
			||||||
 | 
								expected ErrorType
 | 
				
			||||||
 | 
							}{
 | 
				
			||||||
 | 
								{fmt.Errorf("connection refused"), ErrorTypeNetwork},
 | 
				
			||||||
 | 
								{fmt.Errorf("database connection failed"), ErrorTypeDatabase},
 | 
				
			||||||
 | 
								{fmt.Errorf("context deadline exceeded"), ErrorTypeTimeout},
 | 
				
			||||||
 | 
								{fmt.Errorf("too many requests"), ErrorTypeRateLimit},
 | 
				
			||||||
 | 
								{fmt.Errorf("unauthorized access"), ErrorTypeAuth},
 | 
				
			||||||
 | 
								{fmt.Errorf("invalid parameter"), ErrorTypeValidation},
 | 
				
			||||||
 | 
								{fmt.Errorf("insufficient balance"), ErrorTypeBusiness},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, tc := range testCases {
 | 
				
			||||||
 | 
								errorInfo := suite.errorHandler.ClassifyError(tc.error, "test")
 | 
				
			||||||
 | 
								assert.Equal(suite.T(), tc.expected, errorInfo.Type)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试重试机制
 | 
				
			||||||
 | 
						suite.Run("RetryMechanism", func() {
 | 
				
			||||||
 | 
							attempts := 0
 | 
				
			||||||
 | 
							config := RetryConfig{
 | 
				
			||||||
 | 
								MaxRetries:     3,
 | 
				
			||||||
 | 
								RetryDelay:     10 * time.Millisecond,
 | 
				
			||||||
 | 
								MaxRetryDelay:  100 * time.Millisecond,
 | 
				
			||||||
 | 
								BackoffFactor:  2.0,
 | 
				
			||||||
 | 
								ApiRetryCount:  3,
 | 
				
			||||||
 | 
								DbRetryCount:   3,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 测试重试成功
 | 
				
			||||||
 | 
							err := suite.errorHandler.RetryWithBackoff(ctx, func() error {
 | 
				
			||||||
 | 
								attempts++
 | 
				
			||||||
 | 
								if attempts < 3 {
 | 
				
			||||||
 | 
									return fmt.Errorf("temporary error")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}, config, "test_retry")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), 3, attempts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 测试重试失败
 | 
				
			||||||
 | 
							attempts = 0
 | 
				
			||||||
 | 
							err = suite.errorHandler.RetryWithBackoff(ctx, func() error {
 | 
				
			||||||
 | 
								attempts++
 | 
				
			||||||
 | 
								return fmt.Errorf("persistent error")
 | 
				
			||||||
 | 
							}, config, "test_retry_fail")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Error(suite.T(), err)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), 3, attempts)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestMetricsCollector 测试指标收集器
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TestMetricsCollector() {
 | 
				
			||||||
 | 
						// 测试订单指标
 | 
				
			||||||
 | 
						suite.Run("OrderMetrics", func() {
 | 
				
			||||||
 | 
							// 记录订单处理
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordOrderProcessed(true, 100*time.Millisecond)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordOrderProcessed(false, 200*time.Millisecond)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordOrderCanceled()
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordReverseOrder(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							metrics := suite.metricsCollector.GetMetrics()
 | 
				
			||||||
 | 
							orderMetrics := metrics["order_metrics"].(*OrderMetrics)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(2), orderMetrics.TotalProcessed)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), orderMetrics.SuccessfulOrders)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), orderMetrics.FailedOrders)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), orderMetrics.CanceledOrders)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), orderMetrics.ReverseOrdersCreated)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试持仓指标
 | 
				
			||||||
 | 
						suite.Run("PositionMetrics", func() {
 | 
				
			||||||
 | 
							difference := decimal.NewFromFloat(0.1)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordPositionSync(true, difference)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordPositionSync(false, decimal.Zero)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordClosePosition(false)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordClosePosition(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							metrics := suite.metricsCollector.GetMetrics()
 | 
				
			||||||
 | 
							positionMetrics := metrics["position_metrics"].(*PositionMetrics)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(2), positionMetrics.SyncAttempts)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), positionMetrics.SyncSuccesses)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), positionMetrics.SyncFailures)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(2), positionMetrics.ClosePositions)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), positionMetrics.ForceClosePositions)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 测试性能指标
 | 
				
			||||||
 | 
						suite.Run("PerformanceMetrics", func() {
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordLockOperation(true, 50*time.Millisecond)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordDbOperation(false, 30*time.Millisecond, nil)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordApiCall(100*time.Millisecond, nil, 1)
 | 
				
			||||||
 | 
							suite.metricsCollector.RecordConcurrency(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							metrics := suite.metricsCollector.GetMetrics()
 | 
				
			||||||
 | 
							perfMetrics := metrics["performance_metrics"].(*PerformanceMetrics)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), perfMetrics.LockAcquisitions)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), perfMetrics.DbQueries)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(1), perfMetrics.ApiCalls)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(5), perfMetrics.ConcurrentRequests)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestConcurrentOperations 测试并发操作
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TestConcurrentOperations() {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("ConcurrentOrderProcessing", func() {
 | 
				
			||||||
 | 
							var wg sync.WaitGroup
 | 
				
			||||||
 | 
							successCount := 0
 | 
				
			||||||
 | 
							var mu sync.Mutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 并发处理订单
 | 
				
			||||||
 | 
							for i := 0; i < 10; i++ {
 | 
				
			||||||
 | 
								wg.Add(1)
 | 
				
			||||||
 | 
								go func(id int) {
 | 
				
			||||||
 | 
									defer wg.Done()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// 使用锁保护订单处理
 | 
				
			||||||
 | 
									lockConfig := GetOrderLockConfig(fmt.Sprintf("order_%d", id))
 | 
				
			||||||
 | 
									err := suite.lockManager.WithLock(ctx, lockConfig, func() error {
 | 
				
			||||||
 | 
										// 模拟订单处理
 | 
				
			||||||
 | 
										config := GetDefaultTransactionConfig()
 | 
				
			||||||
 | 
										result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
											return txCtx.Tx.Exec(`
 | 
				
			||||||
 | 
												INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) 
 | 
				
			||||||
 | 
												VALUES (?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
											`, fmt.Sprintf("concurrent_order_%d", id), fmt.Sprintf("user_%d", id), 
 | 
				
			||||||
 | 
												"BTCUSDT", "BUY", 1.0, "NEW").Error
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if result.Success {
 | 
				
			||||||
 | 
											mu.Lock()
 | 
				
			||||||
 | 
											successCount++
 | 
				
			||||||
 | 
											mu.Unlock()
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										return result.Error
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										logger.Errorf("并发订单处理失败 [%d]: %v", id, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}(i)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wg.Wait()
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), 10, successCount, "所有并发订单都应该成功处理")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 验证数据库中的记录数
 | 
				
			||||||
 | 
							var count int64
 | 
				
			||||||
 | 
							suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id LIKE 'concurrent_order_%'").Scan(&count)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), int64(10), count)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestSystemIntegration 测试系统集成
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TestSystemIntegration() {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("CompleteOrderFlow", func() {
 | 
				
			||||||
 | 
							userID := "integration_user"
 | 
				
			||||||
 | 
							symbol := "BTCUSDT"
 | 
				
			||||||
 | 
							orderID := "integration_order_001"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 步骤1:创建订单(使用锁和事务)
 | 
				
			||||||
 | 
							err := WithOrderLock(ctx, orderID, func() error {
 | 
				
			||||||
 | 
								return WithDefaultTransaction(ctx, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
									return txCtx.Tx.Exec(`
 | 
				
			||||||
 | 
										INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, price, status) 
 | 
				
			||||||
 | 
										VALUES (?, ?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
									`, orderID, userID, symbol, "BUY", 1.0, 50000.0, "NEW").Error
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 步骤2:更新订单状态(使用乐观锁)
 | 
				
			||||||
 | 
							updates := map[string]interface{}{
 | 
				
			||||||
 | 
								"status": "FILLED",
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = UpdateWithOptimisticLockGlobal(ctx, nil, updates, 
 | 
				
			||||||
 | 
								"order_id = ? AND version = ?", orderID, 0)
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 步骤3:创建持仓(使用锁和事务)
 | 
				
			||||||
 | 
							err = WithPositionLock(ctx, userID, symbol, func() error {
 | 
				
			||||||
 | 
								return WithDefaultTransaction(ctx, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
									return txCtx.Tx.Exec(`
 | 
				
			||||||
 | 
										INSERT INTO test_positions (user_id, symbol, side, quantity, avg_price, status) 
 | 
				
			||||||
 | 
										VALUES (?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
										ON DUPLICATE KEY UPDATE 
 | 
				
			||||||
 | 
										quantity = quantity + VALUES(quantity),
 | 
				
			||||||
 | 
										avg_price = (avg_price * quantity + VALUES(avg_price) * VALUES(quantity)) / (quantity + VALUES(quantity)),
 | 
				
			||||||
 | 
										version = version + 1
 | 
				
			||||||
 | 
									`, userID, symbol, "LONG", 1.0, 50000.0, "OPEN").Error
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 验证最终状态
 | 
				
			||||||
 | 
							var orderStatus string
 | 
				
			||||||
 | 
							suite.db.Raw("SELECT status FROM test_orders WHERE order_id = ?", orderID).Scan(&orderStatus)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), "FILLED", orderStatus)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var positionQuantity float64
 | 
				
			||||||
 | 
							suite.db.Raw("SELECT quantity FROM test_positions WHERE user_id = ? AND symbol = ?", 
 | 
				
			||||||
 | 
								userID, symbol).Scan(&positionQuantity)
 | 
				
			||||||
 | 
							assert.Equal(suite.T(), 1.0, positionQuantity)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestPerformance 测试性能
 | 
				
			||||||
 | 
					func (suite *OptimizedSystemTestSuite) TestPerformance() {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("LockPerformance", func() {
 | 
				
			||||||
 | 
							startTime := time.Now()
 | 
				
			||||||
 | 
							iterations := 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i := 0; i < iterations; i++ {
 | 
				
			||||||
 | 
								lockConfig := GetOrderLockConfig(fmt.Sprintf("perf_order_%d", i))
 | 
				
			||||||
 | 
								lock, err := suite.lockManager.AcquireLock(ctx, lockConfig)
 | 
				
			||||||
 | 
								assert.NoError(suite.T(), err)
 | 
				
			||||||
 | 
								suite.lockManager.ReleaseLock(ctx, lock)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							duration := time.Since(startTime)
 | 
				
			||||||
 | 
							avgDuration := duration / time.Duration(iterations)
 | 
				
			||||||
 | 
							logger.Infof("锁性能测试: %d 次操作耗时 %v,平均每次 %v", iterations, duration, avgDuration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 平均每次操作应该在合理范围内
 | 
				
			||||||
 | 
							assert.Less(suite.T(), avgDuration, 10*time.Millisecond)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("TransactionPerformance", func() {
 | 
				
			||||||
 | 
							startTime := time.Now()
 | 
				
			||||||
 | 
							iterations := 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i := 0; i < iterations; i++ {
 | 
				
			||||||
 | 
								config := GetDefaultTransactionConfig()
 | 
				
			||||||
 | 
								result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
									return txCtx.Tx.Exec(`
 | 
				
			||||||
 | 
										INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) 
 | 
				
			||||||
 | 
										VALUES (?, ?, ?, ?, ?, ?)
 | 
				
			||||||
 | 
									`, fmt.Sprintf("perf_order_%d", i), "perf_user", "BTCUSDT", "BUY", 1.0, "NEW").Error
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								assert.True(suite.T(), result.Success)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							duration := time.Since(startTime)
 | 
				
			||||||
 | 
							avgDuration := duration / time.Duration(iterations)
 | 
				
			||||||
 | 
							logger.Infof("事务性能测试: %d 次操作耗时 %v,平均每次 %v", iterations, duration, avgDuration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 平均每次事务应该在合理范围内
 | 
				
			||||||
 | 
							assert.Less(suite.T(), avgDuration, 100*time.Millisecond)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestSuite 运行测试套件
 | 
				
			||||||
 | 
					func TestOptimizedSystemSuite(t *testing.T) {
 | 
				
			||||||
 | 
						suite.Run(t, new(OptimizedSystemTestSuite))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BenchmarkLockManager 锁管理器基准测试
 | 
				
			||||||
 | 
					func BenchmarkLockManager(b *testing.B) {
 | 
				
			||||||
 | 
						// 这里可以添加基准测试
 | 
				
			||||||
 | 
						b.Skip("基准测试需要Redis连接")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BenchmarkTransactionManager 事务管理器基准测试
 | 
				
			||||||
 | 
					func BenchmarkTransactionManager(b *testing.B) {
 | 
				
			||||||
 | 
						// 这里可以添加基准测试
 | 
				
			||||||
 | 
						b.Skip("基准测试需要数据库连接")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										575
									
								
								services/binanceservice/lock_manager_optimized.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										575
									
								
								services/binanceservice/lock_manager_optimized.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,575 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LockManagerConfig 锁管理器专用配置
 | 
				
			||||||
 | 
					type LockManagerConfig struct {
 | 
				
			||||||
 | 
						Type       LockType      `json:"type"`
 | 
				
			||||||
 | 
						Scope      LockScope     `json:"scope"`
 | 
				
			||||||
 | 
						Key        string        `json:"key"`
 | 
				
			||||||
 | 
						Expiration time.Duration `json:"expiration"`
 | 
				
			||||||
 | 
						Timeout    time.Duration `json:"timeout"`
 | 
				
			||||||
 | 
						RetryDelay time.Duration `json:"retry_delay"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LockType 锁类型
 | 
				
			||||||
 | 
					type LockType string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						LockTypeLocal       LockType = "local"
 | 
				
			||||||
 | 
						LockTypeDistributed LockType = "distributed"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LockScope 锁范围
 | 
				
			||||||
 | 
					type LockScope string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ScopeOrder    LockScope = "order"
 | 
				
			||||||
 | 
						ScopePosition LockScope = "position"
 | 
				
			||||||
 | 
						ScopeUser     LockScope = "user"
 | 
				
			||||||
 | 
						ScopeSymbol   LockScope = "symbol"
 | 
				
			||||||
 | 
						ScopeGlobal   LockScope = "global"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Lock 锁接口
 | 
				
			||||||
 | 
					type Lock interface {
 | 
				
			||||||
 | 
						Lock(ctx context.Context) error
 | 
				
			||||||
 | 
						Unlock(ctx context.Context) error
 | 
				
			||||||
 | 
						TryLock(ctx context.Context) (bool, error)
 | 
				
			||||||
 | 
						IsLocked() bool
 | 
				
			||||||
 | 
						GetKey() string
 | 
				
			||||||
 | 
						GetExpiration() time.Duration
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LocalLock 本地锁
 | 
				
			||||||
 | 
					type LocalLock struct {
 | 
				
			||||||
 | 
						key        string
 | 
				
			||||||
 | 
						mu         *sync.RWMutex
 | 
				
			||||||
 | 
						locked     bool
 | 
				
			||||||
 | 
						expiration time.Duration
 | 
				
			||||||
 | 
						acquiredAt time.Time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DistributedLock 分布式锁
 | 
				
			||||||
 | 
					type DistributedLock struct {
 | 
				
			||||||
 | 
						key        string
 | 
				
			||||||
 | 
						value      string
 | 
				
			||||||
 | 
						expiration time.Duration
 | 
				
			||||||
 | 
						redisClient *redis.Client
 | 
				
			||||||
 | 
						locked     bool
 | 
				
			||||||
 | 
						acquiredAt time.Time
 | 
				
			||||||
 | 
						refreshTicker *time.Ticker
 | 
				
			||||||
 | 
						stopRefresh   chan struct{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LockManager 锁管理器
 | 
				
			||||||
 | 
					type LockManager struct {
 | 
				
			||||||
 | 
						config       *OptimizedConfig
 | 
				
			||||||
 | 
						redisClient  *redis.Client
 | 
				
			||||||
 | 
						localLocks   map[string]*LocalLock
 | 
				
			||||||
 | 
						localMutex   sync.RWMutex
 | 
				
			||||||
 | 
						metrics      *MetricsCollector
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LockConfig 已在 config_optimized.go 中定义
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewLockManager 创建锁管理器
 | 
				
			||||||
 | 
					func NewLockManager(config *OptimizedConfig, redisClient *redis.Client, metrics *MetricsCollector) *LockManager {
 | 
				
			||||||
 | 
						return &LockManager{
 | 
				
			||||||
 | 
							config:      config,
 | 
				
			||||||
 | 
							redisClient: redisClient,
 | 
				
			||||||
 | 
							localLocks:  make(map[string]*LocalLock),
 | 
				
			||||||
 | 
							metrics:     metrics,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateLock 创建锁
 | 
				
			||||||
 | 
					func (lm *LockManager) CreateLock(config LockManagerConfig) Lock {
 | 
				
			||||||
 | 
						switch config.Type {
 | 
				
			||||||
 | 
						case LockTypeDistributed:
 | 
				
			||||||
 | 
							return lm.createDistributedLock(config)
 | 
				
			||||||
 | 
						case LockTypeLocal:
 | 
				
			||||||
 | 
							return lm.createLocalLock(config)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							// 默认使用分布式锁
 | 
				
			||||||
 | 
							return lm.createDistributedLock(config)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// createLocalLock 创建本地锁
 | 
				
			||||||
 | 
					func (lm *LockManager) createLocalLock(config LockManagerConfig) *LocalLock {
 | 
				
			||||||
 | 
						lm.localMutex.Lock()
 | 
				
			||||||
 | 
						defer lm.localMutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						key := lm.buildLockKey(config.Scope, config.Key)
 | 
				
			||||||
 | 
						if lock, exists := lm.localLocks[key]; exists {
 | 
				
			||||||
 | 
							return lock
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lock := &LocalLock{
 | 
				
			||||||
 | 
							key:        key,
 | 
				
			||||||
 | 
							mu:         &sync.RWMutex{},
 | 
				
			||||||
 | 
							expiration: config.Expiration,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lm.localLocks[key] = lock
 | 
				
			||||||
 | 
						return lock
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// createDistributedLock 创建分布式锁
 | 
				
			||||||
 | 
					func (lm *LockManager) createDistributedLock(config LockManagerConfig) *DistributedLock {
 | 
				
			||||||
 | 
						key := lm.buildLockKey(config.Scope, config.Key)
 | 
				
			||||||
 | 
						value := fmt.Sprintf("%d_%s", time.Now().UnixNano(), generateRandomString(8))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &DistributedLock{
 | 
				
			||||||
 | 
							key:         key,
 | 
				
			||||||
 | 
							value:       value,
 | 
				
			||||||
 | 
							expiration:  config.Expiration,
 | 
				
			||||||
 | 
							redisClient: lm.redisClient,
 | 
				
			||||||
 | 
							stopRefresh: make(chan struct{}),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// buildLockKey 构建锁键名
 | 
				
			||||||
 | 
					func (lm *LockManager) buildLockKey(scope LockScope, key string) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("lock:%s:%s", scope, key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AcquireLock 获取锁(带超时和重试)
 | 
				
			||||||
 | 
					func (lm *LockManager) AcquireLock(ctx context.Context, config LockManagerConfig) (Lock, error) {
 | 
				
			||||||
 | 
						lock := lm.CreateLock(config)
 | 
				
			||||||
 | 
						startTime := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 设置超时上下文
 | 
				
			||||||
 | 
						lockCtx := ctx
 | 
				
			||||||
 | 
						if config.Timeout > 0 {
 | 
				
			||||||
 | 
							var cancel context.CancelFunc
 | 
				
			||||||
 | 
							lockCtx, cancel = context.WithTimeout(ctx, config.Timeout)
 | 
				
			||||||
 | 
							defer cancel()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 重试获取锁
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-lockCtx.Done():
 | 
				
			||||||
 | 
								waitTime := time.Since(startTime)
 | 
				
			||||||
 | 
								if lm.metrics != nil {
 | 
				
			||||||
 | 
									lm.metrics.RecordLockOperation(false, waitTime)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("获取锁超时: %s", config.Key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								err := lock.Lock(lockCtx)
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									waitTime := time.Since(startTime)
 | 
				
			||||||
 | 
									if lm.metrics != nil {
 | 
				
			||||||
 | 
										lm.metrics.RecordLockOperation(true, waitTime)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return lock, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// 等待重试
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case <-lockCtx.Done():
 | 
				
			||||||
 | 
									return nil, lockCtx.Err()
 | 
				
			||||||
 | 
								case <-time.After(config.RetryDelay):
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReleaseLock 释放锁
 | 
				
			||||||
 | 
					func (lm *LockManager) ReleaseLock(ctx context.Context, lock Lock) error {
 | 
				
			||||||
 | 
						if lock == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return lock.Unlock(ctx)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithLock 使用锁执行操作
 | 
				
			||||||
 | 
					func (lm *LockManager) WithLock(ctx context.Context, config LockManagerConfig, operation func() error) error {
 | 
				
			||||||
 | 
						lock, err := lm.AcquireLock(ctx, config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("获取锁失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if unlockErr := lm.ReleaseLock(ctx, lock); unlockErr != nil {
 | 
				
			||||||
 | 
								logger.Errorf("释放锁失败 [%s]: %v", config.Key, unlockErr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return operation()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CleanupExpiredLocks 清理过期的本地锁
 | 
				
			||||||
 | 
					func (lm *LockManager) CleanupExpiredLocks() {
 | 
				
			||||||
 | 
						lm.localMutex.Lock()
 | 
				
			||||||
 | 
						defer lm.localMutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
						for key, lock := range lm.localLocks {
 | 
				
			||||||
 | 
							if lock.locked && lock.expiration > 0 && now.Sub(lock.acquiredAt) > lock.expiration {
 | 
				
			||||||
 | 
								lock.locked = false
 | 
				
			||||||
 | 
								logger.Warnf("本地锁已过期并被清理: %s", key)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLockStatus 获取锁状态
 | 
				
			||||||
 | 
					func (lm *LockManager) GetLockStatus() map[string]interface{} {
 | 
				
			||||||
 | 
						lm.localMutex.RLock()
 | 
				
			||||||
 | 
						defer lm.localMutex.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						status := make(map[string]interface{})
 | 
				
			||||||
 | 
						localLocks := make(map[string]interface{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for key, lock := range lm.localLocks {
 | 
				
			||||||
 | 
							localLocks[key] = map[string]interface{}{
 | 
				
			||||||
 | 
								"locked":      lock.locked,
 | 
				
			||||||
 | 
								"acquired_at": lock.acquiredAt,
 | 
				
			||||||
 | 
								"expiration":  lock.expiration,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						status["local_locks"] = localLocks
 | 
				
			||||||
 | 
						status["total_local_locks"] = len(lm.localLocks)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return status
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LocalLock 实现
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Lock 获取本地锁
 | 
				
			||||||
 | 
					func (ll *LocalLock) Lock(ctx context.Context) error {
 | 
				
			||||||
 | 
						ll.mu.Lock()
 | 
				
			||||||
 | 
						defer ll.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ll.locked {
 | 
				
			||||||
 | 
							return fmt.Errorf("锁已被占用: %s", ll.key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ll.locked = true
 | 
				
			||||||
 | 
						ll.acquiredAt = time.Now()
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Unlock 释放本地锁
 | 
				
			||||||
 | 
					func (ll *LocalLock) Unlock(ctx context.Context) error {
 | 
				
			||||||
 | 
						ll.mu.Lock()
 | 
				
			||||||
 | 
						defer ll.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !ll.locked {
 | 
				
			||||||
 | 
							return fmt.Errorf("锁未被占用: %s", ll.key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ll.locked = false
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TryLock 尝试获取本地锁
 | 
				
			||||||
 | 
					func (ll *LocalLock) TryLock(ctx context.Context) (bool, error) {
 | 
				
			||||||
 | 
						ll.mu.Lock()
 | 
				
			||||||
 | 
						defer ll.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ll.locked {
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ll.locked = true
 | 
				
			||||||
 | 
						ll.acquiredAt = time.Now()
 | 
				
			||||||
 | 
						return true, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsLocked 检查本地锁是否被占用
 | 
				
			||||||
 | 
					func (ll *LocalLock) IsLocked() bool {
 | 
				
			||||||
 | 
						ll.mu.RLock()
 | 
				
			||||||
 | 
						defer ll.mu.RUnlock()
 | 
				
			||||||
 | 
						return ll.locked
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetKey 获取锁键名
 | 
				
			||||||
 | 
					func (ll *LocalLock) GetKey() string {
 | 
				
			||||||
 | 
						return ll.key
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetExpiration 获取过期时间
 | 
				
			||||||
 | 
					func (ll *LocalLock) GetExpiration() time.Duration {
 | 
				
			||||||
 | 
						return ll.expiration
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DistributedLock 实现
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Lock 获取分布式锁
 | 
				
			||||||
 | 
					func (dl *DistributedLock) Lock(ctx context.Context) error {
 | 
				
			||||||
 | 
						// 使用 SET key value EX seconds NX 命令
 | 
				
			||||||
 | 
						result := dl.redisClient.SetNX(ctx, dl.key, dl.value, dl.expiration)
 | 
				
			||||||
 | 
						if result.Err() != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("获取分布式锁失败: %w", result.Err())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !result.Val() {
 | 
				
			||||||
 | 
							return fmt.Errorf("分布式锁已被占用: %s", dl.key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dl.locked = true
 | 
				
			||||||
 | 
						dl.acquiredAt = time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 启动锁续期
 | 
				
			||||||
 | 
						dl.startRefresh()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Unlock 释放分布式锁
 | 
				
			||||||
 | 
					func (dl *DistributedLock) Unlock(ctx context.Context) error {
 | 
				
			||||||
 | 
						if !dl.locked {
 | 
				
			||||||
 | 
							return fmt.Errorf("分布式锁未被占用: %s", dl.key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 停止续期
 | 
				
			||||||
 | 
						dl.stopRefreshProcess()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 使用 Lua 脚本确保只删除自己的锁
 | 
				
			||||||
 | 
						luaScript := `
 | 
				
			||||||
 | 
							if redis.call("get", KEYS[1]) == ARGV[1] then
 | 
				
			||||||
 | 
								return redis.call("del", KEYS[1])
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
 | 
						`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result := dl.redisClient.Eval(ctx, luaScript, []string{dl.key}, dl.value)
 | 
				
			||||||
 | 
						if result.Err() != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("释放分布式锁失败: %w", result.Err())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dl.locked = false
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TryLock 尝试获取分布式锁
 | 
				
			||||||
 | 
					func (dl *DistributedLock) TryLock(ctx context.Context) (bool, error) {
 | 
				
			||||||
 | 
						result := dl.redisClient.SetNX(ctx, dl.key, dl.value, dl.expiration)
 | 
				
			||||||
 | 
						if result.Err() != nil {
 | 
				
			||||||
 | 
							return false, fmt.Errorf("尝试获取分布式锁失败: %w", result.Err())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if result.Val() {
 | 
				
			||||||
 | 
							dl.locked = true
 | 
				
			||||||
 | 
							dl.acquiredAt = time.Now()
 | 
				
			||||||
 | 
							dl.startRefresh()
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsLocked 检查分布式锁是否被占用
 | 
				
			||||||
 | 
					func (dl *DistributedLock) IsLocked() bool {
 | 
				
			||||||
 | 
						return dl.locked
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetKey 获取锁键名
 | 
				
			||||||
 | 
					func (dl *DistributedLock) GetKey() string {
 | 
				
			||||||
 | 
						return dl.key
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetExpiration 获取过期时间
 | 
				
			||||||
 | 
					func (dl *DistributedLock) GetExpiration() time.Duration {
 | 
				
			||||||
 | 
						return dl.expiration
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// startRefresh 启动锁续期
 | 
				
			||||||
 | 
					func (dl *DistributedLock) startRefresh() {
 | 
				
			||||||
 | 
						if dl.expiration <= 0 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 每隔过期时间的1/3进行续期
 | 
				
			||||||
 | 
						refreshInterval := dl.expiration / 3
 | 
				
			||||||
 | 
						dl.refreshTicker = time.NewTicker(refreshInterval)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer dl.refreshTicker.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case <-dl.stopRefresh:
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								case <-dl.refreshTicker.C:
 | 
				
			||||||
 | 
									if !dl.locked {
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// 使用 Lua 脚本续期
 | 
				
			||||||
 | 
									luaScript := `
 | 
				
			||||||
 | 
										if redis.call("get", KEYS[1]) == ARGV[1] then
 | 
				
			||||||
 | 
											return redis.call("expire", KEYS[1], ARGV[2])
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
											return 0
 | 
				
			||||||
 | 
										end
 | 
				
			||||||
 | 
									`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ctx := context.Background()
 | 
				
			||||||
 | 
									result := dl.redisClient.Eval(ctx, luaScript, []string{dl.key}, dl.value, int(dl.expiration.Seconds()))
 | 
				
			||||||
 | 
									if result.Err() != nil {
 | 
				
			||||||
 | 
										logger.Errorf("分布式锁续期失败 [%s]: %v", dl.key, result.Err())
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if result.Val().(int64) == 0 {
 | 
				
			||||||
 | 
										logger.Warnf("分布式锁已被其他进程占用,停止续期 [%s]", dl.key)
 | 
				
			||||||
 | 
										dl.locked = false
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// stopRefreshProcess 停止续期进程
 | 
				
			||||||
 | 
					func (dl *DistributedLock) stopRefreshProcess() {
 | 
				
			||||||
 | 
						if dl.refreshTicker != nil {
 | 
				
			||||||
 | 
							close(dl.stopRefresh)
 | 
				
			||||||
 | 
							dl.refreshTicker.Stop()
 | 
				
			||||||
 | 
							dl.refreshTicker = nil
 | 
				
			||||||
 | 
							dl.stopRefresh = make(chan struct{})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 辅助函数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// generateRandomString 生成随机字符串
 | 
				
			||||||
 | 
					func generateRandomString(length int) string {
 | 
				
			||||||
 | 
						const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 | 
				
			||||||
 | 
						b := make([]byte, length)
 | 
				
			||||||
 | 
						for i := range b {
 | 
				
			||||||
 | 
							b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return string(b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 预定义的锁配置
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetOrderLockConfig 获取订单锁配置
 | 
				
			||||||
 | 
					func GetOrderLockConfig(orderID string) LockManagerConfig {
 | 
				
			||||||
 | 
						return LockManagerConfig{
 | 
				
			||||||
 | 
							Type:       LockTypeDistributed,
 | 
				
			||||||
 | 
							Scope:      ScopeOrder,
 | 
				
			||||||
 | 
							Key:        orderID,
 | 
				
			||||||
 | 
							Expiration: 30 * time.Second,
 | 
				
			||||||
 | 
							Timeout:    5 * time.Second,
 | 
				
			||||||
 | 
							RetryDelay: 100 * time.Millisecond,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPositionLockConfig 获取持仓锁配置
 | 
				
			||||||
 | 
					func GetPositionLockConfig(userID, symbol string) LockManagerConfig {
 | 
				
			||||||
 | 
						return LockManagerConfig{
 | 
				
			||||||
 | 
							Type:       LockTypeDistributed,
 | 
				
			||||||
 | 
							Scope:      ScopePosition,
 | 
				
			||||||
 | 
							Key:        fmt.Sprintf("%s_%s", userID, symbol),
 | 
				
			||||||
 | 
							Expiration: 60 * time.Second,
 | 
				
			||||||
 | 
							Timeout:    10 * time.Second,
 | 
				
			||||||
 | 
							RetryDelay: 200 * time.Millisecond,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUserLockConfig 获取用户锁配置
 | 
				
			||||||
 | 
					func GetUserLockConfig(userID string) LockManagerConfig {
 | 
				
			||||||
 | 
						return LockManagerConfig{
 | 
				
			||||||
 | 
							Type:       LockTypeLocal,
 | 
				
			||||||
 | 
							Scope:      ScopeUser,
 | 
				
			||||||
 | 
							Key:        userID,
 | 
				
			||||||
 | 
							Expiration: 30 * time.Second,
 | 
				
			||||||
 | 
							Timeout:    3 * time.Second,
 | 
				
			||||||
 | 
							RetryDelay: 50 * time.Millisecond,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetSymbolLockConfig 获取交易对锁配置
 | 
				
			||||||
 | 
					func GetSymbolLockConfig(symbol string) LockManagerConfig {
 | 
				
			||||||
 | 
						return LockManagerConfig{
 | 
				
			||||||
 | 
							Type:       LockTypeLocal,
 | 
				
			||||||
 | 
							Scope:      ScopeSymbol,
 | 
				
			||||||
 | 
							Key:        symbol,
 | 
				
			||||||
 | 
							Expiration: 15 * time.Second,
 | 
				
			||||||
 | 
							Timeout:    2 * time.Second,
 | 
				
			||||||
 | 
							RetryDelay: 25 * time.Millisecond,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetGlobalLockConfig 获取全局锁配置
 | 
				
			||||||
 | 
					func GetGlobalLockConfig(operation string) LockManagerConfig {
 | 
				
			||||||
 | 
						return LockManagerConfig{
 | 
				
			||||||
 | 
							Type:       LockTypeDistributed,
 | 
				
			||||||
 | 
							Scope:      ScopeGlobal,
 | 
				
			||||||
 | 
							Key:        operation,
 | 
				
			||||||
 | 
							Expiration: 120 * time.Second,
 | 
				
			||||||
 | 
							Timeout:    30 * time.Second,
 | 
				
			||||||
 | 
							RetryDelay: 500 * time.Millisecond,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 全局锁管理器实例
 | 
				
			||||||
 | 
					var GlobalLockManager *LockManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InitLockManager 初始化锁管理器
 | 
				
			||||||
 | 
					func InitLockManager(config *OptimizedConfig, redisClient *redis.Client, metrics *MetricsCollector) {
 | 
				
			||||||
 | 
						GlobalLockManager = NewLockManager(config, redisClient, metrics)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 启动清理过期锁的定时任务
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							ticker := time.NewTicker(time.Minute)
 | 
				
			||||||
 | 
							defer ticker.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for range ticker.C {
 | 
				
			||||||
 | 
								GlobalLockManager.CleanupExpiredLocks()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLockManager 获取全局锁管理器
 | 
				
			||||||
 | 
					func GetLockManager() *LockManager {
 | 
				
			||||||
 | 
						return GlobalLockManager
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithOrderLock 使用订单锁执行操作
 | 
				
			||||||
 | 
					func WithOrderLock(ctx context.Context, orderID string, operation func() error) error {
 | 
				
			||||||
 | 
						if GlobalLockManager == nil {
 | 
				
			||||||
 | 
							return operation()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := GetOrderLockConfig(orderID)
 | 
				
			||||||
 | 
						return GlobalLockManager.WithLock(ctx, config, operation)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithPositionLock 使用持仓锁执行操作
 | 
				
			||||||
 | 
					func WithPositionLock(ctx context.Context, userID, symbol string, operation func() error) error {
 | 
				
			||||||
 | 
						if GlobalLockManager == nil {
 | 
				
			||||||
 | 
							return operation()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := GetPositionLockConfig(userID, symbol)
 | 
				
			||||||
 | 
						return GlobalLockManager.WithLock(ctx, config, operation)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithUserLock 使用用户锁执行操作
 | 
				
			||||||
 | 
					func WithUserLock(ctx context.Context, userID string, operation func() error) error {
 | 
				
			||||||
 | 
						if GlobalLockManager == nil {
 | 
				
			||||||
 | 
							return operation()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := GetUserLockConfig(userID)
 | 
				
			||||||
 | 
						return GlobalLockManager.WithLock(ctx, config, operation)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -61,17 +61,19 @@ type EntryPriceResult struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type FutOrderPlace struct {
 | 
					type FutOrderPlace struct {
 | 
				
			||||||
	ApiId            int             `json:"api_id"`     //api用户id
 | 
						ApiId            int             `json:"api_id"`        //api用户id
 | 
				
			||||||
	Symbol           string          `json:"symbol"`     //合约交易对
 | 
						Symbol           string          `json:"symbol"`        //合约交易对
 | 
				
			||||||
	Side             string          `json:"side"`       //购买方向
 | 
						Side             string          `json:"side"`          //购买方向
 | 
				
			||||||
	Quantity         decimal.Decimal `json:"quantity"`   //数量
 | 
						PositionSide     string          `json:"position_side"` //持仓方向
 | 
				
			||||||
	Price            decimal.Decimal `json:"price"`      //限价单价
 | 
						Quantity         decimal.Decimal `json:"quantity"`      //数量
 | 
				
			||||||
	SideType         string          `json:"side_type"`  //现价或者市价
 | 
						Price            decimal.Decimal `json:"price"`         //限价单价
 | 
				
			||||||
	OpenOrder        int             `json:"open_order"` //是否开启限价单止盈止损
 | 
						SideType         string          `json:"side_type"`     //现价或者市价
 | 
				
			||||||
	Profit           decimal.Decimal `json:"profit"`     //止盈价格
 | 
						OpenOrder        int             `json:"open_order"`    //是否开启限价单止盈止损
 | 
				
			||||||
	StopPrice        decimal.Decimal `json:"stopprice"`  //止损价格
 | 
						Profit           decimal.Decimal `json:"profit"`        //止盈价格
 | 
				
			||||||
	OrderType        string          `json:"order_type"` //订单类型,市价或限价MARKET(市价单) TAKE_PROFIT_MARKET(市价止盈) TAKE_PROFIT(限价止盈) STOP (限价止损)  STOP_MARKET(市价止损)
 | 
						StopPrice        decimal.Decimal `json:"stopprice"`     //止损价格
 | 
				
			||||||
 | 
						OrderType        string          `json:"order_type"`    //订单类型,市价或限价MARKET(市价单) TAKE_PROFIT_MARKET(市价止盈) TAKE_PROFIT(限价止盈) STOP (限价止损)  STOP_MARKET(市价止损)
 | 
				
			||||||
	NewClientOrderId string          `json:"newClientOrderId"`
 | 
						NewClientOrderId string          `json:"newClientOrderId"`
 | 
				
			||||||
 | 
						ClosePosition    bool            `json:"closePosition"` //是否平仓
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s FutOrderPlace) CheckParams() error {
 | 
					func (s FutOrderPlace) CheckParams() error {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										594
									
								
								services/binanceservice/monitor_optimized.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										594
									
								
								services/binanceservice/monitor_optimized.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,594 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MetricsCollector 指标收集器
 | 
				
			||||||
 | 
					type MetricsCollector struct {
 | 
				
			||||||
 | 
						mu sync.RWMutex
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 业务指标
 | 
				
			||||||
 | 
						OrderMetrics      *OrderMetrics
 | 
				
			||||||
 | 
						PositionMetrics   *PositionMetrics
 | 
				
			||||||
 | 
						TakeProfitMetrics *TakeProfitMetrics
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 技术指标
 | 
				
			||||||
 | 
						PerformanceMetrics *PerformanceMetrics
 | 
				
			||||||
 | 
						ErrorMetrics       *ErrorMetrics
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 系统指标
 | 
				
			||||||
 | 
						SystemMetrics *SystemMetrics
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 配置
 | 
				
			||||||
 | 
						config *OptimizedConfig
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 控制
 | 
				
			||||||
 | 
						ctx    context.Context
 | 
				
			||||||
 | 
						cancel context.CancelFunc
 | 
				
			||||||
 | 
						startTime time.Time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OrderMetrics 订单相关指标
 | 
				
			||||||
 | 
					type OrderMetrics struct {
 | 
				
			||||||
 | 
						// 订单处理统计
 | 
				
			||||||
 | 
						TotalProcessed    int64 `json:"total_processed"`
 | 
				
			||||||
 | 
						SuccessfulOrders  int64 `json:"successful_orders"`
 | 
				
			||||||
 | 
						FailedOrders      int64 `json:"failed_orders"`
 | 
				
			||||||
 | 
						CanceledOrders    int64 `json:"canceled_orders"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 反向订单统计
 | 
				
			||||||
 | 
						ReverseOrdersCreated int64 `json:"reverse_orders_created"`
 | 
				
			||||||
 | 
						ReverseOrdersFailed  int64 `json:"reverse_orders_failed"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 响应时间统计
 | 
				
			||||||
 | 
						TotalResponseTime time.Duration `json:"total_response_time"`
 | 
				
			||||||
 | 
						MaxResponseTime   time.Duration `json:"max_response_time"`
 | 
				
			||||||
 | 
						MinResponseTime   time.Duration `json:"min_response_time"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 最近更新时间
 | 
				
			||||||
 | 
						LastUpdated time.Time `json:"last_updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PositionMetrics 持仓相关指标
 | 
				
			||||||
 | 
					type PositionMetrics struct {
 | 
				
			||||||
 | 
						// 持仓同步统计
 | 
				
			||||||
 | 
						SyncAttempts     int64 `json:"sync_attempts"`
 | 
				
			||||||
 | 
						SyncSuccesses    int64 `json:"sync_successes"`
 | 
				
			||||||
 | 
						SyncFailures     int64 `json:"sync_failures"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 持仓差异统计
 | 
				
			||||||
 | 
						PositionMismatches int64           `json:"position_mismatches"`
 | 
				
			||||||
 | 
						MaxDifference      decimal.Decimal `json:"max_difference"`
 | 
				
			||||||
 | 
						TotalDifference    decimal.Decimal `json:"total_difference"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 平仓统计
 | 
				
			||||||
 | 
						ClosePositions        int64 `json:"close_positions"`
 | 
				
			||||||
 | 
						ForceClosePositions   int64 `json:"force_close_positions"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						LastUpdated time.Time `json:"last_updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TakeProfitMetrics 止盈止损相关指标
 | 
				
			||||||
 | 
					type TakeProfitMetrics struct {
 | 
				
			||||||
 | 
						// 止盈止损创建统计
 | 
				
			||||||
 | 
						TakeProfitCreated int64 `json:"take_profit_created"`
 | 
				
			||||||
 | 
						StopLossCreated   int64 `json:"stop_loss_created"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 止盈止损执行统计
 | 
				
			||||||
 | 
						TakeProfitExecuted int64 `json:"take_profit_executed"`
 | 
				
			||||||
 | 
						StopLossExecuted   int64 `json:"stop_loss_executed"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 取消统计
 | 
				
			||||||
 | 
						OrdersCanceled       int64 `json:"orders_canceled"`
 | 
				
			||||||
 | 
						BatchCancelAttempts  int64 `json:"batch_cancel_attempts"`
 | 
				
			||||||
 | 
						BatchCancelSuccesses int64 `json:"batch_cancel_successes"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						LastUpdated time.Time `json:"last_updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PerformanceMetrics 性能相关指标
 | 
				
			||||||
 | 
					type PerformanceMetrics struct {
 | 
				
			||||||
 | 
						// 并发统计
 | 
				
			||||||
 | 
						ConcurrentRequests int64 `json:"concurrent_requests"`
 | 
				
			||||||
 | 
						MaxConcurrency     int64 `json:"max_concurrency"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 锁统计
 | 
				
			||||||
 | 
						LockAcquisitions int64         `json:"lock_acquisitions"`
 | 
				
			||||||
 | 
						LockWaitTime     time.Duration `json:"lock_wait_time"`
 | 
				
			||||||
 | 
						LockTimeouts     int64         `json:"lock_timeouts"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 数据库操作统计
 | 
				
			||||||
 | 
						DbQueries        int64         `json:"db_queries"`
 | 
				
			||||||
 | 
						DbTransactions   int64         `json:"db_transactions"`
 | 
				
			||||||
 | 
						DbQueryTime      time.Duration `json:"db_query_time"`
 | 
				
			||||||
 | 
						DbErrors         int64         `json:"db_errors"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// API调用统计
 | 
				
			||||||
 | 
						ApiCalls        int64         `json:"api_calls"`
 | 
				
			||||||
 | 
						ApiCallTime     time.Duration `json:"api_call_time"`
 | 
				
			||||||
 | 
						ApiErrors       int64         `json:"api_errors"`
 | 
				
			||||||
 | 
						ApiRetries      int64         `json:"api_retries"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						LastUpdated time.Time `json:"last_updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorMetrics 错误相关指标
 | 
				
			||||||
 | 
					type ErrorMetrics struct {
 | 
				
			||||||
 | 
						// 错误分类统计
 | 
				
			||||||
 | 
						NetworkErrors    int64 `json:"network_errors"`
 | 
				
			||||||
 | 
						DatabaseErrors   int64 `json:"database_errors"`
 | 
				
			||||||
 | 
						BusinessErrors   int64 `json:"business_errors"`
 | 
				
			||||||
 | 
						SystemErrors     int64 `json:"system_errors"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 重试统计
 | 
				
			||||||
 | 
						RetryAttempts   int64 `json:"retry_attempts"`
 | 
				
			||||||
 | 
						RetrySuccesses  int64 `json:"retry_successes"`
 | 
				
			||||||
 | 
						RetryFailures   int64 `json:"retry_failures"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 最近错误
 | 
				
			||||||
 | 
						RecentErrors []ErrorRecord `json:"recent_errors"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						LastUpdated time.Time `json:"last_updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SystemMetrics 系统相关指标
 | 
				
			||||||
 | 
					type SystemMetrics struct {
 | 
				
			||||||
 | 
						// 内存使用
 | 
				
			||||||
 | 
						MemoryUsage    uint64  `json:"memory_usage"`
 | 
				
			||||||
 | 
						MemoryPercent  float64 `json:"memory_percent"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// CPU使用
 | 
				
			||||||
 | 
						CpuPercent float64 `json:"cpu_percent"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Goroutine统计
 | 
				
			||||||
 | 
						GoroutineCount int `json:"goroutine_count"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// GC统计
 | 
				
			||||||
 | 
						GcCount    uint32 `json:"gc_count"`
 | 
				
			||||||
 | 
						GcPauseMs  uint64 `json:"gc_pause_ms"`
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						LastUpdated time.Time `json:"last_updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorRecord 错误记录
 | 
				
			||||||
 | 
					type ErrorRecord struct {
 | 
				
			||||||
 | 
						Timestamp time.Time `json:"timestamp"`
 | 
				
			||||||
 | 
						Type      string    `json:"type"`
 | 
				
			||||||
 | 
						Message   string    `json:"message"`
 | 
				
			||||||
 | 
						Context   string    `json:"context"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewMetricsCollector 创建新的指标收集器
 | 
				
			||||||
 | 
					func NewMetricsCollector(config *OptimizedConfig) *MetricsCollector {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return &MetricsCollector{
 | 
				
			||||||
 | 
							OrderMetrics: &OrderMetrics{
 | 
				
			||||||
 | 
								MinResponseTime: time.Hour, // 初始化为一个大值
 | 
				
			||||||
 | 
								LastUpdated:     time.Now(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							PositionMetrics: &PositionMetrics{
 | 
				
			||||||
 | 
								MaxDifference:   decimal.Zero,
 | 
				
			||||||
 | 
								TotalDifference: decimal.Zero,
 | 
				
			||||||
 | 
								LastUpdated:     time.Now(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							TakeProfitMetrics: &TakeProfitMetrics{
 | 
				
			||||||
 | 
								LastUpdated: time.Now(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							PerformanceMetrics: &PerformanceMetrics{
 | 
				
			||||||
 | 
								LastUpdated: time.Now(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							ErrorMetrics: &ErrorMetrics{
 | 
				
			||||||
 | 
								RecentErrors: make([]ErrorRecord, 0, 100),
 | 
				
			||||||
 | 
								LastUpdated:  time.Now(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							SystemMetrics: &SystemMetrics{
 | 
				
			||||||
 | 
								LastUpdated: time.Now(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							config:    config,
 | 
				
			||||||
 | 
							ctx:       ctx,
 | 
				
			||||||
 | 
							cancel:    cancel,
 | 
				
			||||||
 | 
							startTime: time.Now(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start 启动指标收集
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) Start() {
 | 
				
			||||||
 | 
						if !mc.config.MonitorConfig.Enabled {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						logger.Info("启动指标收集器")
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 启动定期收集
 | 
				
			||||||
 | 
						go mc.collectLoop()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 启动告警检查
 | 
				
			||||||
 | 
						go mc.alertLoop()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Stop 停止指标收集
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) Stop() {
 | 
				
			||||||
 | 
						logger.Info("停止指标收集器")
 | 
				
			||||||
 | 
						mc.cancel()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// collectLoop 收集循环
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) collectLoop() {
 | 
				
			||||||
 | 
						ticker := time.NewTicker(mc.config.MonitorConfig.CollectInterval)
 | 
				
			||||||
 | 
						defer ticker.Stop()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-mc.ctx.Done():
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								mc.collectSystemMetrics()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// alertLoop 告警检查循环
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) alertLoop() {
 | 
				
			||||||
 | 
						ticker := time.NewTicker(time.Minute) // 每分钟检查一次
 | 
				
			||||||
 | 
						defer ticker.Stop()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-mc.ctx.Done():
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								mc.checkAlerts()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordOrderProcessed 记录订单处理
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordOrderProcessed(success bool, responseTime time.Duration) {
 | 
				
			||||||
 | 
						mc.mu.Lock()
 | 
				
			||||||
 | 
						defer mc.mu.Unlock()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.OrderMetrics.TotalProcessed, 1)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if success {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.OrderMetrics.SuccessfulOrders, 1)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.OrderMetrics.FailedOrders, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 更新响应时间统计
 | 
				
			||||||
 | 
						mc.OrderMetrics.TotalResponseTime += responseTime
 | 
				
			||||||
 | 
						if responseTime > mc.OrderMetrics.MaxResponseTime {
 | 
				
			||||||
 | 
							mc.OrderMetrics.MaxResponseTime = responseTime
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if responseTime < mc.OrderMetrics.MinResponseTime {
 | 
				
			||||||
 | 
							mc.OrderMetrics.MinResponseTime = responseTime
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.OrderMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordOrderCanceled 记录订单取消
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordOrderCanceled() {
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.OrderMetrics.CanceledOrders, 1)
 | 
				
			||||||
 | 
						mc.OrderMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordReverseOrder 记录反向订单
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordReverseOrder(success bool) {
 | 
				
			||||||
 | 
						if success {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.OrderMetrics.ReverseOrdersCreated, 1)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.OrderMetrics.ReverseOrdersFailed, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mc.OrderMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordPositionSync 记录持仓同步
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordPositionSync(success bool, difference decimal.Decimal) {
 | 
				
			||||||
 | 
						mc.mu.Lock()
 | 
				
			||||||
 | 
						defer mc.mu.Unlock()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.PositionMetrics.SyncAttempts, 1)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if success {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PositionMetrics.SyncSuccesses, 1)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PositionMetrics.SyncFailures, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 更新差异统计
 | 
				
			||||||
 | 
						if !difference.IsZero() {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PositionMetrics.PositionMismatches, 1)
 | 
				
			||||||
 | 
							mc.PositionMetrics.TotalDifference = mc.PositionMetrics.TotalDifference.Add(difference.Abs())
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if difference.Abs().GreaterThan(mc.PositionMetrics.MaxDifference) {
 | 
				
			||||||
 | 
								mc.PositionMetrics.MaxDifference = difference.Abs()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.PositionMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordClosePosition 记录平仓
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordClosePosition(forced bool) {
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.PositionMetrics.ClosePositions, 1)
 | 
				
			||||||
 | 
						if forced {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PositionMetrics.ForceClosePositions, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mc.PositionMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordTakeProfitCreated 记录止盈止损创建
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordTakeProfitCreated(orderType string) {
 | 
				
			||||||
 | 
						switch orderType {
 | 
				
			||||||
 | 
						case "TAKE_PROFIT", "TAKE_PROFIT_MARKET":
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.TakeProfitMetrics.TakeProfitCreated, 1)
 | 
				
			||||||
 | 
						case "STOP", "STOP_MARKET":
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.TakeProfitMetrics.StopLossCreated, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mc.TakeProfitMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordTakeProfitExecuted 记录止盈止损执行
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordTakeProfitExecuted(orderType string) {
 | 
				
			||||||
 | 
						switch orderType {
 | 
				
			||||||
 | 
						case "TAKE_PROFIT", "TAKE_PROFIT_MARKET":
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.TakeProfitMetrics.TakeProfitExecuted, 1)
 | 
				
			||||||
 | 
						case "STOP", "STOP_MARKET":
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.TakeProfitMetrics.StopLossExecuted, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mc.TakeProfitMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordOrdersCanceled 记录订单取消
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordOrdersCanceled(count int) {
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.TakeProfitMetrics.OrdersCanceled, int64(count))
 | 
				
			||||||
 | 
						mc.TakeProfitMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordBatchCancel 记录批量取消
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordBatchCancel(success bool) {
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.TakeProfitMetrics.BatchCancelAttempts, 1)
 | 
				
			||||||
 | 
						if success {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.TakeProfitMetrics.BatchCancelSuccesses, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mc.TakeProfitMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordLockOperation 记录锁操作
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordLockOperation(acquired bool, waitTime time.Duration) {
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.PerformanceMetrics.LockAcquisitions, 1)
 | 
				
			||||||
 | 
						mc.PerformanceMetrics.LockWaitTime += waitTime
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if !acquired {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PerformanceMetrics.LockTimeouts, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.PerformanceMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordDbOperation 记录数据库操作
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordDbOperation(isTransaction bool, duration time.Duration, err error) {
 | 
				
			||||||
 | 
						if isTransaction {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PerformanceMetrics.DbTransactions, 1)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PerformanceMetrics.DbQueries, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.PerformanceMetrics.DbQueryTime += duration
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PerformanceMetrics.DbErrors, 1)
 | 
				
			||||||
 | 
							mc.RecordError("database", err.Error(), "db_operation")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.PerformanceMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordApiCall 记录API调用
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordApiCall(duration time.Duration, err error, retryCount int) {
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.PerformanceMetrics.ApiCalls, 1)
 | 
				
			||||||
 | 
						mc.PerformanceMetrics.ApiCallTime += duration
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PerformanceMetrics.ApiErrors, 1)
 | 
				
			||||||
 | 
							mc.RecordError("api", err.Error(), "api_call")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if retryCount > 0 {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.PerformanceMetrics.ApiRetries, int64(retryCount))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.PerformanceMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordConcurrency 记录并发数
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordConcurrency(current int64) {
 | 
				
			||||||
 | 
						atomic.StoreInt64(&mc.PerformanceMetrics.ConcurrentRequests, current)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if current > atomic.LoadInt64(&mc.PerformanceMetrics.MaxConcurrency) {
 | 
				
			||||||
 | 
							atomic.StoreInt64(&mc.PerformanceMetrics.MaxConcurrency, current)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.PerformanceMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordError 记录错误
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordError(errorType, message, context string) {
 | 
				
			||||||
 | 
						mc.mu.Lock()
 | 
				
			||||||
 | 
						defer mc.mu.Unlock()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 分类统计
 | 
				
			||||||
 | 
						switch errorType {
 | 
				
			||||||
 | 
						case "network":
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.ErrorMetrics.NetworkErrors, 1)
 | 
				
			||||||
 | 
						case "database":
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.ErrorMetrics.DatabaseErrors, 1)
 | 
				
			||||||
 | 
						case "business":
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.ErrorMetrics.BusinessErrors, 1)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.ErrorMetrics.SystemErrors, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 记录最近错误
 | 
				
			||||||
 | 
						errorRecord := ErrorRecord{
 | 
				
			||||||
 | 
							Timestamp: time.Now(),
 | 
				
			||||||
 | 
							Type:      errorType,
 | 
				
			||||||
 | 
							Message:   message,
 | 
				
			||||||
 | 
							Context:   context,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 保持最近100个错误
 | 
				
			||||||
 | 
						if len(mc.ErrorMetrics.RecentErrors) >= 100 {
 | 
				
			||||||
 | 
							mc.ErrorMetrics.RecentErrors = mc.ErrorMetrics.RecentErrors[1:]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mc.ErrorMetrics.RecentErrors = append(mc.ErrorMetrics.RecentErrors, errorRecord)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.ErrorMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RecordRetry 记录重试
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) RecordRetry(success bool) {
 | 
				
			||||||
 | 
						atomic.AddInt64(&mc.ErrorMetrics.RetryAttempts, 1)
 | 
				
			||||||
 | 
						if success {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.ErrorMetrics.RetrySuccesses, 1)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							atomic.AddInt64(&mc.ErrorMetrics.RetryFailures, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mc.ErrorMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// collectSystemMetrics 收集系统指标
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) collectSystemMetrics() {
 | 
				
			||||||
 | 
						mc.mu.Lock()
 | 
				
			||||||
 | 
						defer mc.mu.Unlock()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 获取内存统计
 | 
				
			||||||
 | 
						var m runtime.MemStats
 | 
				
			||||||
 | 
						runtime.ReadMemStats(&m)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.SystemMetrics.MemoryUsage = m.Alloc
 | 
				
			||||||
 | 
						mc.SystemMetrics.GoroutineCount = runtime.NumGoroutine()
 | 
				
			||||||
 | 
						mc.SystemMetrics.GcCount = m.NumGC
 | 
				
			||||||
 | 
						mc.SystemMetrics.GcPauseMs = uint64(m.PauseNs[(m.NumGC+255)%256]) / 1000000
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						mc.SystemMetrics.LastUpdated = time.Now()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// checkAlerts 检查告警
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) checkAlerts() {
 | 
				
			||||||
 | 
						thresholds := mc.config.MonitorConfig.AlertThresholds
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 检查订单失败率
 | 
				
			||||||
 | 
						if mc.OrderMetrics.TotalProcessed > 0 {
 | 
				
			||||||
 | 
							failureRate := float64(mc.OrderMetrics.FailedOrders) / float64(mc.OrderMetrics.TotalProcessed) * 100
 | 
				
			||||||
 | 
							if failureRate > thresholds.OrderFailureRate {
 | 
				
			||||||
 | 
								logger.Warnf("订单失败率告警: %.2f%% (阈值: %.2f%%)", failureRate, thresholds.OrderFailureRate)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 检查持仓同步失败率
 | 
				
			||||||
 | 
						if mc.PositionMetrics.SyncAttempts > 0 {
 | 
				
			||||||
 | 
							syncFailureRate := float64(mc.PositionMetrics.SyncFailures) / float64(mc.PositionMetrics.SyncAttempts) * 100
 | 
				
			||||||
 | 
							if syncFailureRate > thresholds.PositionSyncFailureRate {
 | 
				
			||||||
 | 
								logger.Warnf("持仓同步失败率告警: %.2f%% (阈值: %.2f%%)", syncFailureRate, thresholds.PositionSyncFailureRate)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 检查API失败率
 | 
				
			||||||
 | 
						if mc.PerformanceMetrics.ApiCalls > 0 {
 | 
				
			||||||
 | 
							apiFailureRate := float64(mc.PerformanceMetrics.ApiErrors) / float64(mc.PerformanceMetrics.ApiCalls) * 100
 | 
				
			||||||
 | 
							if apiFailureRate > thresholds.ApiFailureRate {
 | 
				
			||||||
 | 
								logger.Warnf("API失败率告警: %.2f%% (阈值: %.2f%%)", apiFailureRate, thresholds.ApiFailureRate)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 检查内存使用率
 | 
				
			||||||
 | 
						if mc.SystemMetrics.MemoryPercent > thresholds.MemoryUsageThreshold {
 | 
				
			||||||
 | 
							logger.Warnf("内存使用率告警: %.2f%% (阈值: %.2f%%)", mc.SystemMetrics.MemoryPercent, thresholds.MemoryUsageThreshold)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 检查CPU使用率
 | 
				
			||||||
 | 
						if mc.SystemMetrics.CpuPercent > thresholds.CpuUsageThreshold {
 | 
				
			||||||
 | 
							logger.Warnf("CPU使用率告警: %.2f%% (阈值: %.2f%%)", mc.SystemMetrics.CpuPercent, thresholds.CpuUsageThreshold)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetMetrics 获取所有指标
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) GetMetrics() map[string]interface{} {
 | 
				
			||||||
 | 
						mc.mu.RLock()
 | 
				
			||||||
 | 
						defer mc.mu.RUnlock()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return map[string]interface{}{
 | 
				
			||||||
 | 
							"order_metrics":       mc.OrderMetrics,
 | 
				
			||||||
 | 
							"position_metrics":    mc.PositionMetrics,
 | 
				
			||||||
 | 
							"take_profit_metrics": mc.TakeProfitMetrics,
 | 
				
			||||||
 | 
							"performance_metrics": mc.PerformanceMetrics,
 | 
				
			||||||
 | 
							"error_metrics":       mc.ErrorMetrics,
 | 
				
			||||||
 | 
							"system_metrics":      mc.SystemMetrics,
 | 
				
			||||||
 | 
							"uptime":              time.Since(mc.startTime).String(),
 | 
				
			||||||
 | 
							"collected_at":        time.Now(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetSummary 获取指标摘要
 | 
				
			||||||
 | 
					func (mc *MetricsCollector) GetSummary() map[string]interface{} {
 | 
				
			||||||
 | 
						mc.mu.RLock()
 | 
				
			||||||
 | 
						defer mc.mu.RUnlock()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						summary := make(map[string]interface{})
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 订单摘要
 | 
				
			||||||
 | 
						if mc.OrderMetrics.TotalProcessed > 0 {
 | 
				
			||||||
 | 
							successRate := float64(mc.OrderMetrics.SuccessfulOrders) / float64(mc.OrderMetrics.TotalProcessed) * 100
 | 
				
			||||||
 | 
							avgResponseTime := mc.OrderMetrics.TotalResponseTime / time.Duration(mc.OrderMetrics.TotalProcessed)
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							summary["order_success_rate"] = fmt.Sprintf("%.2f%%", successRate)
 | 
				
			||||||
 | 
							summary["avg_response_time"] = avgResponseTime.String()
 | 
				
			||||||
 | 
							summary["total_orders"] = mc.OrderMetrics.TotalProcessed
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 持仓摘要
 | 
				
			||||||
 | 
						if mc.PositionMetrics.SyncAttempts > 0 {
 | 
				
			||||||
 | 
							syncSuccessRate := float64(mc.PositionMetrics.SyncSuccesses) / float64(mc.PositionMetrics.SyncAttempts) * 100
 | 
				
			||||||
 | 
							summary["position_sync_rate"] = fmt.Sprintf("%.2f%%", syncSuccessRate)
 | 
				
			||||||
 | 
							summary["position_mismatches"] = mc.PositionMetrics.PositionMismatches
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 性能摘要
 | 
				
			||||||
 | 
						summary["current_concurrency"] = mc.PerformanceMetrics.ConcurrentRequests
 | 
				
			||||||
 | 
						summary["max_concurrency"] = mc.PerformanceMetrics.MaxConcurrency
 | 
				
			||||||
 | 
						summary["total_api_calls"] = mc.PerformanceMetrics.ApiCalls
 | 
				
			||||||
 | 
						summary["total_db_queries"] = mc.PerformanceMetrics.DbQueries
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 系统摘要
 | 
				
			||||||
 | 
						summary["memory_usage_mb"] = mc.SystemMetrics.MemoryUsage / 1024 / 1024
 | 
				
			||||||
 | 
						summary["goroutine_count"] = mc.SystemMetrics.GoroutineCount
 | 
				
			||||||
 | 
						summary["uptime"] = time.Since(mc.startTime).String()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return summary
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 全局指标收集器实例
 | 
				
			||||||
 | 
					var GlobalMetricsCollector *MetricsCollector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InitMetricsCollector 初始化指标收集器
 | 
				
			||||||
 | 
					func InitMetricsCollector(config *OptimizedConfig) {
 | 
				
			||||||
 | 
						GlobalMetricsCollector = NewMetricsCollector(config)
 | 
				
			||||||
 | 
						GlobalMetricsCollector.Start()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetMetricsCollector 获取全局指标收集器
 | 
				
			||||||
 | 
					func GetMetricsCollector() *MetricsCollector {
 | 
				
			||||||
 | 
						return GlobalMetricsCollector
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1074
									
								
								services/binanceservice/reverse_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1074
									
								
								services/binanceservice/reverse_service.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1121
									
								
								services/binanceservice/reverse_service_optimized.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1121
									
								
								services/binanceservice/reverse_service_optimized.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										34
									
								
								services/binanceservice/reverse_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								services/binanceservice/reverse_service_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"go-admin/common/helper"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/bytedance/sonic"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/sdk"
 | 
				
			||||||
 | 
						"gorm.io/driver/mysql"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func initConfig() *gorm.DB {
 | 
				
			||||||
 | 
						dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
 | 
				
			||||||
 | 
						db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
 | 
				
			||||||
 | 
						sdk.Runtime.SetDb("default", db)
 | 
				
			||||||
 | 
						helper.InitDefaultRedis("127.0.0.1:6379", "", 2)
 | 
				
			||||||
 | 
						helper.InitLockRedisConn("127.0.0.1:6379", "", "2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return db
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func TestReverseOrder(t *testing.T) {
 | 
				
			||||||
 | 
						db := initConfig()
 | 
				
			||||||
 | 
						mapData := make(map[string]interface{})
 | 
				
			||||||
 | 
						content := `{"s":"ADAUSDT","c":"439301585084878848","S":"SELL","o":"LIMIT","f":"GTC","q":"8","p":"0.7781","ap":"0.7843","sp":"0","x":"TRADE","X":"FILLED","i":56468022240,"l":"8","z":"8","L":"0.7843","n":"0.0031372","N":"USDT","T":1753950025820,"t":1642816051,"b":"0","a":"0","m":false,"R":false,"wt":"CONTRACT_PRICE","ot":"LIMIT","ps":"SHORT","cp":false,"rp":"0","pP":false,"si":0,"ss":0,"V":"EXPIRE_MAKER","pm":"NONE","gtd":0}`
 | 
				
			||||||
 | 
						sonic.Unmarshal([]byte(content), &mapData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						service := ReverseService{}
 | 
				
			||||||
 | 
						service.Orm = db
 | 
				
			||||||
 | 
						service.Log = logger.NewHelper(logger.DefaultLogger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						service.ReverseOrder("c8ej2vxXzNUIlCjQCmU1iavK8LG78uZaXY3CIT4kz0PuhnXycg44HsVAbsqupHTw", mapData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -21,7 +21,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/bytedance/sonic"
 | 
						"github.com/bytedance/sonic"
 | 
				
			||||||
	"github.com/go-admin-team/go-admin-core/logger"
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
	log "github.com/go-admin-team/go-admin-core/logger"
 | 
					 | 
				
			||||||
	"github.com/shopspring/decimal"
 | 
						"github.com/shopspring/decimal"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -29,7 +28,7 @@ import (
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
订单回调
 | 
					订单回调
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
func ChangeSpotOrder(mapData map[string]interface{}) {
 | 
					func ChangeSpotOrder(mapData map[string]interface{}, apiKey string) {
 | 
				
			||||||
	// 检查订单号是否存在
 | 
						// 检查订单号是否存在
 | 
				
			||||||
	orderSn, ok := mapData["c"]
 | 
						orderSn, ok := mapData["c"]
 | 
				
			||||||
	originOrderSn := mapData["C"] //取消操作 代表原始订单号
 | 
						originOrderSn := mapData["C"] //取消操作 代表原始订单号
 | 
				
			||||||
@ -193,7 +192,7 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
 | 
				
			|||||||
	lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotReduceCallback, preOrder.ApiId, preOrder.Symbol), 120, 20, 100*time.Millisecond)
 | 
						lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotReduceCallback, preOrder.ApiId, preOrder.Symbol), 120, 20, 100*time.Millisecond)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ok, err := lock.AcquireWait(context.Background()); err != nil {
 | 
						if ok, err := lock.AcquireWait(context.Background()); err != nil {
 | 
				
			||||||
		log.Error("获取锁失败", err)
 | 
							logger.Error("获取锁失败", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	} else if ok {
 | 
						} else if ok {
 | 
				
			||||||
		defer lock.Release()
 | 
							defer lock.Release()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										457
									
								
								services/binanceservice/transaction_manager_optimized.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										457
									
								
								services/binanceservice/transaction_manager_optimized.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,457 @@
 | 
				
			|||||||
 | 
					package binanceservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-admin-team/go-admin-core/logger"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TransactionManager 事务管理器
 | 
				
			||||||
 | 
					type TransactionManager struct {
 | 
				
			||||||
 | 
						db      *gorm.DB
 | 
				
			||||||
 | 
						metrics *MetricsCollector
 | 
				
			||||||
 | 
						config  *OptimizedConfig
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TransactionConfig 事务配置
 | 
				
			||||||
 | 
					type TransactionConfig struct {
 | 
				
			||||||
 | 
						Timeout        time.Duration `json:"timeout"`
 | 
				
			||||||
 | 
						RetryAttempts  int           `json:"retry_attempts"`
 | 
				
			||||||
 | 
						RetryDelay     time.Duration `json:"retry_delay"`
 | 
				
			||||||
 | 
						IsolationLevel string        `json:"isolation_level"`
 | 
				
			||||||
 | 
						ReadOnly       bool          `json:"read_only"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TransactionContext 事务上下文
 | 
				
			||||||
 | 
					type TransactionContext struct {
 | 
				
			||||||
 | 
						Tx        *gorm.DB
 | 
				
			||||||
 | 
						StartTime time.Time
 | 
				
			||||||
 | 
						Config    TransactionConfig
 | 
				
			||||||
 | 
						Context   context.Context
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TransactionResult 事务结果
 | 
				
			||||||
 | 
					type TransactionResult struct {
 | 
				
			||||||
 | 
						Success   bool          `json:"success"`
 | 
				
			||||||
 | 
						Duration  time.Duration `json:"duration"`
 | 
				
			||||||
 | 
						Error     error         `json:"error"`
 | 
				
			||||||
 | 
						Retries   int           `json:"retries"`
 | 
				
			||||||
 | 
						Timestamp time.Time     `json:"timestamp"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewTransactionManager 创建事务管理器
 | 
				
			||||||
 | 
					func NewTransactionManager(db *gorm.DB, metrics *MetricsCollector, config *OptimizedConfig) *TransactionManager {
 | 
				
			||||||
 | 
						return &TransactionManager{
 | 
				
			||||||
 | 
							db:      db,
 | 
				
			||||||
 | 
							metrics: metrics,
 | 
				
			||||||
 | 
							config:  config,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithTransaction 在事务中执行操作
 | 
				
			||||||
 | 
					func (tm *TransactionManager) WithTransaction(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) *TransactionResult {
 | 
				
			||||||
 | 
						startTime := time.Now()
 | 
				
			||||||
 | 
						result := &TransactionResult{
 | 
				
			||||||
 | 
							Timestamp: startTime,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 设置默认配置
 | 
				
			||||||
 | 
						if config.Timeout == 0 {
 | 
				
			||||||
 | 
							config.Timeout = 30 * time.Second // 默认事务超时时间
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if config.RetryAttempts == 0 {
 | 
				
			||||||
 | 
							config.RetryAttempts = tm.config.RetryConfig.MaxRetries
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if config.RetryDelay == 0 {
 | 
				
			||||||
 | 
							config.RetryDelay = tm.config.RetryConfig.RetryDelay
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 执行事务(带重试)
 | 
				
			||||||
 | 
						for attempt := 1; attempt <= config.RetryAttempts; attempt++ {
 | 
				
			||||||
 | 
							err := tm.executeTransaction(ctx, config, operation)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								result.Success = true
 | 
				
			||||||
 | 
								result.Duration = time.Since(startTime)
 | 
				
			||||||
 | 
								result.Retries = attempt - 1
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							result.Error = err
 | 
				
			||||||
 | 
							result.Retries = attempt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 检查是否可重试
 | 
				
			||||||
 | 
							if !tm.isRetryableError(err) || attempt == config.RetryAttempts {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 等待重试
 | 
				
			||||||
 | 
							logger.Warnf("事务执行失败,第 %d 次重试: %v", attempt, err)
 | 
				
			||||||
 | 
							time.Sleep(config.RetryDelay * time.Duration(attempt))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.Duration = time.Since(startTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 记录指标
 | 
				
			||||||
 | 
						if tm.metrics != nil {
 | 
				
			||||||
 | 
							tm.metrics.RecordDbOperation(true, result.Duration, result.Error)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// executeTransaction 执行单次事务
 | 
				
			||||||
 | 
					func (tm *TransactionManager) executeTransaction(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) error {
 | 
				
			||||||
 | 
						// 设置事务超时
 | 
				
			||||||
 | 
						txCtx := ctx
 | 
				
			||||||
 | 
						if config.Timeout > 0 {
 | 
				
			||||||
 | 
							var cancel context.CancelFunc
 | 
				
			||||||
 | 
							txCtx, cancel = context.WithTimeout(ctx, config.Timeout)
 | 
				
			||||||
 | 
							defer cancel()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 开始事务
 | 
				
			||||||
 | 
						tx := tm.db.WithContext(txCtx).Begin()
 | 
				
			||||||
 | 
						if tx.Error != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("开始事务失败: %w", tx.Error)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 设置事务隔离级别
 | 
				
			||||||
 | 
						if config.IsolationLevel != "" {
 | 
				
			||||||
 | 
							if err := tx.Exec(fmt.Sprintf("SET TRANSACTION ISOLATION LEVEL %s", config.IsolationLevel)).Error; err != nil {
 | 
				
			||||||
 | 
								tx.Rollback()
 | 
				
			||||||
 | 
								return fmt.Errorf("设置事务隔离级别失败: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 设置只读模式
 | 
				
			||||||
 | 
						if config.ReadOnly {
 | 
				
			||||||
 | 
							if err := tx.Exec("SET TRANSACTION READ ONLY").Error; err != nil {
 | 
				
			||||||
 | 
								tx.Rollback()
 | 
				
			||||||
 | 
								return fmt.Errorf("设置只读事务失败: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 创建事务上下文
 | 
				
			||||||
 | 
						txContext := &TransactionContext{
 | 
				
			||||||
 | 
							Tx:        tx,
 | 
				
			||||||
 | 
							StartTime: time.Now(),
 | 
				
			||||||
 | 
							Config:    config,
 | 
				
			||||||
 | 
							Context:   txCtx,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 执行操作
 | 
				
			||||||
 | 
						err := operation(txContext)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// 回滚事务
 | 
				
			||||||
 | 
							if rollbackErr := tx.Rollback().Error; rollbackErr != nil {
 | 
				
			||||||
 | 
								logger.Errorf("事务回滚失败: %v", rollbackErr)
 | 
				
			||||||
 | 
								return fmt.Errorf("操作失败且回滚失败: %w, 回滚错误: %v", err, rollbackErr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return fmt.Errorf("事务操作失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 提交事务
 | 
				
			||||||
 | 
						if commitErr := tx.Commit().Error; commitErr != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("事务提交失败: %w", commitErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithOptimisticLock 使用乐观锁执行操作
 | 
				
			||||||
 | 
					func (tm *TransactionManager) WithOptimisticLock(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) *TransactionResult {
 | 
				
			||||||
 | 
						return tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
							// 在乐观锁模式下,我们需要在操作中检查版本号
 | 
				
			||||||
 | 
							return operation(txCtx)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveWithOptimisticLock 使用乐观锁保存数据
 | 
				
			||||||
 | 
					func (tm *TransactionManager) SaveWithOptimisticLock(ctx context.Context, model interface{}, condition string, args ...interface{}) error {
 | 
				
			||||||
 | 
						config := TransactionConfig{
 | 
				
			||||||
 | 
							Timeout:       30 * time.Second,
 | 
				
			||||||
 | 
							RetryAttempts: 3,
 | 
				
			||||||
 | 
							RetryDelay:    100 * time.Millisecond,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result := tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
							// 使用 WHERE 条件和版本号进行更新
 | 
				
			||||||
 | 
							result := txCtx.Tx.Where(condition, args...).Save(model)
 | 
				
			||||||
 | 
							if result.Error != nil {
 | 
				
			||||||
 | 
								return result.Error
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 检查是否有行被更新
 | 
				
			||||||
 | 
							if result.RowsAffected == 0 {
 | 
				
			||||||
 | 
								return fmt.Errorf("乐观锁冲突: 数据已被其他进程修改")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result.Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateWithOptimisticLock 使用乐观锁更新数据
 | 
				
			||||||
 | 
					func (tm *TransactionManager) UpdateWithOptimisticLock(ctx context.Context, model interface{}, updates map[string]interface{}, condition string, args ...interface{}) error {
 | 
				
			||||||
 | 
						config := TransactionConfig{
 | 
				
			||||||
 | 
							Timeout:       30 * time.Second,
 | 
				
			||||||
 | 
							RetryAttempts: 3,
 | 
				
			||||||
 | 
							RetryDelay:    100 * time.Millisecond,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result := tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
							// 增加版本号
 | 
				
			||||||
 | 
							if _, hasVersion := updates["version"]; !hasVersion {
 | 
				
			||||||
 | 
								updates["version"] = gorm.Expr("version + 1")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 执行更新
 | 
				
			||||||
 | 
							result := txCtx.Tx.Model(model).Where(condition, args...).Updates(updates)
 | 
				
			||||||
 | 
							if result.Error != nil {
 | 
				
			||||||
 | 
								return result.Error
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 检查是否有行被更新
 | 
				
			||||||
 | 
							if result.RowsAffected == 0 {
 | 
				
			||||||
 | 
								return fmt.Errorf("乐观锁冲突: 数据已被其他进程修改")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result.Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BatchOperation 批量操作
 | 
				
			||||||
 | 
					func (tm *TransactionManager) BatchOperation(ctx context.Context, config TransactionConfig, operations []func(*TransactionContext) error) *TransactionResult {
 | 
				
			||||||
 | 
						return tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
 | 
				
			||||||
 | 
							for i, operation := range operations {
 | 
				
			||||||
 | 
								if err := operation(txCtx); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("批量操作第 %d 步失败: %w", i+1, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateSavepoint 创建保存点
 | 
				
			||||||
 | 
					func (tm *TransactionManager) CreateSavepoint(txCtx *TransactionContext, name string) error {
 | 
				
			||||||
 | 
						return txCtx.Tx.Exec(fmt.Sprintf("SAVEPOINT %s", name)).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RollbackToSavepoint 回滚到保存点
 | 
				
			||||||
 | 
					func (tm *TransactionManager) RollbackToSavepoint(txCtx *TransactionContext, name string) error {
 | 
				
			||||||
 | 
						return txCtx.Tx.Exec(fmt.Sprintf("ROLLBACK TO SAVEPOINT %s", name)).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReleaseSavepoint 释放保存点
 | 
				
			||||||
 | 
					func (tm *TransactionManager) ReleaseSavepoint(txCtx *TransactionContext, name string) error {
 | 
				
			||||||
 | 
						return txCtx.Tx.Exec(fmt.Sprintf("RELEASE SAVEPOINT %s", name)).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isRetryableError 判断错误是否可重试
 | 
				
			||||||
 | 
					func (tm *TransactionManager) isRetryableError(err error) bool {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorMsg := err.Error()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// 可重试的错误类型
 | 
				
			||||||
 | 
						retryableErrors := []string{
 | 
				
			||||||
 | 
							"deadlock",
 | 
				
			||||||
 | 
							"lock wait timeout",
 | 
				
			||||||
 | 
							"connection",
 | 
				
			||||||
 | 
							"timeout",
 | 
				
			||||||
 | 
							"temporary",
 | 
				
			||||||
 | 
							"乐观锁冲突",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, retryableError := range retryableErrors {
 | 
				
			||||||
 | 
							if strings.Contains(errorMsg, retryableError) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTransactionStats 获取事务统计信息
 | 
				
			||||||
 | 
					func (tm *TransactionManager) GetTransactionStats() map[string]interface{} {
 | 
				
			||||||
 | 
						stats := make(map[string]interface{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 从指标收集器获取数据库相关统计
 | 
				
			||||||
 | 
						if tm.metrics != nil {
 | 
				
			||||||
 | 
							stats["total_transactions"] = tm.metrics.PerformanceMetrics.DbTransactions
 | 
				
			||||||
 | 
							stats["total_queries"] = tm.metrics.PerformanceMetrics.DbQueries
 | 
				
			||||||
 | 
							stats["total_errors"] = tm.metrics.PerformanceMetrics.DbErrors
 | 
				
			||||||
 | 
							stats["avg_query_time"] = tm.metrics.PerformanceMetrics.DbQueryTime
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return stats
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 预定义的事务配置
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetDefaultTransactionConfig 获取默认事务配置
 | 
				
			||||||
 | 
					func GetDefaultTransactionConfig() TransactionConfig {
 | 
				
			||||||
 | 
						return TransactionConfig{
 | 
				
			||||||
 | 
							Timeout:        30 * time.Second,
 | 
				
			||||||
 | 
							RetryAttempts:  3,
 | 
				
			||||||
 | 
							RetryDelay:     100 * time.Millisecond,
 | 
				
			||||||
 | 
							IsolationLevel: "READ COMMITTED",
 | 
				
			||||||
 | 
							ReadOnly:       false,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetReadOnlyTransactionConfig 获取只读事务配置
 | 
				
			||||||
 | 
					func GetReadOnlyTransactionConfig() TransactionConfig {
 | 
				
			||||||
 | 
						return TransactionConfig{
 | 
				
			||||||
 | 
							Timeout:        10 * time.Second,
 | 
				
			||||||
 | 
							RetryAttempts:  1,
 | 
				
			||||||
 | 
							RetryDelay:     0,
 | 
				
			||||||
 | 
							IsolationLevel: "READ COMMITTED",
 | 
				
			||||||
 | 
							ReadOnly:       true,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLongRunningTransactionConfig 获取长时间运行事务配置
 | 
				
			||||||
 | 
					func GetLongRunningTransactionConfig() TransactionConfig {
 | 
				
			||||||
 | 
						return TransactionConfig{
 | 
				
			||||||
 | 
							Timeout:        300 * time.Second, // 5分钟
 | 
				
			||||||
 | 
							RetryAttempts:  5,
 | 
				
			||||||
 | 
							RetryDelay:     500 * time.Millisecond,
 | 
				
			||||||
 | 
							IsolationLevel: "REPEATABLE READ",
 | 
				
			||||||
 | 
							ReadOnly:       false,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCriticalTransactionConfig 获取关键事务配置
 | 
				
			||||||
 | 
					func GetCriticalTransactionConfig() TransactionConfig {
 | 
				
			||||||
 | 
						return TransactionConfig{
 | 
				
			||||||
 | 
							Timeout:        60 * time.Second,
 | 
				
			||||||
 | 
							RetryAttempts:  5,
 | 
				
			||||||
 | 
							RetryDelay:     200 * time.Millisecond,
 | 
				
			||||||
 | 
							IsolationLevel: "SERIALIZABLE",
 | 
				
			||||||
 | 
							ReadOnly:       false,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 全局事务管理器实例
 | 
				
			||||||
 | 
					var GlobalTransactionManager *TransactionManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InitTransactionManager 初始化事务管理器
 | 
				
			||||||
 | 
					func InitTransactionManager(db *gorm.DB, metrics *MetricsCollector, config *OptimizedConfig) {
 | 
				
			||||||
 | 
						GlobalTransactionManager = NewTransactionManager(db, metrics, config)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTransactionManager 获取全局事务管理器
 | 
				
			||||||
 | 
					func GetTransactionManager() *TransactionManager {
 | 
				
			||||||
 | 
						return GlobalTransactionManager
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithDefaultTransaction 使用默认配置执行事务
 | 
				
			||||||
 | 
					func WithDefaultTransaction(ctx context.Context, operation func(*TransactionContext) error) error {
 | 
				
			||||||
 | 
						if GlobalTransactionManager == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("事务管理器未初始化")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := GetDefaultTransactionConfig()
 | 
				
			||||||
 | 
						result := GlobalTransactionManager.WithTransaction(ctx, config, operation)
 | 
				
			||||||
 | 
						return result.Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithReadOnlyTransaction 使用只读配置执行事务
 | 
				
			||||||
 | 
					func WithReadOnlyTransaction(ctx context.Context, operation func(*TransactionContext) error) error {
 | 
				
			||||||
 | 
						if GlobalTransactionManager == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("事务管理器未初始化")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := GetReadOnlyTransactionConfig()
 | 
				
			||||||
 | 
						result := GlobalTransactionManager.WithTransaction(ctx, config, operation)
 | 
				
			||||||
 | 
						return result.Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithCriticalTransaction 使用关键事务配置执行事务
 | 
				
			||||||
 | 
					func WithCriticalTransaction(ctx context.Context, operation func(*TransactionContext) error) error {
 | 
				
			||||||
 | 
						if GlobalTransactionManager == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("事务管理器未初始化")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := GetCriticalTransactionConfig()
 | 
				
			||||||
 | 
						result := GlobalTransactionManager.WithTransaction(ctx, config, operation)
 | 
				
			||||||
 | 
						return result.Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveWithOptimisticLockGlobal 全局乐观锁保存
 | 
				
			||||||
 | 
					func SaveWithOptimisticLockGlobal(ctx context.Context, model interface{}, condition string, args ...interface{}) error {
 | 
				
			||||||
 | 
						if GlobalTransactionManager == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("事务管理器未初始化")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return GlobalTransactionManager.SaveWithOptimisticLock(ctx, model, condition, args...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateWithOptimisticLockGlobal 全局乐观锁更新
 | 
				
			||||||
 | 
					func UpdateWithOptimisticLockGlobal(ctx context.Context, model interface{}, updates map[string]interface{}, condition string, args ...interface{}) error {
 | 
				
			||||||
 | 
						if GlobalTransactionManager == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("事务管理器未初始化")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return GlobalTransactionManager.UpdateWithOptimisticLock(ctx, model, updates, condition, args...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TransactionHelper 事务辅助函数集合
 | 
				
			||||||
 | 
					type TransactionHelper struct {
 | 
				
			||||||
 | 
						tm *TransactionManager
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewTransactionHelper 创建事务辅助器
 | 
				
			||||||
 | 
					func NewTransactionHelper(tm *TransactionManager) *TransactionHelper {
 | 
				
			||||||
 | 
						return &TransactionHelper{tm: tm}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SavePosition 保存持仓(带乐观锁)
 | 
				
			||||||
 | 
					func (th *TransactionHelper) SavePosition(ctx context.Context, position interface{}, userID, symbol string, version int) error {
 | 
				
			||||||
 | 
						condition := "user_id = ? AND symbol = ? AND version = ?"
 | 
				
			||||||
 | 
						return th.tm.SaveWithOptimisticLock(ctx, position, condition, userID, symbol, version)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveOrder 保存订单(带乐观锁)
 | 
				
			||||||
 | 
					func (th *TransactionHelper) SaveOrder(ctx context.Context, order interface{}, orderID string, version int) error {
 | 
				
			||||||
 | 
						condition := "order_id = ? AND version = ?"
 | 
				
			||||||
 | 
						return th.tm.SaveWithOptimisticLock(ctx, order, condition, orderID, version)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateOrderStatus 更新订单状态(带乐观锁)
 | 
				
			||||||
 | 
					func (th *TransactionHelper) UpdateOrderStatus(ctx context.Context, orderID, newStatus string, version int) error {
 | 
				
			||||||
 | 
						updates := map[string]interface{}{
 | 
				
			||||||
 | 
							"status":     newStatus,
 | 
				
			||||||
 | 
							"updated_at": time.Now(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						condition := "order_id = ? AND version = ?"
 | 
				
			||||||
 | 
						return th.tm.UpdateWithOptimisticLock(ctx, nil, updates, condition, orderID, version)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdatePositionQuantity 更新持仓数量(带乐观锁)
 | 
				
			||||||
 | 
					func (th *TransactionHelper) UpdatePositionQuantity(ctx context.Context, userID, symbol string, quantity float64, version int) error {
 | 
				
			||||||
 | 
						updates := map[string]interface{}{
 | 
				
			||||||
 | 
							"quantity":   quantity,
 | 
				
			||||||
 | 
							"updated_at": time.Now(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						condition := "user_id = ? AND symbol = ? AND version = ?"
 | 
				
			||||||
 | 
						return th.tm.UpdateWithOptimisticLock(ctx, nil, updates, condition, userID, symbol, version)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetGlobalTransactionHelper 获取全局事务辅助器
 | 
				
			||||||
 | 
					func GetGlobalTransactionHelper() *TransactionHelper {
 | 
				
			||||||
 | 
						if GlobalTransactionManager == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return NewTransactionHelper(GlobalTransactionManager)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								services/cacheservice/confg_server_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								services/cacheservice/confg_server_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package cacheservice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"go-admin/common/helper"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gorm.io/driver/mysql"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetReverseSetting(t *testing.T) {
 | 
				
			||||||
 | 
						dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
 | 
				
			||||||
 | 
						db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
 | 
				
			||||||
 | 
						helper.InitDefaultRedis("127.0.0.1:6379", "", 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setting, err := GetReverseSetting(db)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Log(setting)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -130,3 +130,22 @@ func ResetSystemSetting(db *gorm.DB) (models.LineSystemSetting, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return models.LineSystemSetting{}, nil
 | 
						return models.LineSystemSetting{}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取反单配置
 | 
				
			||||||
 | 
					func GetReverseSetting(db *gorm.DB) (models.LineReverseSetting, error) {
 | 
				
			||||||
 | 
						setting := models.LineReverseSetting{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						helper.DefaultRedis.HGetAsObject(rediskey.ReverseSetting, &setting)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if setting.ReverseOrderType == "" {
 | 
				
			||||||
 | 
							if err := db.Model(&setting).First(&setting).Error; err != nil {
 | 
				
			||||||
 | 
								logger.Error("获取反单配置失败", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := helper.DefaultRedis.SetHashWithTags(rediskey.ReverseSetting, &setting); err != nil {
 | 
				
			||||||
 | 
								logger.Error("redis添加反单配置失败", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return setting, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ import (
 | 
				
			|||||||
  - @msg 消息内容
 | 
					  - @msg 消息内容
 | 
				
			||||||
  - @listenType 订阅类型 0-现货 1-合约
 | 
					  - @listenType 订阅类型 0-现货 1-合约
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
func ReceiveListen(msg []byte, listenType int) (reconnect bool, err error) {
 | 
					func ReceiveListen(msg []byte, listenType int, apiKey string) (reconnect bool, err error) {
 | 
				
			||||||
	var dataMap map[string]interface{}
 | 
						var dataMap map[string]interface{}
 | 
				
			||||||
	err = sonic.Unmarshal(msg, &dataMap)
 | 
						err = sonic.Unmarshal(msg, &dataMap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,7 +52,7 @@ func ReceiveListen(msg []byte, listenType int) (reconnect bool, err error) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			utility.SafeGo(func() {
 | 
								utility.SafeGo(func() {
 | 
				
			||||||
				binanceservice.ChangeSpotOrder(mapData)
 | 
									binanceservice.ChangeSpotOrder(mapData, apiKey)
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			var data futuresdto.OrderTradeUpdate
 | 
								var data futuresdto.OrderTradeUpdate
 | 
				
			||||||
@ -64,7 +64,7 @@ func ReceiveListen(msg []byte, listenType int) (reconnect bool, err error) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			utility.SafeGo(func() {
 | 
								utility.SafeGo(func() {
 | 
				
			||||||
				binanceservice.ChangeFutureOrder(data.OrderDetails)
 | 
									binanceservice.ChangeFutureOrder(data.OrderDetails, apiKey)
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	//订单更新
 | 
						//订单更新
 | 
				
			||||||
@ -72,9 +72,9 @@ func ReceiveListen(msg []byte, listenType int) (reconnect bool, err error) {
 | 
				
			|||||||
		log.Info("executionReport 推送:", string(msg))
 | 
							log.Info("executionReport 推送:", string(msg))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if listenType == 0 { //现货
 | 
							if listenType == 0 { //现货
 | 
				
			||||||
			binanceservice.ChangeSpotOrder(dataMap)
 | 
								binanceservice.ChangeSpotOrder(dataMap, apiKey)
 | 
				
			||||||
		} else if listenType == 1 { //合约
 | 
							} else if listenType == 1 { //合约
 | 
				
			||||||
			binanceservice.ChangeFutureOrder(dataMap)
 | 
								binanceservice.ChangeFutureOrder(dataMap, apiKey)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			log.Error("executionReport 不支持的订阅类型", strconv.Itoa(listenType))
 | 
								log.Error("executionReport 不支持的订阅类型", strconv.Itoa(listenType))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -101,6 +101,7 @@ func ReceiveListen(msg []byte, listenType int) (reconnect bool, err error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	case "eventStreamTerminated":
 | 
						case "eventStreamTerminated":
 | 
				
			||||||
		log.Info("账户数据流被终止 type:", getWsTypeName(listenType))
 | 
							log.Info("账户数据流被终止 type:", getWsTypeName(listenType))
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		log.Info("未知事件 内容:", string(msg))
 | 
							log.Info("未知事件 内容:", string(msg))
 | 
				
			||||||
		log.Info("未知事件", event)
 | 
							log.Info("未知事件", event)
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/bytedance/sonic"
 | 
						"github.com/bytedance/sonic"
 | 
				
			||||||
@ -42,7 +43,9 @@ type BinanceWebSocketManager struct {
 | 
				
			|||||||
	isStopped    bool       // 标记 WebSocket 是否已主动停止
 | 
						isStopped    bool       // 标记 WebSocket 是否已主动停止
 | 
				
			||||||
	mu           sync.Mutex // 用于控制并发访问 isStopped
 | 
						mu           sync.Mutex // 用于控制并发访问 isStopped
 | 
				
			||||||
	cancelFunc   context.CancelFunc
 | 
						cancelFunc   context.CancelFunc
 | 
				
			||||||
	listenKey    string // 新增字段
 | 
						listenKey    string      // 新增字段
 | 
				
			||||||
 | 
						reconnecting atomic.Bool // 防止重复重连
 | 
				
			||||||
 | 
						ConnectTime  time.Time   // 当前连接建立时间
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 已有连接
 | 
					// 已有连接
 | 
				
			||||||
@ -72,6 +75,10 @@ func NewBinanceWebSocketManager(wsType int, apiKey, apiSecret, proxyType, proxyA
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (wm *BinanceWebSocketManager) GetKey() string {
 | 
				
			||||||
 | 
						return wm.apiKey
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (wm *BinanceWebSocketManager) Start() {
 | 
					func (wm *BinanceWebSocketManager) Start() {
 | 
				
			||||||
	utility.SafeGo(wm.run)
 | 
						utility.SafeGo(wm.run)
 | 
				
			||||||
	// wm.run()
 | 
						// wm.run()
 | 
				
			||||||
@ -91,14 +98,33 @@ func (wm *BinanceWebSocketManager) Restart(apiKey, apiSecret, proxyType, proxyAd
 | 
				
			|||||||
		wm.isStopped = false
 | 
							wm.isStopped = false
 | 
				
			||||||
		utility.SafeGo(wm.run)
 | 
							utility.SafeGo(wm.run)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		wm.reconnect <- struct{}{}
 | 
							log.Warnf("调用restart")
 | 
				
			||||||
 | 
							wm.triggerReconnect(true)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return wm
 | 
						return wm
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 触发重连
 | 
				
			||||||
 | 
					func (wm *BinanceWebSocketManager) triggerReconnect(force bool) {
 | 
				
			||||||
 | 
						if force {
 | 
				
			||||||
 | 
							wm.reconnecting.Store(false) // 强制重置标志位
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if wm.reconnecting.CompareAndSwap(false, true) {
 | 
				
			||||||
 | 
							log.Warnf("准备重连 key: %s wsType: %v", wm.apiKey, wm.wsType)
 | 
				
			||||||
 | 
							// 发送信号触发重连协程
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case wm.reconnect <- struct{}{}:
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								// 防止阻塞,如果通道满了就跳过
 | 
				
			||||||
 | 
								log.Debugf("reconnect 信号已存在,跳过 key:%s", wm.apiKey)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
func Restart(wm *BinanceWebSocketManager) {
 | 
					func Restart(wm *BinanceWebSocketManager) {
 | 
				
			||||||
	wm.reconnect <- struct{}{}
 | 
						log.Warnf("调用restart")
 | 
				
			||||||
 | 
						wm.triggerReconnect(true)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
func (wm *BinanceWebSocketManager) run() {
 | 
					func (wm *BinanceWebSocketManager) run() {
 | 
				
			||||||
	ctx, cancel := context.WithCancel(context.Background())
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
@ -201,6 +227,8 @@ func (wm *BinanceWebSocketManager) connect(ctx context.Context) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 连接成功,更新连接时间
 | 
				
			||||||
 | 
						wm.ConnectTime = time.Now()
 | 
				
			||||||
	log.Info(fmt.Sprintf("已连接到 Binance %s WebSocket【%s】 key:%s", getWsTypeName(wm.wsType), wm.apiKey, listenKey))
 | 
						log.Info(fmt.Sprintf("已连接到 Binance %s WebSocket【%s】 key:%s", getWsTypeName(wm.wsType), wm.apiKey, listenKey))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Ping处理
 | 
						// Ping处理
 | 
				
			||||||
@ -222,10 +250,88 @@ func (wm *BinanceWebSocketManager) connect(ctx context.Context) error {
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// utility.SafeGoParam(wm.restartConnect, ctx)
 | 
					 | 
				
			||||||
	utility.SafeGo(func() { wm.startListenKeyRenewal2(ctx) })
 | 
						utility.SafeGo(func() { wm.startListenKeyRenewal2(ctx) })
 | 
				
			||||||
	utility.SafeGo(func() { wm.readMessages(ctx) })
 | 
						utility.SafeGo(func() { wm.readMessages(ctx) })
 | 
				
			||||||
	utility.SafeGo(func() { wm.handleReconnect(ctx) })
 | 
						utility.SafeGo(func() { wm.handleReconnect(ctx) })
 | 
				
			||||||
 | 
						utility.SafeGo(func() { wm.startPingLoop(ctx) })
 | 
				
			||||||
 | 
						// utility.SafeGo(func() { wm.startDeadCheck(ctx) })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReplaceConnection 创建新连接并关闭旧连接,实现无缝连接替换
 | 
				
			||||||
 | 
					func (wm *BinanceWebSocketManager) ReplaceConnection() error {
 | 
				
			||||||
 | 
						wm.mu.Lock()
 | 
				
			||||||
 | 
						if wm.isStopped {
 | 
				
			||||||
 | 
							wm.mu.Unlock()
 | 
				
			||||||
 | 
							return errors.New("WebSocket 已停止")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						oldCtxCancel := wm.cancelFunc
 | 
				
			||||||
 | 
						oldConn := wm.ws
 | 
				
			||||||
 | 
						wm.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Infof("🔄 正在替换连接: %s", wm.apiKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 步骤 1:先获取新的 listenKey 和连接
 | 
				
			||||||
 | 
						newListenKey, err := wm.getListenKey()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("获取新 listenKey 失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dialer, err := wm.getDialer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						newURL := fmt.Sprintf("%s/%s", wm.url, newListenKey)
 | 
				
			||||||
 | 
						newConn, _, err := dialer.Dial(newURL, nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("新连接 Dial 失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 步骤 2:创建新的上下文并启动协程
 | 
				
			||||||
 | 
						newCtx, newCancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 设置 ping handler
 | 
				
			||||||
 | 
						newConn.SetPingHandler(func(appData string) error {
 | 
				
			||||||
 | 
							log.Infof("收到 Ping(新连接) key:%s msg:%s", wm.apiKey, appData)
 | 
				
			||||||
 | 
							for x := 0; x < 5; x++ {
 | 
				
			||||||
 | 
								if err := newConn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(10*time.Second)); err != nil {
 | 
				
			||||||
 | 
									log.Errorf("Pong 失败 %d 次 err:%v", x, err)
 | 
				
			||||||
 | 
									time.Sleep(time.Second)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							setLastTime(wm)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 步骤 3:安全切换连接
 | 
				
			||||||
 | 
						wm.mu.Lock()
 | 
				
			||||||
 | 
						wm.ws = newConn
 | 
				
			||||||
 | 
						wm.listenKey = newListenKey
 | 
				
			||||||
 | 
						wm.ConnectTime = time.Now()
 | 
				
			||||||
 | 
						wm.cancelFunc = newCancel
 | 
				
			||||||
 | 
						wm.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Infof("✅ 替换连接成功: %s listenKey: %s", wm.apiKey, newListenKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 步骤 4:启动新连接协程
 | 
				
			||||||
 | 
						go wm.startListenKeyRenewal2(newCtx)
 | 
				
			||||||
 | 
						go wm.readMessages(newCtx)
 | 
				
			||||||
 | 
						go wm.handleReconnect(newCtx)
 | 
				
			||||||
 | 
						go wm.startPingLoop(newCtx)
 | 
				
			||||||
 | 
						// go wm.startDeadCheck(newCtx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 步骤 5:关闭旧连接、取消旧协程
 | 
				
			||||||
 | 
						if oldCtxCancel != nil {
 | 
				
			||||||
 | 
							oldCtxCancel()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if oldConn != nil {
 | 
				
			||||||
 | 
							_ = oldConn.Close()
 | 
				
			||||||
 | 
							log.Infof("🔒 旧连接已关闭: %s", wm.apiKey)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -251,6 +357,7 @@ func setLastTime(wm *BinanceWebSocketManager) {
 | 
				
			|||||||
	if val != "" {
 | 
						if val != "" {
 | 
				
			||||||
		helper.DefaultRedis.SetString(subKey, val)
 | 
							helper.DefaultRedis.SetString(subKey, val)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (wm *BinanceWebSocketManager) getDialer() (*websocket.Dialer, error) {
 | 
					func (wm *BinanceWebSocketManager) getDialer() (*websocket.Dialer, error) {
 | 
				
			||||||
@ -357,7 +464,8 @@ func (wm *BinanceWebSocketManager) readMessages(ctx context.Context) {
 | 
				
			|||||||
			_, msg, err := wm.ws.ReadMessage()
 | 
								_, msg, err := wm.ws.ReadMessage()
 | 
				
			||||||
			if err != nil && strings.Contains(err.Error(), "websocket: close") {
 | 
								if err != nil && strings.Contains(err.Error(), "websocket: close") {
 | 
				
			||||||
				if !wm.isStopped {
 | 
									if !wm.isStopped {
 | 
				
			||||||
					wm.reconnect <- struct{}{}
 | 
										log.Error("收到关闭消息", err.Error())
 | 
				
			||||||
 | 
										wm.triggerReconnect(false)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				log.Error("websocket 关闭")
 | 
									log.Error("websocket 关闭")
 | 
				
			||||||
@ -375,11 +483,13 @@ func (wm *BinanceWebSocketManager) readMessages(ctx context.Context) {
 | 
				
			|||||||
func (wm *BinanceWebSocketManager) handleOrderUpdate(msg []byte) {
 | 
					func (wm *BinanceWebSocketManager) handleOrderUpdate(msg []byte) {
 | 
				
			||||||
	setLastTime(wm)
 | 
						setLastTime(wm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if reconnect, _ := ReceiveListen(msg, wm.wsType); reconnect {
 | 
						if reconnect, _ := ReceiveListen(msg, wm.wsType, wm.apiKey); reconnect {
 | 
				
			||||||
		wm.reconnect <- struct{}{}
 | 
							log.Errorf("收到重连请求")
 | 
				
			||||||
 | 
							wm.triggerReconnect(false)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Stop 安全停止 WebSocket
 | 
				
			||||||
func (wm *BinanceWebSocketManager) Stop() {
 | 
					func (wm *BinanceWebSocketManager) Stop() {
 | 
				
			||||||
	wm.mu.Lock()
 | 
						wm.mu.Lock()
 | 
				
			||||||
	defer wm.mu.Unlock()
 | 
						defer wm.mu.Unlock()
 | 
				
			||||||
@ -387,9 +497,8 @@ func (wm *BinanceWebSocketManager) Stop() {
 | 
				
			|||||||
	if wm.isStopped {
 | 
						if wm.isStopped {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	wm.isStopped = true
 | 
						wm.isStopped = true
 | 
				
			||||||
	// 关闭 stopChannel(确保已经关闭,避免 panic)
 | 
					
 | 
				
			||||||
	select {
 | 
						select {
 | 
				
			||||||
	case <-wm.stopChannel:
 | 
						case <-wm.stopChannel:
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
@ -398,107 +507,182 @@ func (wm *BinanceWebSocketManager) Stop() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if wm.cancelFunc != nil {
 | 
						if wm.cancelFunc != nil {
 | 
				
			||||||
		wm.cancelFunc()
 | 
							wm.cancelFunc()
 | 
				
			||||||
 | 
							wm.cancelFunc = nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if wm.ws != nil {
 | 
						if wm.ws != nil {
 | 
				
			||||||
		if err := wm.ws.Close(); err != nil {
 | 
							if err := wm.ws.Close(); err != nil {
 | 
				
			||||||
			log.Error(fmt.Sprintf("key【%s】close失败", wm.apiKey), err)
 | 
								log.Errorf("WebSocket Close 错误 key:%s err:%v", wm.apiKey, err)
 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			log.Info(fmt.Sprintf("key【%s】close", wm.apiKey))
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							wm.ws = nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// **重新创建 stopChannel,避免 Restart() 时无效**
 | 
						log.Infof("WebSocket 已完全停止 key:%s", wm.apiKey)
 | 
				
			||||||
	wm.stopChannel = make(chan struct{})
 | 
						wm.stopChannel = make(chan struct{}, 10)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 重连机制
 | 
					// handleReconnect 使用指数退避并保持永不退出
 | 
				
			||||||
func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) {
 | 
					func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) {
 | 
				
			||||||
	maxRetries := 5 // 最大重试次数
 | 
						const maxRetries = 100
 | 
				
			||||||
 | 
						baseDelay := time.Second * 2
 | 
				
			||||||
	retryCount := 0
 | 
						retryCount := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								log.Infof("handleReconnect context done: %s", wm.apiKey)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case <-wm.reconnect:
 | 
							case <-wm.reconnect:
 | 
				
			||||||
 | 
								wm.mu.Lock()
 | 
				
			||||||
			if wm.isStopped {
 | 
								if wm.isStopped {
 | 
				
			||||||
 | 
									wm.mu.Unlock()
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								wm.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			log.Warn("WebSocket 连接断开,尝试重连...")
 | 
								log.Warnf("WebSocket 连接断开,准备重连 key:%s", wm.apiKey)
 | 
				
			||||||
 | 
					 | 
				
			||||||
			if wm.ws != nil {
 | 
					 | 
				
			||||||
				wm.ws.Close()
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// 取消旧的上下文
 | 
					 | 
				
			||||||
			if wm.cancelFunc != nil {
 | 
					 | 
				
			||||||
				wm.cancelFunc()
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for {
 | 
								for {
 | 
				
			||||||
 | 
									wm.mu.Lock()
 | 
				
			||||||
 | 
									if wm.ws != nil {
 | 
				
			||||||
 | 
										_ = wm.ws.Close()
 | 
				
			||||||
 | 
										wm.ws = nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if wm.cancelFunc != nil {
 | 
				
			||||||
 | 
										wm.cancelFunc()
 | 
				
			||||||
 | 
										wm.cancelFunc = nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									wm.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				newCtx, cancel := context.WithCancel(context.Background())
 | 
									newCtx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
				wm.cancelFunc = cancel // 更新 cancelFunc
 | 
									wm.mu.Lock()
 | 
				
			||||||
 | 
									wm.cancelFunc = cancel
 | 
				
			||||||
 | 
									wm.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if err := wm.connect(newCtx); err != nil {
 | 
									if err := wm.connect(newCtx); err != nil {
 | 
				
			||||||
					log.Errorf("重连失败: %v", err)
 | 
										log.Errorf("🔌 重连失败(%d/%d)key:%s,err: %v", retryCount+1, maxRetries, wm.apiKey, err)
 | 
				
			||||||
					cancel()
 | 
										cancel()
 | 
				
			||||||
					retryCount++
 | 
										retryCount++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if retryCount >= maxRetries {
 | 
										if retryCount >= maxRetries {
 | 
				
			||||||
						log.Error("重连失败次数过多,退出重连逻辑")
 | 
											log.Errorf("❌ 重连失败次数过多,停止重连逻辑 key:%s", wm.apiKey)
 | 
				
			||||||
 | 
											wm.reconnecting.Store(false)
 | 
				
			||||||
						return
 | 
											return
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					time.Sleep(5 * time.Second)
 | 
										delay := baseDelay * time.Duration(1<<retryCount)
 | 
				
			||||||
 | 
										if delay > time.Minute*5 {
 | 
				
			||||||
 | 
											delay = time.Minute * 5
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										log.Warnf("等待 %v 后重试...", delay)
 | 
				
			||||||
 | 
										time.Sleep(delay)
 | 
				
			||||||
					continue
 | 
										continue
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									log.Infof("✅ 重连成功 key:%s", wm.apiKey)
 | 
				
			||||||
 | 
									retryCount = 0
 | 
				
			||||||
 | 
									wm.reconnecting.Store(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// ✅ 重连成功后开启假死检测
 | 
				
			||||||
 | 
									utility.SafeGo(func() { wm.startDeadCheck(newCtx) })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// startDeadCheck 替代 Start 中的定时器,绑定连接生命周期
 | 
				
			||||||
 | 
					func (wm *BinanceWebSocketManager) startDeadCheck(ctx context.Context) {
 | 
				
			||||||
 | 
						ticker := time.NewTicker(1 * time.Minute)
 | 
				
			||||||
 | 
						defer ticker.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								if wm.isStopped {
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								wm.DeadCheck()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 假死检测
 | 
				
			||||||
 | 
					func (wm *BinanceWebSocketManager) DeadCheck() {
 | 
				
			||||||
 | 
						subKey := fmt.Sprintf(global.USER_SUBSCRIBE, wm.apiKey)
 | 
				
			||||||
 | 
						val, _ := helper.DefaultRedis.GetString(subKey)
 | 
				
			||||||
 | 
						if val == "" {
 | 
				
			||||||
 | 
							log.Warnf("没有订阅信息,无法进行假死检测")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var data binancedto.UserSubscribeState
 | 
				
			||||||
 | 
						_ = sonic.Unmarshal([]byte(val), &data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var lastTime *time.Time
 | 
				
			||||||
 | 
						if wm.wsType == 0 {
 | 
				
			||||||
 | 
							lastTime = data.SpotLastTime
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							lastTime = data.FuturesLastTime
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 定义最大静默时间(超出视为假死)
 | 
				
			||||||
 | 
						var timeout time.Duration
 | 
				
			||||||
 | 
						if wm.wsType == 0 {
 | 
				
			||||||
 | 
							timeout = 40 * time.Second // Spot 每 20s ping,40s 足够
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							timeout = 6 * time.Minute // Futures 每 3 分钟 ping
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if lastTime != nil && time.Since(*lastTime) > timeout {
 | 
				
			||||||
 | 
							log.Warnf("检测到假死连接 key:%s type:%v, 距离上次通信: %v, 触发重连", wm.apiKey, wm.wsType, time.Since(*lastTime))
 | 
				
			||||||
 | 
							wm.triggerReconnect(true)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 主动心跳发送机制
 | 
				
			||||||
 | 
					func (wm *BinanceWebSocketManager) startPingLoop(ctx context.Context) {
 | 
				
			||||||
 | 
						ticker := time.NewTicker(1 * time.Minute)
 | 
				
			||||||
 | 
						defer ticker.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								if wm.isStopped {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								err := wm.ws.WriteMessage(websocket.PingMessage, []byte("ping"))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error("主动 Ping Binance 失败:", err)
 | 
				
			||||||
 | 
									wm.triggerReconnect(false)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 定期删除listenkey 并重启ws
 | 
					// 定期删除listenkey 并重启ws
 | 
				
			||||||
func (wm *BinanceWebSocketManager) startListenKeyRenewal(ctx context.Context, listenKey string) {
 | 
					// func (wm *BinanceWebSocketManager) startListenKeyRenewal(ctx context.Context, listenKey string) {
 | 
				
			||||||
	time.Sleep(30 * time.Minute)
 | 
					// 	time.Sleep(30 * time.Minute)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	select {
 | 
					// 	select {
 | 
				
			||||||
	case <-ctx.Done():
 | 
					// 	case <-ctx.Done():
 | 
				
			||||||
		return
 | 
					// 		return
 | 
				
			||||||
	default:
 | 
					// 	default:
 | 
				
			||||||
		if err := wm.deleteListenKey(listenKey); err != nil {
 | 
					// 		if err := wm.deleteListenKey(listenKey); err != nil {
 | 
				
			||||||
			log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err)
 | 
					// 			log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err)
 | 
				
			||||||
		} else {
 | 
					// 		} else {
 | 
				
			||||||
			log.Debug("Successfully delete listenKey")
 | 
					// 			log.Debug("Successfully delete listenKey")
 | 
				
			||||||
			wm.reconnect <- struct{}{}
 | 
					// 			wm.triggerReconnect()
 | 
				
			||||||
		}
 | 
					// 		}
 | 
				
			||||||
	}
 | 
					// 	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ticker := time.NewTicker(5 * time.Minute)
 | 
					// }
 | 
				
			||||||
	// defer ticker.Stop()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// for {
 | 
					 | 
				
			||||||
	// 	select {
 | 
					 | 
				
			||||||
	// 	case <-ticker.C:
 | 
					 | 
				
			||||||
	// 		if wm.isStopped {
 | 
					 | 
				
			||||||
	// 			return
 | 
					 | 
				
			||||||
	// 		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 		if err := wm.deleteListenKey(listenKey); err != nil {
 | 
					 | 
				
			||||||
	// 			log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err)
 | 
					 | 
				
			||||||
	// 		} else {
 | 
					 | 
				
			||||||
	// 			log.Debug("Successfully delete listenKey")
 | 
					 | 
				
			||||||
	// 			wm.reconnect <- struct{}{}
 | 
					 | 
				
			||||||
	// 			return
 | 
					 | 
				
			||||||
	// 		}
 | 
					 | 
				
			||||||
	// 	case <-ctx.Done():
 | 
					 | 
				
			||||||
	// 		return
 | 
					 | 
				
			||||||
	// 	}
 | 
					 | 
				
			||||||
	// }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 定时续期
 | 
					// 定时续期
 | 
				
			||||||
func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) {
 | 
					func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) {
 | 
				
			||||||
@ -527,49 +711,34 @@ func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) {
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
删除listenkey
 | 
					删除listenkey
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
func (wm *BinanceWebSocketManager) deleteListenKey(listenKey string) error {
 | 
					// func (wm *BinanceWebSocketManager) deleteListenKey(listenKey string) error {
 | 
				
			||||||
	client, err := wm.createBinanceClient()
 | 
					// 	client, err := wm.createBinanceClient()
 | 
				
			||||||
	if err != nil {
 | 
					// 	if err != nil {
 | 
				
			||||||
		return err
 | 
					// 		return err
 | 
				
			||||||
	}
 | 
					// 	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var resp []byte
 | 
					// 	var resp []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch wm.wsType {
 | 
					// 	switch wm.wsType {
 | 
				
			||||||
	case 0:
 | 
					// 	case 0:
 | 
				
			||||||
		path := fmt.Sprintf("/api/v3/userDataStream")
 | 
					// 		path := fmt.Sprintf("/api/v3/userDataStream")
 | 
				
			||||||
		params := map[string]interface{}{
 | 
					// 		params := map[string]interface{}{
 | 
				
			||||||
			"listenKey": listenKey,
 | 
					// 			"listenKey": listenKey,
 | 
				
			||||||
		}
 | 
					// 		}
 | 
				
			||||||
		resp, _, err = client.SendSpotRequestByKey(path, "DELETE", params)
 | 
					// 		resp, _, err = client.SendSpotRequestByKey(path, "DELETE", params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp)))
 | 
					// 		log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp)))
 | 
				
			||||||
	case 1:
 | 
					// 	case 1:
 | 
				
			||||||
		resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "DELETE", nil)
 | 
					// 		resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "DELETE", nil)
 | 
				
			||||||
		log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp)))
 | 
					// 		log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp)))
 | 
				
			||||||
	default:
 | 
					// 	default:
 | 
				
			||||||
		return errors.New("unknown ws type")
 | 
					// 		return errors.New("unknown ws type")
 | 
				
			||||||
	}
 | 
					// 	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return err
 | 
					// 	return err
 | 
				
			||||||
}
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (wm *BinanceWebSocketManager) renewListenKey(listenKey string) error {
 | 
					func (wm *BinanceWebSocketManager) renewListenKey(listenKey string) error {
 | 
				
			||||||
	// payloadParam := map[string]interface{}{
 | 
					 | 
				
			||||||
	// 	"listenKey": listenKey,
 | 
					 | 
				
			||||||
	// 	"apiKey":    wm.apiKey,
 | 
					 | 
				
			||||||
	// }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// params := map[string]interface{}{
 | 
					 | 
				
			||||||
	// 	"id":     getUUID(),
 | 
					 | 
				
			||||||
	// 	"method": "userDataStream.ping",
 | 
					 | 
				
			||||||
	// 	"params": payloadParam,
 | 
					 | 
				
			||||||
	// }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// if err := wm.ws.WriteJSON(params); err != nil {
 | 
					 | 
				
			||||||
	// 	return err
 | 
					 | 
				
			||||||
	// }
 | 
					 | 
				
			||||||
	// wm.ws.WriteJSON()
 | 
					 | 
				
			||||||
	client, err := wm.createBinanceClient()
 | 
						client, err := wm.createBinanceClient()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user