1、反向下单 暂时提交
This commit is contained in:
		| @ -305,3 +305,26 @@ func (e LineApiUser) GetUnBindReverseApiUser(c *gin.Context) { | |||||||
|  |  | ||||||
| 	e.OK(datas, "查询成功") | 	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, "操作成功") | ||||||
|  | } | ||||||
|  | |||||||
| @ -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). | ||||||
|  | |||||||
| @ -14,22 +14,27 @@ import ( | |||||||
| 	"go-admin/common/actions" | 	"go-admin/common/actions" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type LineReverseOrderExt struct { | type LineReversePosition struct { | ||||||
| 	api.Api | 	api.Api | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetPage 获取反单拓展信息列表 | // GetPage 获取反单管理-仓位列表 | ||||||
| // @Summary 获取反单拓展信息列表 | // @Summary 获取反单管理-仓位列表 | ||||||
| // @Description 获取反单拓展信息列表 | // @Description 获取反单管理-仓位列表 | ||||||
| // @Tags 反单拓展信息 | // @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 pageSize query int false "页条数" | ||||||
| // @Param pageIndex query int false "页码" | // @Param pageIndex query int false "页码" | ||||||
| // @Success 200 {object} response.Response{data=response.Page{list=[]models.LineReverseOrderExt}} "{"code": 200, "data": [...]}" | // @Success 200 {object} response.Response{data=response.Page{list=[]models.LineReversePosition}} "{"code": 200, "data": [...]}" | ||||||
| // @Router /api/v1/line-reverse-order-ext [get] | // @Router /api/v1/line-reverse-position [get] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseOrderExt) GetPage(c *gin.Context) { | func (e LineReversePosition) GetPage(c *gin.Context) { | ||||||
|     req := dto.LineReverseOrderExtGetPageReq{} |     req := dto.LineReversePositionGetPageReq{} | ||||||
|     s := service.LineReverseOrderExt{} |     s := service.LineReversePosition{} | ||||||
|     err := e.MakeContext(c). |     err := e.MakeContext(c). | ||||||
|         MakeOrm(). |         MakeOrm(). | ||||||
|         Bind(&req). |         Bind(&req). | ||||||
| @ -42,29 +47,29 @@ func (e LineReverseOrderExt) GetPage(c *gin.Context) { | |||||||
|    	} |    	} | ||||||
| 
 | 
 | ||||||
| 	p := actions.GetPermissionFromContext(c) | 	p := actions.GetPermissionFromContext(c) | ||||||
| 	list := make([]models.LineReverseOrderExt, 0) | 	list := make([]models.LineReversePosition, 0) | ||||||
| 	var count int64 | 	var count int64 | ||||||
| 
 | 
 | ||||||
| 	err = s.GetPage(&req, p, &list, &count) | 	err = s.GetPage(&req, p, &list, &count) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("获取反单拓展信息失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("获取反单管理-仓位失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return |         return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Get 获取反单拓展信息 | // Get 获取反单管理-仓位 | ||||||
| // @Summary 获取反单拓展信息 | // @Summary 获取反单管理-仓位 | ||||||
| // @Description 获取反单拓展信息 | // @Description 获取反单管理-仓位 | ||||||
| // @Tags 反单拓展信息 | // @Tags 反单管理-仓位 | ||||||
| // @Param id path int false "id" | // @Param id path int false "id" | ||||||
| // @Success 200 {object} response.Response{data=models.LineReverseOrderExt} "{"code": 200, "data": [...]}" | // @Success 200 {object} response.Response{data=models.LineReversePosition} "{"code": 200, "data": [...]}" | ||||||
| // @Router /api/v1/line-reverse-order-ext/{id} [get] | // @Router /api/v1/line-reverse-position/{id} [get] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseOrderExt) Get(c *gin.Context) { | func (e LineReversePosition) Get(c *gin.Context) { | ||||||
| 	req := dto.LineReverseOrderExtGetReq{} | 	req := dto.LineReversePositionGetReq{} | ||||||
| 	s := service.LineReverseOrderExt{} | 	s := service.LineReversePosition{} | ||||||
|     err := e.MakeContext(c). |     err := e.MakeContext(c). | ||||||
| 		MakeOrm(). | 		MakeOrm(). | ||||||
| 		Bind(&req). | 		Bind(&req). | ||||||
| @ -75,31 +80,31 @@ func (e LineReverseOrderExt) Get(c *gin.Context) { | |||||||
| 		e.Error(500, err, err.Error()) | 		e.Error(500, err, err.Error()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	var object models.LineReverseOrderExt | 	var object models.LineReversePosition | ||||||
| 
 | 
 | ||||||
| 	p := actions.GetPermissionFromContext(c) | 	p := actions.GetPermissionFromContext(c) | ||||||
| 	err = s.Get(&req, p, &object) | 	err = s.Get(&req, p, &object) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("获取反单拓展信息失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("获取反单管理-仓位失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return |         return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	e.OK( object, "查询成功") | 	e.OK( object, "查询成功") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Insert 创建反单拓展信息 | // Insert 创建反单管理-仓位 | ||||||
| // @Summary 创建反单拓展信息 | // @Summary 创建反单管理-仓位 | ||||||
| // @Description 创建反单拓展信息 | // @Description 创建反单管理-仓位 | ||||||
| // @Tags 反单拓展信息 | // @Tags 反单管理-仓位 | ||||||
| // @Accept application/json | // @Accept application/json | ||||||
| // @Product application/json | // @Product application/json | ||||||
| // @Param data body dto.LineReverseOrderExtInsertReq true "data" | // @Param data body dto.LineReversePositionInsertReq true "data" | ||||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}" | // @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}" | ||||||
| // @Router /api/v1/line-reverse-order-ext [post] | // @Router /api/v1/line-reverse-position [post] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseOrderExt) Insert(c *gin.Context) { | func (e LineReversePosition) Insert(c *gin.Context) { | ||||||
|     req := dto.LineReverseOrderExtInsertReq{} |     req := dto.LineReversePositionInsertReq{} | ||||||
|     s := service.LineReverseOrderExt{} |     s := service.LineReversePosition{} | ||||||
|     err := e.MakeContext(c). |     err := e.MakeContext(c). | ||||||
|         MakeOrm(). |         MakeOrm(). | ||||||
|         Bind(&req). |         Bind(&req). | ||||||
| @ -115,27 +120,27 @@ func (e LineReverseOrderExt) Insert(c *gin.Context) { | |||||||
| 
 | 
 | ||||||
| 	err = s.Insert(&req) | 	err = s.Insert(&req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("创建反单拓展信息失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("创建反单管理-仓位失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return |         return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	e.OK(req.GetId(), "创建成功") | 	e.OK(req.GetId(), "创建成功") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Update 修改反单拓展信息 | // Update 修改反单管理-仓位 | ||||||
| // @Summary 修改反单拓展信息 | // @Summary 修改反单管理-仓位 | ||||||
| // @Description 修改反单拓展信息 | // @Description 修改反单管理-仓位 | ||||||
| // @Tags 反单拓展信息 | // @Tags 反单管理-仓位 | ||||||
| // @Accept application/json | // @Accept application/json | ||||||
| // @Product application/json | // @Product application/json | ||||||
| // @Param id path int true "id" | // @Param id path int true "id" | ||||||
| // @Param data body dto.LineReverseOrderExtUpdateReq true "body" | // @Param data body dto.LineReversePositionUpdateReq true "body" | ||||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}" | // @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}" | ||||||
| // @Router /api/v1/line-reverse-order-ext/{id} [put] | // @Router /api/v1/line-reverse-position/{id} [put] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseOrderExt) Update(c *gin.Context) { | func (e LineReversePosition) Update(c *gin.Context) { | ||||||
|     req := dto.LineReverseOrderExtUpdateReq{} |     req := dto.LineReversePositionUpdateReq{} | ||||||
|     s := service.LineReverseOrderExt{} |     s := service.LineReversePosition{} | ||||||
|     err := e.MakeContext(c). |     err := e.MakeContext(c). | ||||||
|         MakeOrm(). |         MakeOrm(). | ||||||
|         Bind(&req). |         Bind(&req). | ||||||
| @ -151,23 +156,23 @@ func (e LineReverseOrderExt) Update(c *gin.Context) { | |||||||
| 
 | 
 | ||||||
| 	err = s.Update(&req, p) | 	err = s.Update(&req, p) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("修改反单拓展信息失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("修改反单管理-仓位失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return |         return | ||||||
| 	} | 	} | ||||||
| 	e.OK( req.GetId(), "修改成功") | 	e.OK( req.GetId(), "修改成功") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Delete 删除反单拓展信息 | // Delete 删除反单管理-仓位 | ||||||
| // @Summary 删除反单拓展信息 | // @Summary 删除反单管理-仓位 | ||||||
| // @Description 删除反单拓展信息 | // @Description 删除反单管理-仓位 | ||||||
| // @Tags 反单拓展信息 | // @Tags 反单管理-仓位 | ||||||
| // @Param data body dto.LineReverseOrderExtDeleteReq true "body" | // @Param data body dto.LineReversePositionDeleteReq true "body" | ||||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}" | // @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}" | ||||||
| // @Router /api/v1/line-reverse-order-ext [delete] | // @Router /api/v1/line-reverse-position [delete] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseOrderExt) Delete(c *gin.Context) { | func (e LineReversePosition) Delete(c *gin.Context) { | ||||||
|     s := service.LineReverseOrderExt{} |     s := service.LineReversePosition{} | ||||||
|     req := dto.LineReverseOrderExtDeleteReq{} |     req := dto.LineReversePositionDeleteReq{} | ||||||
|     err := e.MakeContext(c). |     err := e.MakeContext(c). | ||||||
|         MakeOrm(). |         MakeOrm(). | ||||||
|         Bind(&req). |         Bind(&req). | ||||||
| @ -184,7 +189,7 @@ func (e LineReverseOrderExt) Delete(c *gin.Context) { | |||||||
| 
 | 
 | ||||||
| 	err = s.Remove(&req, p) | 	err = s.Remove(&req, p) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("删除反单拓展信息失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("删除反单管理-仓位失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return |         return | ||||||
| 	} | 	} | ||||||
| 	e.OK( req.GetId(), "删除成功") | 	e.OK( req.GetId(), "删除成功") | ||||||
| @ -1,7 +1,7 @@ | |||||||
| package apis | 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" | ||||||
| @ -28,18 +28,18 @@ type LineReverseSetting struct { | |||||||
| // @Router /api/v1/line-reverse-setting [get] | // @Router /api/v1/line-reverse-setting [get] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseSetting) GetPage(c *gin.Context) { | func (e LineReverseSetting) GetPage(c *gin.Context) { | ||||||
|     req := dto.LineReverseSettingGetPageReq{} | 	req := dto.LineReverseSettingGetPageReq{} | ||||||
|     s := service.LineReverseSetting{} | 	s := service.LineReverseSetting{} | ||||||
|     err := e.MakeContext(c). | 	err := e.MakeContext(c). | ||||||
|         MakeOrm(). | 		MakeOrm(). | ||||||
|         Bind(&req). | 		Bind(&req). | ||||||
|         MakeService(&s.Service). | 		MakeService(&s.Service). | ||||||
|         Errors | 		Errors | ||||||
|    	if err != nil { | 	if err != nil { | ||||||
|    		e.Logger.Error(err) | 		e.Logger.Error(err) | ||||||
|    		e.Error(500, err, err.Error()) | 		e.Error(500, err, err.Error()) | ||||||
|    		return | 		return | ||||||
|    	} | 	} | ||||||
|  |  | ||||||
| 	p := actions.GetPermissionFromContext(c) | 	p := actions.GetPermissionFromContext(c) | ||||||
| 	list := make([]models.LineReverseSetting, 0) | 	list := make([]models.LineReverseSetting, 0) | ||||||
| @ -48,7 +48,7 @@ func (e LineReverseSetting) GetPage(c *gin.Context) { | |||||||
| 	err = s.GetPage(&req, p, &list, &count) | 	err = s.GetPage(&req, p, &list, &count) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("获取反单下单配置失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("获取反单下单配置失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | ||||||
| @ -65,7 +65,7 @@ func (e LineReverseSetting) GetPage(c *gin.Context) { | |||||||
| func (e LineReverseSetting) Get(c *gin.Context) { | func (e LineReverseSetting) Get(c *gin.Context) { | ||||||
| 	req := dto.LineReverseSettingGetReq{} | 	req := dto.LineReverseSettingGetReq{} | ||||||
| 	s := service.LineReverseSetting{} | 	s := service.LineReverseSetting{} | ||||||
|     err := e.MakeContext(c). | 	err := e.MakeContext(c). | ||||||
| 		MakeOrm(). | 		MakeOrm(). | ||||||
| 		Bind(&req). | 		Bind(&req). | ||||||
| 		MakeService(&s.Service). | 		MakeService(&s.Service). | ||||||
| @ -81,10 +81,10 @@ func (e LineReverseSetting) Get(c *gin.Context) { | |||||||
| 	err = s.Get(&req, p, &object) | 	err = s.Get(&req, p, &object) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("获取反单下单配置失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("获取反单下单配置失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	e.OK( object, "查询成功") | 	e.OK(object, "查询成功") | ||||||
| } | } | ||||||
|  |  | ||||||
| // Insert 创建反单下单配置 | // Insert 创建反单下单配置 | ||||||
| @ -98,25 +98,25 @@ func (e LineReverseSetting) Get(c *gin.Context) { | |||||||
| // @Router /api/v1/line-reverse-setting [post] | // @Router /api/v1/line-reverse-setting [post] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseSetting) Insert(c *gin.Context) { | func (e LineReverseSetting) Insert(c *gin.Context) { | ||||||
|     req := dto.LineReverseSettingInsertReq{} | 	req := dto.LineReverseSettingInsertReq{} | ||||||
|     s := service.LineReverseSetting{} | 	s := service.LineReverseSetting{} | ||||||
|     err := e.MakeContext(c). | 	err := e.MakeContext(c). | ||||||
|         MakeOrm(). | 		MakeOrm(). | ||||||
|         Bind(&req). | 		Bind(&req). | ||||||
|         MakeService(&s.Service). | 		MakeService(&s.Service). | ||||||
|         Errors | 		Errors | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         e.Logger.Error(err) | 		e.Logger.Error(err) | ||||||
|         e.Error(500, err, err.Error()) | 		e.Error(500, err, err.Error()) | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
| 	// 设置创建人 | 	// 设置创建人 | ||||||
| 	req.SetCreateBy(user.GetUserId(c)) | 	req.SetCreateBy(user.GetUserId(c)) | ||||||
|  |  | ||||||
| 	err = s.Insert(&req) | 	err = s.Insert(&req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("创建反单下单配置失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("创建反单下单配置失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	e.OK(req.GetId(), "创建成功") | 	e.OK(req.GetId(), "创建成功") | ||||||
| @ -134,58 +134,25 @@ func (e LineReverseSetting) Insert(c *gin.Context) { | |||||||
| // @Router /api/v1/line-reverse-setting/{id} [put] | // @Router /api/v1/line-reverse-setting/{id} [put] | ||||||
| // @Security Bearer | // @Security Bearer | ||||||
| func (e LineReverseSetting) Update(c *gin.Context) { | func (e LineReverseSetting) Update(c *gin.Context) { | ||||||
|     req := dto.LineReverseSettingUpdateReq{} | 	req := dto.LineReverseSettingUpdateReq{} | ||||||
|     s := service.LineReverseSetting{} | 	s := service.LineReverseSetting{} | ||||||
|     err := e.MakeContext(c). | 	err := e.MakeContext(c). | ||||||
|         MakeOrm(). | 		MakeOrm(). | ||||||
|         Bind(&req). | 		Bind(&req). | ||||||
|         MakeService(&s.Service). | 		MakeService(&s.Service). | ||||||
|         Errors | 		Errors | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         e.Logger.Error(err) | 		e.Logger.Error(err) | ||||||
|         e.Error(500, err, err.Error()) | 		e.Error(500, err, err.Error()) | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
| 	req.SetUpdateBy(user.GetUserId(c)) | 	req.SetUpdateBy(user.GetUserId(c)) | ||||||
| 	p := actions.GetPermissionFromContext(c) | 	p := actions.GetPermissionFromContext(c) | ||||||
|  |  | ||||||
| 	err = s.Update(&req, p) | 	err = s.Update(&req, p) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Error(500, err, fmt.Sprintf("修改反单下单配置失败,\r\n失败信息 %s", err.Error())) | 		e.Error(500, err, fmt.Sprintf("修改反单下单配置失败,\r\n失败信息 %s", err.Error())) | ||||||
|         return | 		return | ||||||
| 	} | 	} | ||||||
| 	e.OK( req.GetId(), "修改成功") | 	e.OK(req.GetId(), "修改成功") | ||||||
| } |  | ||||||
|  |  | ||||||
| // Delete 删除反单下单配置 |  | ||||||
| // @Summary 删除反单下单配置 |  | ||||||
| // @Description 删除反单下单配置 |  | ||||||
| // @Tags 反单下单配置 |  | ||||||
| // @Param data body dto.LineReverseSettingDeleteReq true "body" |  | ||||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}" |  | ||||||
| // @Router /api/v1/line-reverse-setting [delete] |  | ||||||
| // @Security Bearer |  | ||||||
| func (e LineReverseSetting) Delete(c *gin.Context) { |  | ||||||
|     s := service.LineReverseSetting{} |  | ||||||
|     req := dto.LineReverseSettingDeleteReq{} |  | ||||||
|     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(), "删除成功") |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,36 +1,44 @@ | |||||||
| package models | package models | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|     "time" | 	"time" | ||||||
|  |  | ||||||
| 	"go-admin/common/models" | 	"go-admin/common/models" | ||||||
|  |  | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type LineReverseOrder struct { | type LineReverseOrder struct { | ||||||
|     models.Model | 	models.Model | ||||||
|      |  | ||||||
|     PId int `json:"pId" gorm:"type:bigint;comment:主单id"`  | 	ApiId    int `json:"apiId" gorm:"type:bigint;comment:api id"` | ||||||
|     OrderSn string `json:"orderSn" gorm:"type:varchar(50);comment:订单号"`  | 	Category int `json:"category" gorm:"type:tinyint;comment:分类 0-原始订单 1-反单"` | ||||||
|     OrderId string `json:"orderId" gorm:"type:varchar(50);comment:币安订单号"`  |  | ||||||
|     FollowOrderSn string `json:"followOrderSn" gorm:"type:varchar(50);comment:跟随币安订单号"`  | 	OrderSn       string          `json:"orderSn" gorm:"type:varchar(50);comment:订单号 0-主单 1-止盈 2-止损"` | ||||||
|     Symbol string `json:"symbol" gorm:"type:varchar(20);comment:交易对"`  | 	OrderId       string          `json:"orderId" gorm:"type:varchar(50);comment:币安订单号"` | ||||||
|     OrderType int `json:"orderType" gorm:"type:tinyint;comment:订单类型 0-主单 1-止损单 2-加仓 3-减仓"`  | 	FollowOrderSn string          `json:"followOrderSn" gorm:"type:varchar(50);comment:跟随币安订单号"` | ||||||
|     BuyPrice decimal.Decimal `json:"buyPrice" gorm:"type:decimal(18,8);comment:购买金额"`  | 	Symbol        string          `json:"symbol" gorm:"type:varchar(20);comment:交易对"` | ||||||
|     Price decimal.Decimal `json:"price" gorm:"type:decimal(18,8);comment:委托价格"`  | 	Type          string          `json:"type" gorm:"type:varchar(20);comment:交易类型"` | ||||||
|     PriceU decimal.Decimal `json:"priceU" gorm:"type:decimal(18,8);comment:委托价格(U)"`  | 	OrderType     int             `json:"orderType" gorm:"type:tinyint;comment:订单类型 0-主单 1-止盈  2-止损 3-减仓单 4-平仓单"` | ||||||
|     FinalPrice decimal.Decimal `json:"finalPrice" gorm:"type:decimal(18,8);comment:实际成交价"`  | 	BuyPrice      decimal.Decimal `json:"buyPrice" gorm:"type:decimal(18,8);comment:购买金额"` | ||||||
|     PositionSide string `json:"positionSide" gorm:"type:varchar(10);comment:持仓方向 LONG-多 SHORT-空"`  | 	Price         decimal.Decimal `json:"price" gorm:"type:decimal(18,8);comment:委托价格"` | ||||||
|     Side string `json:"side" gorm:"type:varchar(10);comment:买卖方向 SELL-卖 BUY-买"`  | 	TotalNum      decimal.Decimal `json:"totalNum" gorm:"type:decimal(18,8);comment:总成交数量"` | ||||||
|     SignPrice decimal.Decimal `json:"signPrice" gorm:"type:decimal(18,8);comment:行情价"`  | 	PriceU        decimal.Decimal `json:"priceU" gorm:"type:decimal(18,8);comment:委托价格(U)"` | ||||||
|     TriggerTime time.Time `json:"triggerTime" gorm:"type:datetime;comment:触发时间"`  | 	FinalPrice    decimal.Decimal `json:"finalPrice" gorm:"type:decimal(18,8);comment:实际成交价"` | ||||||
|     Status int `json:"status" gorm:"type:tinyint;comment:状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"`  | 	PositionSide  string          `json:"positionSide" gorm:"type:varchar(10);comment:持仓方向 LONG-多 SHORT-空"` | ||||||
|     models.ModelTime | 	Side          string          `json:"side" gorm:"type:varchar(10);comment:买卖方向 SELL-卖 BUY-买"` | ||||||
|     models.ControlBy | 	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 { | func (LineReverseOrder) TableName() string { | ||||||
|     return "line_reverse_order" | 	return "line_reverse_order" | ||||||
| } | } | ||||||
|  |  | ||||||
| func (e *LineReverseOrder) Generate() models.ActiveRecord { | func (e *LineReverseOrder) Generate() models.ActiveRecord { | ||||||
| @ -40,4 +48,4 @@ func (e *LineReverseOrder) Generate() models.ActiveRecord { | |||||||
|  |  | ||||||
| func (e *LineReverseOrder) GetId() interface{} { | func (e *LineReverseOrder) GetId() interface{} { | ||||||
| 	return e.Id | 	return e.Id | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,30 +0,0 @@ | |||||||
| package models |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
|  |  | ||||||
| 	"go-admin/common/models" |  | ||||||
|  |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type LineReverseOrderExt struct { |  | ||||||
|     models.Model |  | ||||||
|      |  | ||||||
|     ReverseOrderId int `json:"reverseOrderId" gorm:"type:bigint;comment:下反单id"`  |  | ||||||
|     IsAddPosition int `json:"isAddPosition" gorm:"type:tinyint;comment:加仓状态 1-已加仓 2-未加仓"`  |  | ||||||
|     IsReduce int `json:"isReduce" gorm:"type:tinyint;comment:减仓状态 1-已减仓 2-未减仓"`  |  | ||||||
|     models.ModelTime |  | ||||||
|     models.ControlBy |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (LineReverseOrderExt) TableName() string { |  | ||||||
|     return "line_reverse_order_ext" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (e *LineReverseOrderExt) Generate() models.ActiveRecord { |  | ||||||
| 	o := *e |  | ||||||
| 	return &o |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (e *LineReverseOrderExt) GetId() interface{} { |  | ||||||
| 	return e.Id |  | ||||||
| } |  | ||||||
							
								
								
									
										39
									
								
								app/admin/models/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/admin/models/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"go-admin/common/models" | ||||||
|  |  | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type LineReversePosition struct { | ||||||
|  | 	models.Model | ||||||
|  |  | ||||||
|  | 	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-已平仓"` | ||||||
|  |  | ||||||
|  | 	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 | ||||||
|  | } | ||||||
| @ -9,9 +9,10 @@ import ( | |||||||
| type LineReverseSetting struct { | type LineReverseSetting struct { | ||||||
| 	models.Model | 	models.Model | ||||||
|  |  | ||||||
| 	ReverseOrderType    string          `json:"reverseOrderType" gorm:"type:varchar(10);comment:反单下单类型 LIMIT-限价 MARKET-市价"` | 	ReverseOrderType    string          `json:"reverseOrderType" redis:"reverseOrderType" gorm:"type:varchar(10);comment:反单下单类型 LIMIT-限价 MARKET-市价"` | ||||||
| 	ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" gorm:"type:decimal(10,2);comment:溢价百分比"` | 	ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" redis:"reversePremiumRatio" gorm:"type:decimal(10,2);comment:溢价百分比"` | ||||||
| 	StopLossRatio       decimal.Decimal `json:"stopLossRatio" 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.ModelTime | ||||||
| 	models.ControlBy | 	models.ControlBy | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,5 +29,7 @@ func registerLineApiUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMi | |||||||
| 		r.POST("getMainUser", api.GetMainUser) //获取获取主账号的用户 | 		r.POST("getMainUser", api.GetMainUser) //获取获取主账号的用户 | ||||||
|  |  | ||||||
| 		r.GET("unbind-reverse", api.GetUnBindReverseApiUser) //获取未绑定下反单用户 | 		r.GET("unbind-reverse", api.GetUnBindReverseApiUser) //获取未绑定下反单用户 | ||||||
|  |  | ||||||
|  | 		r.GET("reverse-options", api.GetReverseApiOptions) //获取可用反单api用户 | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,13 +10,13 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
| 	routerCheckRole = append(routerCheckRole, registerLineReverseOrderExtRouter) | 	routerCheckRole = append(routerCheckRole, registerLineReversePositionRouter) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // registerLineReverseOrderExtRouter | // registerLineReversePositionRouter | ||||||
| func registerLineReverseOrderExtRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { | func registerLineReversePositionRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { | ||||||
| 	api := apis.LineReverseOrderExt{} | 	api := apis.LineReversePosition{} | ||||||
| 	r := v1.Group("/line-reverse-order-ext").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) | 	r := v1.Group("/line-reverse-position").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) | ||||||
| 	{ | 	{ | ||||||
| 		r.GET("", actions.PermissionAction(), api.GetPage) | 		r.GET("", actions.PermissionAction(), api.GetPage) | ||||||
| 		r.GET("/:id", actions.PermissionAction(), api.Get) | 		r.GET("/:id", actions.PermissionAction(), api.Get) | ||||||
| @ -5,8 +5,8 @@ import ( | |||||||
| 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | ||||||
|  |  | ||||||
| 	"go-admin/app/admin/apis" | 	"go-admin/app/admin/apis" | ||||||
| 	"go-admin/common/middleware" |  | ||||||
| 	"go-admin/common/actions" | 	"go-admin/common/actions" | ||||||
|  | 	"go-admin/common/middleware" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| @ -22,6 +22,6 @@ func registerLineReverseSettingRouter(v1 *gin.RouterGroup, authMiddleware *jwt.G | |||||||
| 		r.GET("/:id", actions.PermissionAction(), api.Get) | 		r.GET("/:id", actions.PermissionAction(), api.Get) | ||||||
| 		r.POST("", api.Insert) | 		r.POST("", api.Insert) | ||||||
| 		r.PUT("/:id", actions.PermissionAction(), api.Update) | 		r.PUT("/:id", actions.PermissionAction(), api.Update) | ||||||
| 		r.DELETE("", api.Delete) | 		// r.DELETE("", api.Delete) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -219,3 +219,8 @@ type UnBindReverseResp struct { | |||||||
| 	Disabled bool   `json:"disabled"` | 	Disabled bool   `json:"disabled"` | ||||||
| 	ApiName  string `json:"apiName"` | 	ApiName  string `json:"apiName"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type GetReverseApiOptionsReq struct { | ||||||
|  | 	Id    int `json:"apiId" form:"id"` | ||||||
|  | 	ApiId int `json:"apiId" form:"apiId"` | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,48 +1,49 @@ | |||||||
| package dto | package dto | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|     "time" | 	"time" | ||||||
|  |  | ||||||
| 	"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 LineReverseOrderGetPageReq struct { | type LineReverseOrderGetPageReq struct { | ||||||
| 	dto.Pagination     `search:"-"` | 	dto.Pagination `search:"-"` | ||||||
|     OrderSn string `form:"orderSn"  search:"type:contains;column:order_sn;table:line_reverse_order" comment:"订单号"` | 	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:"币安订单号"` | 	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:"跟随币安订单号"` | 	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-减仓"` | 	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-空"` | 	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-买"` | 	Side           string `form:"side"  search:"type:exact;column:side;table:line_reverse_order" comment:"买卖方向 SELL-卖 BUY-买"` | ||||||
|     Status int `form:"status"  search:"type:exact;column:status;table:line_reverse_order" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | 	Status         int    `form:"status"  search:"type:exact;column:status;table:line_reverse_order" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | ||||||
|     LineReverseOrderOrder | 	LineReverseOrderOrder | ||||||
| } | } | ||||||
|  |  | ||||||
| type LineReverseOrderOrder struct { | type LineReverseOrderOrder struct { | ||||||
|     Id string `form:"idOrder"  search:"type:order;column:id;table:line_reverse_order"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	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"` | 	UpdateBy      string `form:"updateByOrder"  search:"type:order;column:update_by;table:line_reverse_order"` | ||||||
|      |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *LineReverseOrderGetPageReq) GetNeedSearch() interface{} { | func (m *LineReverseOrderGetPageReq) GetNeedSearch() interface{} { | ||||||
| @ -50,45 +51,43 @@ func (m *LineReverseOrderGetPageReq) GetNeedSearch() interface{} { | |||||||
| } | } | ||||||
|  |  | ||||||
| type LineReverseOrderInsertReq struct { | type LineReverseOrderInsertReq struct { | ||||||
|     Id int `json:"-" comment:"主键id"` // 主键id | 	Id            int             `json:"-" comment:"主键id"` // 主键id | ||||||
|     PId int `json:"pId" comment:"主单id"` | 	PId           int             `json:"pId" comment:"主单id"` | ||||||
|     OrderSn string `json:"orderSn" comment:"订单号"` | 	OrderSn       string          `json:"orderSn" comment:"订单号"` | ||||||
|     OrderId string `json:"orderId" comment:"币安订单号"` | 	OrderId       string          `json:"orderId" comment:"币安订单号"` | ||||||
|     FollowOrderSn string `json:"followOrderSn" comment:"跟随币安订单号"` | 	FollowOrderSn string          `json:"followOrderSn" comment:"跟随币安订单号"` | ||||||
|     Symbol string `json:"symbol" comment:"交易对"` | 	Symbol        string          `json:"symbol" comment:"交易对"` | ||||||
|     OrderType int `json:"orderType" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"` | 	OrderType     int             `json:"orderType" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"` | ||||||
|     BuyPrice decimal.Decimal `json:"buyPrice" comment:"购买金额"` | 	BuyPrice      decimal.Decimal `json:"buyPrice" comment:"购买金额"` | ||||||
|     Price decimal.Decimal `json:"price" comment:"委托价格"` | 	Price         decimal.Decimal `json:"price" comment:"委托价格"` | ||||||
|     PriceU decimal.Decimal `json:"priceU" comment:"委托价格(U)"` | 	PriceU        decimal.Decimal `json:"priceU" comment:"委托价格(U)"` | ||||||
|     FinalPrice decimal.Decimal `json:"finalPrice" comment:"实际成交价"` | 	FinalPrice    decimal.Decimal `json:"finalPrice" comment:"实际成交价"` | ||||||
|     PositionSide string `json:"positionSide" comment:"持仓方向 LONG-多 SHORT-空"` | 	PositionSide  string          `json:"positionSide" comment:"持仓方向 LONG-多 SHORT-空"` | ||||||
|     Side string `json:"side" comment:"买卖方向 SELL-卖 BUY-买"` | 	Side          string          `json:"side" comment:"买卖方向 SELL-卖 BUY-买"` | ||||||
|     SignPrice decimal.Decimal `json:"signPrice" comment:"行情价"` | 	SignPrice     decimal.Decimal `json:"signPrice" comment:"行情价"` | ||||||
|     TriggerTime time.Time `json:"triggerTime" comment:"触发时间"` | 	TriggerTime   time.Time       `json:"triggerTime" comment:"触发时间"` | ||||||
|     Status int `json:"status" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | 	Status        int             `json:"status" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | ||||||
|     common.ControlBy | 	common.ControlBy | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *LineReverseOrderInsertReq) Generate(model *models.LineReverseOrder)  { | func (s *LineReverseOrderInsertReq) Generate(model *models.LineReverseOrder) { | ||||||
|     if s.Id == 0 { | 	if s.Id == 0 { | ||||||
|         model.Model = common.Model{ Id: s.Id } | 		model.Model = common.Model{Id: s.Id} | ||||||
|     } | 	} | ||||||
|     model.PId = s.PId | 	model.OrderSn = s.OrderSn | ||||||
|     model.OrderSn = s.OrderSn | 	model.OrderId = s.OrderId | ||||||
|     model.OrderId = s.OrderId | 	model.FollowOrderSn = s.FollowOrderSn | ||||||
|     model.FollowOrderSn = s.FollowOrderSn | 	model.Symbol = s.Symbol | ||||||
|     model.Symbol = s.Symbol | 	model.OrderType = s.OrderType | ||||||
|     model.OrderType = s.OrderType | 	model.BuyPrice = s.BuyPrice | ||||||
|     model.BuyPrice = s.BuyPrice | 	model.Price = s.Price | ||||||
|     model.Price = s.Price | 	model.PriceU = s.PriceU | ||||||
|     model.PriceU = s.PriceU | 	model.FinalPrice = s.FinalPrice | ||||||
|     model.FinalPrice = s.FinalPrice | 	model.PositionSide = s.PositionSide | ||||||
|     model.PositionSide = s.PositionSide | 	model.Side = s.Side | ||||||
|     model.Side = s.Side | 	model.SignPrice = s.SignPrice | ||||||
|     model.SignPrice = s.SignPrice | 	model.Status = s.Status | ||||||
|     model.TriggerTime = s.TriggerTime | 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||||
|     model.Status = s.Status |  | ||||||
|     model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *LineReverseOrderInsertReq) GetId() interface{} { | func (s *LineReverseOrderInsertReq) GetId() interface{} { | ||||||
| @ -96,45 +95,44 @@ func (s *LineReverseOrderInsertReq) GetId() interface{} { | |||||||
| } | } | ||||||
|  |  | ||||||
| type LineReverseOrderUpdateReq struct { | type LineReverseOrderUpdateReq struct { | ||||||
|     Id int `uri:"id" comment:"主键id"` // 主键id | 	Id            int             `uri:"id" comment:"主键id"` // 主键id | ||||||
|     PId int `json:"pId" comment:"主单id"` | 	PId           int             `json:"pId" comment:"主单id"` | ||||||
|     OrderSn string `json:"orderSn" comment:"订单号"` | 	OrderSn       string          `json:"orderSn" comment:"订单号"` | ||||||
|     OrderId string `json:"orderId" comment:"币安订单号"` | 	OrderId       string          `json:"orderId" comment:"币安订单号"` | ||||||
|     FollowOrderSn string `json:"followOrderSn" comment:"跟随币安订单号"` | 	FollowOrderSn string          `json:"followOrderSn" comment:"跟随币安订单号"` | ||||||
|     Symbol string `json:"symbol" comment:"交易对"` | 	Symbol        string          `json:"symbol" comment:"交易对"` | ||||||
|     OrderType int `json:"orderType" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"` | 	OrderType     int             `json:"orderType" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"` | ||||||
|     BuyPrice decimal.Decimal `json:"buyPrice" comment:"购买金额"` | 	BuyPrice      decimal.Decimal `json:"buyPrice" comment:"购买金额"` | ||||||
|     Price decimal.Decimal `json:"price" comment:"委托价格"` | 	Price         decimal.Decimal `json:"price" comment:"委托价格"` | ||||||
|     PriceU decimal.Decimal `json:"priceU" comment:"委托价格(U)"` | 	PriceU        decimal.Decimal `json:"priceU" comment:"委托价格(U)"` | ||||||
|     FinalPrice decimal.Decimal `json:"finalPrice" comment:"实际成交价"` | 	FinalPrice    decimal.Decimal `json:"finalPrice" comment:"实际成交价"` | ||||||
|     PositionSide string `json:"positionSide" comment:"持仓方向 LONG-多 SHORT-空"` | 	PositionSide  string          `json:"positionSide" comment:"持仓方向 LONG-多 SHORT-空"` | ||||||
|     Side string `json:"side" comment:"买卖方向 SELL-卖 BUY-买"` | 	Side          string          `json:"side" comment:"买卖方向 SELL-卖 BUY-买"` | ||||||
|     SignPrice decimal.Decimal `json:"signPrice" comment:"行情价"` | 	SignPrice     decimal.Decimal `json:"signPrice" comment:"行情价"` | ||||||
|     TriggerTime time.Time `json:"triggerTime" comment:"触发时间"` | 	TriggerTime   time.Time       `json:"triggerTime" comment:"触发时间"` | ||||||
|     Status int `json:"status" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | 	Status        int             `json:"status" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | ||||||
|     common.ControlBy | 	common.ControlBy | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *LineReverseOrderUpdateReq) Generate(model *models.LineReverseOrder)  { | func (s *LineReverseOrderUpdateReq) Generate(model *models.LineReverseOrder) { | ||||||
|     if s.Id == 0 { | 	if s.Id == 0 { | ||||||
|         model.Model = common.Model{ Id: s.Id } | 		model.Model = common.Model{Id: s.Id} | ||||||
|     } | 	} | ||||||
|     model.PId = s.PId |  | ||||||
|     model.OrderSn = s.OrderSn | 	model.OrderSn = s.OrderSn | ||||||
|     model.OrderId = s.OrderId | 	model.OrderId = s.OrderId | ||||||
|     model.FollowOrderSn = s.FollowOrderSn | 	model.FollowOrderSn = s.FollowOrderSn | ||||||
|     model.Symbol = s.Symbol | 	model.Symbol = s.Symbol | ||||||
|     model.OrderType = s.OrderType | 	model.OrderType = s.OrderType | ||||||
|     model.BuyPrice = s.BuyPrice | 	model.BuyPrice = s.BuyPrice | ||||||
|     model.Price = s.Price | 	model.Price = s.Price | ||||||
|     model.PriceU = s.PriceU | 	model.PriceU = s.PriceU | ||||||
|     model.FinalPrice = s.FinalPrice | 	model.FinalPrice = s.FinalPrice | ||||||
|     model.PositionSide = s.PositionSide | 	model.PositionSide = s.PositionSide | ||||||
|     model.Side = s.Side | 	model.Side = s.Side | ||||||
|     model.SignPrice = s.SignPrice | 	model.SignPrice = s.SignPrice | ||||||
|     model.TriggerTime = s.TriggerTime | 	model.Status = s.Status | ||||||
|     model.Status = s.Status | 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||||
|     model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *LineReverseOrderUpdateReq) GetId() interface{} { | func (s *LineReverseOrderUpdateReq) GetId() interface{} { | ||||||
| @ -143,8 +141,9 @@ func (s *LineReverseOrderUpdateReq) GetId() interface{} { | |||||||
|  |  | ||||||
| // LineReverseOrderGetReq 功能获取请求参数 | // LineReverseOrderGetReq 功能获取请求参数 | ||||||
| type LineReverseOrderGetReq struct { | type LineReverseOrderGetReq struct { | ||||||
|      Id int `uri:"id"` | 	Id int `uri:"id"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *LineReverseOrderGetReq) GetId() interface{} { | func (s *LineReverseOrderGetReq) GetId() interface{} { | ||||||
| 	return s.Id | 	return s.Id | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,91 +0,0 @@ | |||||||
| package dto |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
|  |  | ||||||
| 	"go-admin/app/admin/models" |  | ||||||
| 	"go-admin/common/dto" |  | ||||||
| 	common "go-admin/common/models" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type LineReverseOrderExtGetPageReq struct { |  | ||||||
| 	dto.Pagination     `search:"-"` |  | ||||||
|     LineReverseOrderExtOrder |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type LineReverseOrderExtOrder struct { |  | ||||||
|     Id string `form:"idOrder"  search:"type:order;column:id;table:line_reverse_order_ext"` |  | ||||||
|     ReverseOrderId string `form:"reverseOrderIdOrder"  search:"type:order;column:reverse_order_id;table:line_reverse_order_ext"` |  | ||||||
|     IsAddPosition string `form:"isAddPositionOrder"  search:"type:order;column:is_add_position;table:line_reverse_order_ext"` |  | ||||||
|     IsReduce string `form:"isReduceOrder"  search:"type:order;column:is_reduce;table:line_reverse_order_ext"` |  | ||||||
|     CreatedAt string `form:"createdAtOrder"  search:"type:order;column:created_at;table:line_reverse_order_ext"` |  | ||||||
|     UpdatedAt string `form:"updatedAtOrder"  search:"type:order;column:updated_at;table:line_reverse_order_ext"` |  | ||||||
|     DeletedAt string `form:"deletedAtOrder"  search:"type:order;column:deleted_at;table:line_reverse_order_ext"` |  | ||||||
|     CreateBy string `form:"createByOrder"  search:"type:order;column:create_by;table:line_reverse_order_ext"` |  | ||||||
|     UpdateBy string `form:"updateByOrder"  search:"type:order;column:update_by;table:line_reverse_order_ext"` |  | ||||||
|      |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *LineReverseOrderExtGetPageReq) GetNeedSearch() interface{} { |  | ||||||
| 	return *m |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type LineReverseOrderExtInsertReq struct { |  | ||||||
|     Id int `json:"-" comment:"主键id"` // 主键id |  | ||||||
|     ReverseOrderId int `json:"reverseOrderId" comment:"下反单id"` |  | ||||||
|     IsAddPosition int `json:"isAddPosition" comment:"加仓状态 1-已加仓 2-未加仓"` |  | ||||||
|     IsReduce int `json:"isReduce" comment:"减仓状态 1-已减仓 2-未减仓"` |  | ||||||
|     common.ControlBy |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *LineReverseOrderExtInsertReq) Generate(model *models.LineReverseOrderExt)  { |  | ||||||
|     if s.Id == 0 { |  | ||||||
|         model.Model = common.Model{ Id: s.Id } |  | ||||||
|     } |  | ||||||
|     model.ReverseOrderId = s.ReverseOrderId |  | ||||||
|     model.IsAddPosition = s.IsAddPosition |  | ||||||
|     model.IsReduce = s.IsReduce |  | ||||||
|     model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *LineReverseOrderExtInsertReq) GetId() interface{} { |  | ||||||
| 	return s.Id |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type LineReverseOrderExtUpdateReq struct { |  | ||||||
|     Id int `uri:"id" comment:"主键id"` // 主键id |  | ||||||
|     ReverseOrderId int `json:"reverseOrderId" comment:"下反单id"` |  | ||||||
|     IsAddPosition int `json:"isAddPosition" comment:"加仓状态 1-已加仓 2-未加仓"` |  | ||||||
|     IsReduce int `json:"isReduce" comment:"减仓状态 1-已减仓 2-未减仓"` |  | ||||||
|     common.ControlBy |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *LineReverseOrderExtUpdateReq) Generate(model *models.LineReverseOrderExt)  { |  | ||||||
|     if s.Id == 0 { |  | ||||||
|         model.Model = common.Model{ Id: s.Id } |  | ||||||
|     } |  | ||||||
|     model.ReverseOrderId = s.ReverseOrderId |  | ||||||
|     model.IsAddPosition = s.IsAddPosition |  | ||||||
|     model.IsReduce = s.IsReduce |  | ||||||
|     model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *LineReverseOrderExtUpdateReq) GetId() interface{} { |  | ||||||
| 	return s.Id |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // LineReverseOrderExtGetReq 功能获取请求参数 |  | ||||||
| type LineReverseOrderExtGetReq struct { |  | ||||||
|      Id int `uri:"id"` |  | ||||||
| } |  | ||||||
| func (s *LineReverseOrderExtGetReq) GetId() interface{} { |  | ||||||
| 	return s.Id |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // LineReverseOrderExtDeleteReq 功能删除请求参数 |  | ||||||
| type LineReverseOrderExtDeleteReq struct { |  | ||||||
| 	Ids []int `json:"ids"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *LineReverseOrderExtDeleteReq) GetId() interface{} { |  | ||||||
| 	return s.Ids |  | ||||||
| } |  | ||||||
							
								
								
									
										117
									
								
								app/admin/service/dto/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								app/admin/service/dto/line_reverse_position.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | |||||||
|  | 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 | ||||||
|  | } | ||||||
| @ -32,6 +32,8 @@ type LineReverseSettingInsertReq struct { | |||||||
| 	Id                  int             `json:"-" comment:"主键id"` // 主键id | 	Id                  int             `json:"-" comment:"主键id"` // 主键id | ||||||
| 	ReverseOrderType    string          `json:"reverseOrderType" comment:"反单下单类型 LIMIT-限价 MARKET-市价"` | 	ReverseOrderType    string          `json:"reverseOrderType" comment:"反单下单类型 LIMIT-限价 MARKET-市价"` | ||||||
| 	ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" comment:"溢价百分比"` | 	ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" comment:"溢价百分比"` | ||||||
|  | 	TakeProfitRatio     decimal.Decimal `json:"takeProfitRatio" comment:"止盈百分比"` | ||||||
|  | 	StopLossRatio       decimal.Decimal `json:"stopLossRatio" comment:"止损百分比"` | ||||||
| 	common.ControlBy | 	common.ControlBy | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -41,6 +43,8 @@ func (s *LineReverseSettingInsertReq) Generate(model *models.LineReverseSetting) | |||||||
| 	} | 	} | ||||||
| 	model.ReverseOrderType = s.ReverseOrderType | 	model.ReverseOrderType = s.ReverseOrderType | ||||||
| 	model.ReversePremiumRatio = s.ReversePremiumRatio | 	model.ReversePremiumRatio = s.ReversePremiumRatio | ||||||
|  | 	model.StopLossRatio = s.StopLossRatio | ||||||
|  | 	model.TakeProfitRatio = s.TakeProfitRatio | ||||||
| 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -52,6 +56,7 @@ type LineReverseSettingUpdateReq struct { | |||||||
| 	Id                  int             `uri:"id" comment:"主键id"` // 主键id | 	Id                  int             `uri:"id" comment:"主键id"` // 主键id | ||||||
| 	ReverseOrderType    string          `json:"reverseOrderType" comment:"反单下单类型 LIMIT-限价 MARKET-市价"` | 	ReverseOrderType    string          `json:"reverseOrderType" comment:"反单下单类型 LIMIT-限价 MARKET-市价"` | ||||||
| 	ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" comment:"溢价百分比"` | 	ReversePremiumRatio decimal.Decimal `json:"reversePremiumRatio" comment:"溢价百分比"` | ||||||
|  | 	TakeProfitRatio     decimal.Decimal `json:"takeProfitRatio" comment:"止盈百分比"` | ||||||
| 	StopLossRatio       decimal.Decimal `json:"stopLossRatio" comment:"止损百分比"` | 	StopLossRatio       decimal.Decimal `json:"stopLossRatio" comment:"止损百分比"` | ||||||
| 	common.ControlBy | 	common.ControlBy | ||||||
| } | } | ||||||
| @ -63,6 +68,7 @@ func (s *LineReverseSettingUpdateReq) Generate(model *models.LineReverseSetting) | |||||||
| 	model.ReverseOrderType = s.ReverseOrderType | 	model.ReverseOrderType = s.ReverseOrderType | ||||||
| 	model.ReversePremiumRatio = s.ReversePremiumRatio | 	model.ReversePremiumRatio = s.ReversePremiumRatio | ||||||
| 	model.StopLossRatio = s.StopLossRatio | 	model.StopLossRatio = s.StopLossRatio | ||||||
|  | 	model.TakeProfitRatio = s.TakeProfitRatio | ||||||
| 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -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,23 @@ type LineApiUser struct { | |||||||
| 	service.Service | 	service.Service | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 获取可以绑定的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 | ||||||
| @ -107,7 +125,7 @@ func (e *LineApiUser) Insert(c *dto.LineApiUserInsertReq) error { | |||||||
| 				return errors.New("反向api已被使用") | 				return errors.New("反向api已被使用") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if err2 := tx.Model(models.LineApiUser{}).Where("id =?", c.ReverseApiId).Update("subordinate", 2).Error; err2 != nil { | 			if err2 := tx.Model(models.LineApiUser{}).Where("id =?", c.ReverseApiId).Update("subordinate", '2').Error; err2 != nil { | ||||||
| 				return err2 | 				return err2 | ||||||
| 			} | 			} | ||||||
| 			data.Subordinate = "1" | 			data.Subordinate = "1" | ||||||
| @ -133,6 +151,9 @@ func (e *LineApiUser) Insert(c *dto.LineApiUserInsertReq) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if err2 := e.CacheRelation(); err2 != nil { | ||||||
|  | 		return err2 | ||||||
|  | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -170,6 +191,33 @@ func (e *LineApiUser) saveCache(data models.LineApiUser) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 缓存关系 | ||||||
|  | // cacheAll 是否缓存所有关系 | ||||||
|  | func (e *LineApiUser) CacheRelation() error { | ||||||
|  | 	var datas *[]models.LineApiUser | ||||||
|  | 	cacheStrs := make([]string, 0) | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Model(&models.LineApiUser{}).Where("subordinate ='1' and reverse_api_id >0 and open_status =1 and reverse_status =1").Find(&datas).Error; err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, data := range *datas { | ||||||
|  | 		cacheStrs = append(cacheStrs, fmt.Sprintf("%d:%d", data.Id, data.ReverseApiId)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(cacheStrs) > 0 { | ||||||
|  | 		if err := helper.DefaultRedis.SetListCache(rediskey.ApiReverseRelation, 0, cacheStrs...); err != nil { | ||||||
|  | 			e.Log.Errorf("设置缓存失败 err:%v", err) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if err := helper.DefaultRedis.DeleteString(rediskey.ApiReverseRelation); err != nil { | ||||||
|  | 			e.Log.Errorf("删除缓存失败 err:%v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| 打开用户websocket订阅 | 打开用户websocket订阅 | ||||||
| */ | */ | ||||||
| @ -292,6 +340,10 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if err2 := e.CacheRelation(); err2 != nil { | ||||||
|  | 		return err2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -338,10 +390,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 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -12,14 +12,14 @@ import ( | |||||||
| 	cDto "go-admin/common/dto" | 	cDto "go-admin/common/dto" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type LineReverseOrderExt struct { | type LineReversePosition struct { | ||||||
| 	service.Service | 	service.Service | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetPage 获取LineReverseOrderExt列表 | // GetPage 获取LineReversePosition列表 | ||||||
| func (e *LineReverseOrderExt) GetPage(c *dto.LineReverseOrderExtGetPageReq, p *actions.DataPermission, list *[]models.LineReverseOrderExt, count *int64) error { | func (e *LineReversePosition) GetPage(c *dto.LineReversePositionGetPageReq, p *actions.DataPermission, list *[]models.LineReversePosition, count *int64) error { | ||||||
| 	var err error | 	var err error | ||||||
| 	var data models.LineReverseOrderExt | 	var data models.LineReversePosition | ||||||
| 
 | 
 | ||||||
| 	err = e.Orm.Model(&data). | 	err = e.Orm.Model(&data). | ||||||
| 		Scopes( | 		Scopes( | ||||||
| @ -30,15 +30,15 @@ func (e *LineReverseOrderExt) GetPage(c *dto.LineReverseOrderExtGetPageReq, p *a | |||||||
| 		Find(list).Limit(-1).Offset(-1). | 		Find(list).Limit(-1).Offset(-1). | ||||||
| 		Count(count).Error | 		Count(count).Error | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Log.Errorf("LineReverseOrderExtService GetPage error:%s \r\n", err) | 		e.Log.Errorf("LineReversePositionService GetPage error:%s \r\n", err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Get 获取LineReverseOrderExt对象 | // Get 获取LineReversePosition对象 | ||||||
| func (e *LineReverseOrderExt) Get(d *dto.LineReverseOrderExtGetReq, p *actions.DataPermission, model *models.LineReverseOrderExt) error { | func (e *LineReversePosition) Get(d *dto.LineReversePositionGetReq, p *actions.DataPermission, model *models.LineReversePosition) error { | ||||||
| 	var data models.LineReverseOrderExt | 	var data models.LineReversePosition | ||||||
| 
 | 
 | ||||||
| 	err := e.Orm.Model(&data). | 	err := e.Orm.Model(&data). | ||||||
| 		Scopes( | 		Scopes( | ||||||
| @ -47,7 +47,7 @@ func (e *LineReverseOrderExt) Get(d *dto.LineReverseOrderExtGetReq, p *actions.D | |||||||
| 		First(model, d.GetId()).Error | 		First(model, d.GetId()).Error | ||||||
| 	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { | 	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { | ||||||
| 		err = errors.New("查看对象不存在或无权查看") | 		err = errors.New("查看对象不存在或无权查看") | ||||||
| 		e.Log.Errorf("Service GetLineReverseOrderExt error:%s \r\n", err) | 		e.Log.Errorf("Service GetLineReversePosition error:%s \r\n", err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -57,23 +57,23 @@ func (e *LineReverseOrderExt) Get(d *dto.LineReverseOrderExtGetReq, p *actions.D | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Insert 创建LineReverseOrderExt对象 | // Insert 创建LineReversePosition对象 | ||||||
| func (e *LineReverseOrderExt) Insert(c *dto.LineReverseOrderExtInsertReq) error { | func (e *LineReversePosition) Insert(c *dto.LineReversePositionInsertReq) error { | ||||||
|     var err error |     var err error | ||||||
|     var data models.LineReverseOrderExt |     var data models.LineReversePosition | ||||||
|     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("LineReverseOrderExtService Insert error:%s \r\n", err) | 		e.Log.Errorf("LineReversePositionService Insert error:%s \r\n", err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Update 修改LineReverseOrderExt对象 | // Update 修改LineReversePosition对象 | ||||||
| func (e *LineReverseOrderExt) Update(c *dto.LineReverseOrderExtUpdateReq, p *actions.DataPermission) error { | func (e *LineReversePosition) Update(c *dto.LineReversePositionUpdateReq, p *actions.DataPermission) error { | ||||||
|     var err error |     var err error | ||||||
|     var data = models.LineReverseOrderExt{} |     var data = models.LineReversePosition{} | ||||||
|     e.Orm.Scopes( |     e.Orm.Scopes( | ||||||
|             actions.Permission(data.TableName(), p), |             actions.Permission(data.TableName(), p), | ||||||
|         ).First(&data, c.GetId()) |         ).First(&data, c.GetId()) | ||||||
| @ -81,7 +81,7 @@ func (e *LineReverseOrderExt) Update(c *dto.LineReverseOrderExtUpdateReq, p *act | |||||||
| 
 | 
 | ||||||
|     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("LineReverseOrderExtService Save error:%s \r\n", err) |         e.Log.Errorf("LineReversePositionService Save error:%s \r\n", err) | ||||||
|         return err |         return err | ||||||
|     } |     } | ||||||
|     if db.RowsAffected == 0 { |     if db.RowsAffected == 0 { | ||||||
| @ -90,16 +90,16 @@ func (e *LineReverseOrderExt) Update(c *dto.LineReverseOrderExtUpdateReq, p *act | |||||||
|     return nil |     return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Remove 删除LineReverseOrderExt | // Remove 删除LineReversePosition | ||||||
| func (e *LineReverseOrderExt) Remove(d *dto.LineReverseOrderExtDeleteReq, p *actions.DataPermission) error { | func (e *LineReversePosition) Remove(d *dto.LineReversePositionDeleteReq, p *actions.DataPermission) error { | ||||||
| 	var data models.LineReverseOrderExt | 	var data models.LineReversePosition | ||||||
| 
 | 
 | ||||||
| 	db := e.Orm.Model(&data). | 	db := e.Orm.Model(&data). | ||||||
| 		Scopes( | 		Scopes( | ||||||
| 			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 RemoveLineReverseOrderExt error:%s \r\n", err) |         e.Log.Errorf("Service RemoveLineReversePosition error:%s \r\n", err) | ||||||
|         return err |         return err | ||||||
|     } |     } | ||||||
|     if db.RowsAffected == 0 { |     if db.RowsAffected == 0 { | ||||||
| @ -3,13 +3,15 @@ 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" | ||||||
| 	"go-admin/app/admin/service/dto" | 	"go-admin/app/admin/service/dto" | ||||||
| 	"go-admin/common/actions" | 	"go-admin/common/actions" | ||||||
|  | 	"go-admin/common/const/rediskey" | ||||||
| 	cDto "go-admin/common/dto" | 	cDto "go-admin/common/dto" | ||||||
|  | 	"go-admin/common/helper" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type LineReverseSetting struct { | type LineReverseSetting struct { | ||||||
| @ -59,35 +61,60 @@ func (e *LineReverseSetting) Get(d *dto.LineReverseSettingGetReq, p *actions.Dat | |||||||
|  |  | ||||||
| // Insert 创建LineReverseSetting对象 | // Insert 创建LineReverseSetting对象 | ||||||
| func (e *LineReverseSetting) Insert(c *dto.LineReverseSettingInsertReq) error { | func (e *LineReverseSetting) Insert(c *dto.LineReverseSettingInsertReq) error { | ||||||
|     var err error | 	var err error | ||||||
|     var data models.LineReverseSetting | 	var data models.LineReverseSetting | ||||||
|     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("LineReverseSettingService Insert error:%s \r\n", err) | 		e.Log.Errorf("LineReverseSettingService Insert error:%s \r\n", err) | ||||||
| 		return 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 | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Update 修改LineReverseSetting对象 | // Update 修改LineReverseSetting对象 | ||||||
| func (e *LineReverseSetting) Update(c *dto.LineReverseSettingUpdateReq, p *actions.DataPermission) error { | func (e *LineReverseSetting) Update(c *dto.LineReverseSettingUpdateReq, p *actions.DataPermission) error { | ||||||
|     var err error | 	var err error | ||||||
|     var data = models.LineReverseSetting{} | 	var data = models.LineReverseSetting{} | ||||||
|     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("LineReverseSettingService Save error:%s \r\n", err) | 		e.Log.Errorf("LineReverseSettingService 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 | 	if err = helper.DefaultRedis.SetHashWithTags(rediskey.ReverseSetting, data); err != nil { | ||||||
|  | 		e.Log.Errorf("redis error:%s", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Remove 删除LineReverseSetting | // Remove 删除LineReverseSetting | ||||||
| @ -99,11 +126,11 @@ func (e *LineReverseSetting) Remove(d *dto.LineReverseSettingDeleteReq, p *actio | |||||||
| 			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 RemoveLineReverseSetting error:%s \r\n", err) | 		e.Log.Errorf("Service RemoveLineReverseSetting 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 | ||||||
| } | } | ||||||
|  | |||||||
| @ -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,85 @@ 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 { | ||||||
|  | 	var fields map[string]interface{} | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	// 1. 优先检查 obj 是否已经是一个 map[string]interface{} | ||||||
|  | 	if m, ok := obj.(map[string]interface{}); ok { | ||||||
|  | 		fields = m // 如果是,直接使用这个 map | ||||||
|  | 	} else { | ||||||
|  | 		// 2. 如果不是 map,则假设它是一个 struct,并尝试从 struct 中获取字段 | ||||||
|  | 		fields, err = getFieldsFromStruct(obj) // getFieldsFromStruct 现在需要返回 error | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("从结构体获取字段失败: %w", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd := r.client.HSet(r.ctx, key, fields) | ||||||
|  | 	return cmd.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 +585,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() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										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 | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								pkg/retryhelper/retryhelper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								pkg/retryhelper/retryhelper.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | |||||||
|  | package retryhelper | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"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, fmt.Errorf("retry failed after %d attempts, last error: %w", opts.MaxRetries+1, err) | ||||||
|  | } | ||||||
| @ -1,15 +0,0 @@ | |||||||
| package binanceservice |  | ||||||
|  |  | ||||||
| import "github.com/go-admin-team/go-admin-core/sdk/service" |  | ||||||
|  |  | ||||||
| type BinanceReverseFuturesService struct { |  | ||||||
| 	service.Service |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |  | ||||||
| func (e *BinanceReverseFuturesService) DoReverse() error { |  | ||||||
| 	apiInfo, ok := ShouldReverse(apiKey) |  | ||||||
| 	//TODO: 实现反向开仓逻辑 |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| package binanceservice |  | ||||||
|  |  | ||||||
| import "github.com/go-admin-team/go-admin-core/sdk/service" |  | ||||||
|  |  | ||||||
| type BinanceReverseSpotService struct { |  | ||||||
| 	service.Service |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // JudgeApiUser  判断是否需要 |  | ||||||
| func (e *BinanceReverseSpotService) JudgeApiUser() bool { |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| package binanceservice |  | ||||||
|  |  | ||||||
| import DbModels "go-admin/app/admin/models" |  | ||||||
|  |  | ||||||
| // ShouldReverse 判断是否需要反单 |  | ||||||
| // return apiInfo, bool |  | ||||||
| func ShouldReverse(apiKey string) (DbModels.LineApiUser, bool) { |  | ||||||
| 	// TODO: 实现判断是否需要反单的逻辑 |  | ||||||
| 	apiInfo := GetApiInfoByKey(apiKey) |  | ||||||
|  |  | ||||||
| 	if apiInfo.ReverseStatus == 1 && apiInfo.ReverseApiId > 0 { |  | ||||||
| 		return apiInfo, true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return DbModels.LineApiUser{}, false |  | ||||||
| } |  | ||||||
							
								
								
									
										114
									
								
								services/binanceservice/futures_reset_v2.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								services/binanceservice/futures_reset_v2.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | |||||||
|  | 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, | ||||||
|  | 		"quantity":         params.Quantity.String(), | ||||||
|  | 		"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" | ||||||
|  | 	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" | ||||||
|  | 	case "STOP": | ||||||
|  | 		paramsMaps["price"] = params.Price.String() | ||||||
|  | 		paramsMaps["stopprice"] = params.StopPrice.String() | ||||||
|  | 		paramsMaps["workingType"] = "MARK_PRICE" | ||||||
|  | 		paramsMaps["timeInForce"] = "GTC" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 获取 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 = err.Error() | ||||||
|  | 			} | ||||||
|  | 			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" | ||||||
| @ -715,6 +716,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 系统自定义订单号 | ||||||
|  | |||||||
| @ -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,45 @@ 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 { | 	// preOrder, err := getPreOrder(db, orderSn) | ||||||
| 		mapStr, _ := sonic.Marshal(&mapData) | 	// if err != nil { | ||||||
| 		logger.Error("订单回调失败,没有状态:", string(mapStr)) | 	// 	logger.Error("合约订单回调失败,查询订单失败:", orderSn, " err:", err) | ||||||
| 		return | 	// 	return | ||||||
| 	} | 	// } | ||||||
| 	// 更新订单状态 |  | ||||||
| 	orderStatus, reason := parseOrderStatus(status, mapData) |  | ||||||
|  |  | ||||||
| 	if orderStatus == 0 { | 	// // 解析订单状态 | ||||||
| 		logger.Error("订单回调失败,状态错误:", orderSn, " status:", status, " reason:", reason) | 	// status, ok := mapData["X"].(string) | ||||||
| 		return | 	// if !ok { | ||||||
| 	} | 	// 	mapStr, _ := sonic.Marshal(&mapData) | ||||||
|  | 	// 	logger.Error("订单回调失败,没有状态:", string(mapStr)) | ||||||
|  | 	// 	return | ||||||
|  | 	// } | ||||||
|  | 	// // 更新订单状态 | ||||||
|  | 	// orderStatus, reason := parseOrderStatus(status, mapData) | ||||||
|  |  | ||||||
| 	if err := updateOrderStatus(db, preOrder, orderStatus, reason, true, mapData); err != nil { | 	// if orderStatus == 0 { | ||||||
| 		logger.Error("修改订单状态失败:", orderSn, " err:", err) | 	// 	logger.Error("订单回调失败,状态错误:", orderSn, " status:", status, " reason:", reason) | ||||||
| 		return | 	// 	return | ||||||
| 	} | 	// } | ||||||
|  |  | ||||||
| 	handleFutOrderByType(db, preOrder, orderStatus) | 	// if err := updateOrderStatus(db, preOrder, orderStatus, reason, true, mapData); err != nil { | ||||||
|  | 	// 	logger.Error("修改订单状态失败:", orderSn, " err:", err) | ||||||
|  | 	// 	return | ||||||
|  | 	// } | ||||||
|  |  | ||||||
|  | 	// handleFutOrderByType(db, preOrder, orderStatus) | ||||||
| } | } | ||||||
|  |  | ||||||
| // 合约回调 | // 合约回调 | ||||||
|  | |||||||
| @ -61,16 +61,17 @@ 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"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										623
									
								
								services/binanceservice/reverse_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										623
									
								
								services/binanceservice/reverse_service.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,623 @@ | |||||||
|  | package binanceservice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"database/sql" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	DbModels "go-admin/app/admin/models" | ||||||
|  | 	"go-admin/common/global" | ||||||
|  | 	"go-admin/common/helper" | ||||||
|  | 	"go-admin/pkg/maphelper" | ||||||
|  | 	"go-admin/services/cacheservice" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ReverseService struct { | ||||||
|  | 	service.Service | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SaveMainOrder 保存订单信息 | ||||||
|  | // mapData: 下单数据 | ||||||
|  | // status: 订单状态 | ||||||
|  | // orderSn: 自定义订单号 | ||||||
|  | func (e *ReverseService) handleSimpleStatusChange(status string, orderSn string, mapData map[string]interface{}) (bool, error) { | ||||||
|  | 	statusMap := map[string]int{ | ||||||
|  | 		"NEW":              2, | ||||||
|  | 		"CANCELED":         6, | ||||||
|  | 		"EXPIRED":          7, | ||||||
|  | 		"EXPIRED_IN_MATCH": 7, | ||||||
|  | 	} | ||||||
|  | 	if newStatus, ok := statusMap[status]; ok { | ||||||
|  | 		_ = e.changeOrderStatus(newStatus, orderSn, mapData) | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	return false, fmt.Errorf("不支持的订单状态 %s", status) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ReverseOrder 反向下单 | ||||||
|  | // apiKey: api key | ||||||
|  | // mapData: 下单数据 | ||||||
|  | // return apiInfo, bool | ||||||
|  | // bool: true=需要反单, false=不需要反单 | ||||||
|  | func (e *ReverseService) ReverseOrder(apiKey string, mapData map[string]interface{}) (bool, error) { | ||||||
|  | 	apiInfo := GetApiInfoByKey(apiKey) | ||||||
|  |  | ||||||
|  | 	if apiInfo.Id == 0 { | ||||||
|  | 		e.Log.Errorf("获取apiInfo失败 %s", apiKey) | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	status, _ := maphelper.GetString(mapData, "X") | ||||||
|  | 	orderSn, err := maphelper.GetString(mapData, "c") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return true, err | ||||||
|  | 	} | ||||||
|  | 	ot, err := maphelper.GetString(mapData, "ot") | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return true, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch status { | ||||||
|  | 	case "NEW", "CANCELED", "EXPIRED", "EXPIRED_IN_MATCH": | ||||||
|  | 		result, err := e.handleSimpleStatusChange(status, orderSn, mapData) | ||||||
|  |  | ||||||
|  | 		//如果是 新开止盈止损 需要取消反单的止盈止损之后重下反单止盈止损 | ||||||
|  | 		if status == "NEW" && (ot == "TAKE_PROFIT_MARKET" || ot == "STOP_LOSS_MARKET" || ot == "TAKE_PROFIT_LIMIT" || ot == "STOP_LOSS_LIMIT") && | ||||||
|  | 			apiInfo.ReverseStatus == 1 && apiInfo.OpenStatus == 1 && apiInfo.ReverseApiId > 0 { | ||||||
|  |  | ||||||
|  | 			if err := e.ReTakeOrStopOrder(&mapData, orderSn, &apiInfo); err != nil { | ||||||
|  | 				return true, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return result, err | ||||||
|  | 	case "FILLED": | ||||||
|  | 		if apiInfo.ReverseStatus == 1 && apiInfo.OpenStatus == 1 && apiInfo.ReverseApiId > 0 { | ||||||
|  | 			reverseApiInfo, err := GetApiInfo(apiInfo.ReverseApiId) | ||||||
|  | 			if err != nil { | ||||||
|  | 				e.Log.Errorf("获取反向api信息失败 reverseApiId:%d, err:%v", apiInfo.ReverseApiId, err) | ||||||
|  | 				return true, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			mainOrder, err := e.SaveMainOrder(mapData, apiInfo) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return false, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			switch { | ||||||
|  | 			case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": | ||||||
|  | 				if mainOrder.Category == 0 { | ||||||
|  | 					if err1 := e.savePosition(&mainOrder, reverseApiInfo.Id, true, false, false); err1 != nil { | ||||||
|  | 						return true, err1 | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, false, false) | ||||||
|  | 				} | ||||||
|  | 			case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": | ||||||
|  | 				if mainOrder.Category == 0 { | ||||||
|  | 					closePosition := maphelper.GetBool(mapData, "R") | ||||||
|  | 					if err1 := e.savePosition(&mainOrder, reverseApiInfo.Id, true, true, closePosition); err1 != nil { | ||||||
|  | 						e.Log.Errorf("保存主订单失败: %v", err1) | ||||||
|  | 						return true, err1 | ||||||
|  | 					} | ||||||
|  | 					e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, true, closePosition) | ||||||
|  | 				} | ||||||
|  | 			default: | ||||||
|  | 				return true, errors.New("不支持的订单类型") | ||||||
|  | 			} | ||||||
|  | 			return true, nil | ||||||
|  | 		} else if apiInfo.Subordinate == "2" { | ||||||
|  | 			symbol, err := maphelper.GetString(mapData, "s") | ||||||
|  |  | ||||||
|  | 			if err != nil { | ||||||
|  | 				return true, err | ||||||
|  | 			} | ||||||
|  | 			positionSide, err := maphelper.GetString(mapData, "ps") | ||||||
|  |  | ||||||
|  | 			if err != nil { | ||||||
|  | 				return true, err | ||||||
|  | 			} | ||||||
|  | 			side, err := maphelper.GetString(mapData, "S") | ||||||
|  | 			if err != nil { | ||||||
|  | 				return true, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			totalNum := maphelper.GetDecimal(mapData, "z") | ||||||
|  |  | ||||||
|  | 			mainOrder := DbModels.LineReverseOrder{ | ||||||
|  | 				ApiId:        apiInfo.Id, | ||||||
|  | 				Symbol:       symbol, | ||||||
|  | 				PositionSide: positionSide, | ||||||
|  | 				TotalNum:     totalNum, | ||||||
|  | 				Side:         side, | ||||||
|  | 			} | ||||||
|  | 			e.changeOrderStatus(3, orderSn, mapData) | ||||||
|  |  | ||||||
|  | 			switch { | ||||||
|  | 			case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": | ||||||
|  | 				if mainOrder.Category == 0 { | ||||||
|  | 					if err1 := e.savePosition(&mainOrder, 0, false, false, false); err1 != nil { | ||||||
|  | 						return true, err1 | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": | ||||||
|  | 				if mainOrder.Category == 0 { | ||||||
|  | 					closePosition := maphelper.GetBool(mapData, "R") | ||||||
|  | 					if err1 := e.savePosition(&mainOrder, 0, false, true, closePosition); err1 != nil { | ||||||
|  | 						return true, err1 | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			default: | ||||||
|  | 				return true, errors.New("不支持的订单类型") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		return false, fmt.Errorf("不支持的订单状态 %s", status) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return false, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改订单状态 | ||||||
|  | // status: 订单状态 1-待下单 2-已下单 3-已成交 6-已取消 7-已过期 | ||||||
|  | func (e *ReverseService) changeOrderStatus(status int, orderSn string, mapData map[string]interface{}) error { | ||||||
|  | 	data := map[string]interface{}{"status": status, "updated_at": time.Now()} | ||||||
|  |  | ||||||
|  | 	if status == 3 { | ||||||
|  | 		now := time.Now() | ||||||
|  | 		if orderId, ok := mapData["i"].(float64); ok { | ||||||
|  | 			data["order_id"] = orderId | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if ap, ok := mapData["ap"].(bool); ok { | ||||||
|  | 			data["final_price"] = ap | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if num, ok := mapData["z"].(string); ok { | ||||||
|  | 			data["total_num"], _ = decimal.NewFromString(num) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		data["trigger_time"] = &now | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	db := e.Orm.Model(&DbModels.LineReverseOrder{}). | ||||||
|  | 		Where("order_sn =? and status != 3", orderSn). | ||||||
|  | 		Updates(data) | ||||||
|  |  | ||||||
|  | 	if db.Error != nil { | ||||||
|  | 		e.Log.Errorf("修改订单状态失败 orderSn:%s, err:%v", orderSn, db.Error) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if db.RowsAffected == 0 { | ||||||
|  | 		e.Log.Errorf("修改订单状态失败 orderSn:%s, 未找到订单", orderSn) | ||||||
|  | 	} | ||||||
|  | 	return db.Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 先保存主单,持仓信息 必须用双向持仓! | ||||||
|  | func (e *ReverseService) SaveMainOrder(mapData map[string]interface{}, apiInfo DbModels.LineApiUser) (DbModels.LineReverseOrder, error) { | ||||||
|  | 	now := time.Now() | ||||||
|  | 	symbol, err := maphelper.GetString(mapData, "s") | ||||||
|  | 	var reverseOrder DbModels.LineReverseOrder | ||||||
|  | 	if err != nil { | ||||||
|  | 		return reverseOrder, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	orderSn, err := maphelper.GetString(mapData, "c") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return reverseOrder, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	side, err := maphelper.GetString(mapData, "S") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return reverseOrder, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	positionSide, err := maphelper.GetString(mapData, "ps") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return reverseOrder, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mainType, _ := maphelper.GetString(mapData, "ot") | ||||||
|  |  | ||||||
|  | 	reverseOrder = DbModels.LineReverseOrder{ | ||||||
|  | 		ApiId:         apiInfo.Id, | ||||||
|  | 		Category:      0, | ||||||
|  | 		OrderSn:       orderSn, | ||||||
|  | 		TriggerTime:   &now, | ||||||
|  | 		Status:        3, | ||||||
|  | 		Symbol:        symbol, | ||||||
|  | 		FollowOrderSn: "", | ||||||
|  | 		Type:          mainType, | ||||||
|  | 		Price:         maphelper.GetDecimal(mapData, "p"), | ||||||
|  | 		FinalPrice:    maphelper.GetDecimal(mapData, "ap"), | ||||||
|  | 		TotalNum:      maphelper.GetDecimal(mapData, "z"), | ||||||
|  | 		Side:          side, | ||||||
|  | 		PositionSide:  positionSide, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	reverseOrder.PriceU = reverseOrder.Price | ||||||
|  |  | ||||||
|  | 	if !reverseOrder.Price.IsZero() && !reverseOrder.TotalNum.IsZero() { | ||||||
|  | 		reverseOrder.BuyPrice = reverseOrder.Price.Mul(reverseOrder.TotalNum) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if id, err := maphelper.GetFloat64(mapData, "i"); err == nil { | ||||||
|  | 		reverseOrder.OrderId = strconv.FormatFloat(id, 'f', -1, 64) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	orderType, _ := maphelper.GetString(mapData, "ot") | ||||||
|  | 	switch orderType { | ||||||
|  | 	case "LIMIT", "MARKET": | ||||||
|  | 		reverseOrder.OrderType = 0 | ||||||
|  | 	case "TAKE_PROFIT_MARKET", "TAKE_PROFIT": | ||||||
|  | 		reverseOrder.OrderType = 1 | ||||||
|  | 	case "STOP_MARKET", "STOP", "TRAILING_STOP_MARKET": | ||||||
|  | 		reverseOrder.OrderType = 2 | ||||||
|  | 	default: | ||||||
|  | 		return reverseOrder, fmt.Errorf("不支持的订单类型: %s", orderType) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if reverseOrder.PositionSide == "BOTH" { | ||||||
|  | 		return reverseOrder, errors.New("不支持的持仓类型,必须为双向持仓") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Create(&reverseOrder).Error; err != nil { | ||||||
|  | 		e.Log.Errorf("保存主单失败:%v", err) | ||||||
|  | 		return reverseOrder, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return reverseOrder, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 更新仓位信息 | ||||||
|  | // closePosition: true=平仓, false=减仓 | ||||||
|  | func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, reverseApiId int, isMain, reducePosition, closePosition bool) error { | ||||||
|  | 	position := DbModels.LineReversePosition{} | ||||||
|  | 	positionSide := reverseOrder.PositionSide | ||||||
|  | 	side := reverseOrder.Side | ||||||
|  | 	totalNum := reverseOrder.TotalNum | ||||||
|  |  | ||||||
|  | 	var querySql string | ||||||
|  | 	sqlStr := "" | ||||||
|  |  | ||||||
|  | 	//如果是主单,存储仓位则是反单的持仓方向 | ||||||
|  | 	if isMain { | ||||||
|  | 		if reverseOrder.PositionSide == "LONG" { | ||||||
|  | 			positionSide = "SHORT" | ||||||
|  | 		} else { | ||||||
|  | 			positionSide = "LONG" | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !reducePosition { | ||||||
|  | 			//反单止盈止损方向相反 | ||||||
|  | 			if side == "SELL" { | ||||||
|  | 				side = "BUY" | ||||||
|  | 			} else { | ||||||
|  | 				side = "SELL" | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			position.ReverseApiId = reverseApiId | ||||||
|  | 			position.Side = side | ||||||
|  | 			position.ApiId = reverseOrder.ApiId | ||||||
|  | 			position.Symbol = reverseOrder.Symbol | ||||||
|  | 			position.Status = 1 | ||||||
|  | 			position.ReverseStatus = 0 | ||||||
|  | 			position.PositionSide = positionSide | ||||||
|  | 		} | ||||||
|  | 		querySql = "api_id =? and position_side =? and symbol =? and status =1" | ||||||
|  |  | ||||||
|  | 		//平仓 | ||||||
|  | 		if closePosition { | ||||||
|  | 			totalNum = decimal.Zero | ||||||
|  | 			sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2 where id =@id and status!=2 " | ||||||
|  | 		} else if reducePosition { | ||||||
|  | 			//只减仓 | ||||||
|  | 			sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now() where id =@id and status!=2 " | ||||||
|  | 		} else { | ||||||
|  | 			sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now() where id =@id and status!=2" | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)" | ||||||
|  |  | ||||||
|  | 		if closePosition { | ||||||
|  | 			totalNum = decimal.Zero | ||||||
|  | 			sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2 where id =@id and reverse_status !=2" | ||||||
|  | 		} else if reducePosition { | ||||||
|  | 			sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now() where id =@id and reverse_status !=2" | ||||||
|  | 		} else { | ||||||
|  | 			sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1 where id =@id and reverse_status !=2" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||||
|  | 		err1 := tx.Model(&position).Where(querySql, | ||||||
|  | 			reverseOrder.ApiId, positionSide, reverseOrder.Symbol).First(&position).Error | ||||||
|  |  | ||||||
|  | 		if err1 != nil { | ||||||
|  | 			//主单仓位不存在,创建新仓位 | ||||||
|  | 			if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition { | ||||||
|  |  | ||||||
|  | 				if err2 := tx.Create(&position).Error; err2 != nil { | ||||||
|  | 					return err2 | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				return err1 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		dbResult := tx.Exec(sqlStr, sql.Named("totalNum", totalNum), sql.Named("id", position.Id)) | ||||||
|  | 		if dbResult.Error != nil { | ||||||
|  | 			return dbResult.Error | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if dbResult.RowsAffected == 0 { | ||||||
|  | 			e.Log.Errorf("减仓数据 是否平仓单:%v :%v", closePosition, reverseOrder) | ||||||
|  | 			return errors.New("没有找到对应的持仓信息") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 反向下单 | ||||||
|  | // mainOrder: 主单信息 | ||||||
|  | // reverseApiId: 反向apiId | ||||||
|  | // orderProportion: 反向下单比例 | ||||||
|  | // reducePosition: 是否减仓 | ||||||
|  | // closePosition: 是否平仓 | ||||||
|  | func (e *ReverseService) DoAddReverseOrder(mainOrder *DbModels.LineReverseOrder, reverseApiInfo *DbModels.LineApiUser, orderProportion decimal.Decimal, reducePosition, closePosition bool) error { | ||||||
|  | 	order := DbModels.LineReverseOrder{} | ||||||
|  | 	order.ApiId = reverseApiInfo.Id | ||||||
|  | 	order.Category = 1 | ||||||
|  | 	order.OrderSn = helper.GetOrderNo() | ||||||
|  | 	order.FollowOrderSn = mainOrder.OrderSn | ||||||
|  | 	order.Symbol = mainOrder.Symbol | ||||||
|  | 	order.OrderType = 0 | ||||||
|  |  | ||||||
|  | 	switch mainOrder.PositionSide { | ||||||
|  | 	case "LONG": | ||||||
|  | 		order.PositionSide = "SHORT" | ||||||
|  | 	case "SHORT": | ||||||
|  | 		order.PositionSide = "LONG" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch mainOrder.Side { | ||||||
|  | 	case "SELL": | ||||||
|  | 		order.Side = "BUY" | ||||||
|  | 	case "BUY": | ||||||
|  | 		order.Side = "SELL" | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("不支持的订单类型 side:%s", mainOrder.Side) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if reducePosition && closePosition { | ||||||
|  | 		order.OrderType = 4 | ||||||
|  | 	} else if reducePosition { | ||||||
|  | 		order.OrderType = 3 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, mainOrder.Symbol, 1) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("获取交易对信息失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	setting, err := cacheservice.GetReverseSetting(e.Orm) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("获取反单设置失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	signPrice, _ := decimal.NewFromString(symbol.LastPrice) | ||||||
|  | 	price := signPrice.Truncate(int32(symbol.PriceDigit)) | ||||||
|  |  | ||||||
|  | 	if price.Cmp(decimal.Zero) <= 0 { | ||||||
|  | 		e.Log.Errorf("获取最新价格失败 symbol:%s custom:%s 单价小于0", mainOrder.Symbol, mainOrder.OrderSn) | ||||||
|  | 		return errors.New("获取最新价格失败") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var percent decimal.Decimal | ||||||
|  | 	switch { | ||||||
|  | 	case order.PositionSide == "LONG" && order.Side == "BUY", order.PositionSide == "SHORT" && order.Side == "BUY": | ||||||
|  | 		percent = decimal.NewFromInt(100).Add(setting.ReversePremiumRatio) | ||||||
|  | 	case order.PositionSide == "SHORT" && order.Side == "SELL", order.PositionSide == "LONG" && order.Side == "SELL": | ||||||
|  | 		percent = decimal.NewFromInt(100).Sub(setting.ReversePremiumRatio) | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("不支持的订单类型 ps:%s, side:%s", order.PositionSide, order.Side) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	percent = percent.Div(decimal.NewFromInt(100)).Truncate(4) | ||||||
|  | 	//计算溢价单价 | ||||||
|  | 	price = price.Mul(percent).Truncate(int32(symbol.PriceDigit)) | ||||||
|  | 	var amount decimal.Decimal | ||||||
|  |  | ||||||
|  | 	//平仓单直接卖出全部数量 | ||||||
|  | 	if closePosition { | ||||||
|  | 		var position DbModels.LineReversePosition | ||||||
|  | 		if err1 := e.Orm.Model(position). | ||||||
|  | 			Where("position_side =? and reverse_api_id =? and  reverse_status =1", order.PositionSide, order.ApiId). | ||||||
|  | 			Select("reverse_amount"). | ||||||
|  | 			Find(&position).Error; err1 != nil { | ||||||
|  | 			e.Log.Errorf("获取剩余仓位失败 symbol:%s custom:%s :%v", order.Symbol, order.OrderSn, err1) | ||||||
|  |  | ||||||
|  | 			if len(err1.Error()) < 255 { | ||||||
|  | 				order.Remark = err1.Error() | ||||||
|  | 			} else { | ||||||
|  | 				order.Remark = err1.Error()[:254] | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		amount = position.ReverseAmount | ||||||
|  |  | ||||||
|  | 		if amount.IsZero() { | ||||||
|  | 			order.Remark = "没有持仓数量" | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		proportion := decimal.NewFromInt(100) | ||||||
|  |  | ||||||
|  | 		if orderProportion.Cmp(decimal.Zero) > 0 { | ||||||
|  | 			proportion = orderProportion | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		//反向下单百分比 | ||||||
|  | 		proportion = proportion.Div(decimal.NewFromInt(100)).Truncate(2) | ||||||
|  | 		amount = mainOrder.BuyPrice.Mul(proportion).Div(price).Truncate(int32(symbol.AmountDigit)) | ||||||
|  |  | ||||||
|  | 		if amount.Cmp(decimal.Zero) <= 0 { | ||||||
|  | 			e.Log.Errorf("计算数量失败 symbol:%s custom:%s 数量小于0", mainOrder.Symbol, mainOrder.OrderSn) | ||||||
|  | 			return errors.New("计算数量失败") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	order.TotalNum = amount | ||||||
|  | 	order.Price = price | ||||||
|  | 	order.PriceU = price | ||||||
|  | 	order.BuyPrice = amount.Mul(price).Truncate(int32(symbol.PriceDigit)) | ||||||
|  | 	order.Status = 1 | ||||||
|  | 	order.Type = setting.ReverseOrderType | ||||||
|  | 	order.SignPrice = signPrice | ||||||
|  |  | ||||||
|  | 	if order.Remark != "" { | ||||||
|  | 		order.Status = 8 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Model(&order).Create(&order).Error; err != nil { | ||||||
|  | 		e.Log.Errorf("保存反单失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if order.Status == 1 { | ||||||
|  | 		e.DoBianceOrder(&order, reverseApiInfo, &setting, reducePosition, closePosition) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 处理币安订单 | ||||||
|  | // order: 反单信息 | ||||||
|  | // apiInfo: 币安api信息 | ||||||
|  | func (e *ReverseService) DoBianceOrder(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, setting *DbModels.LineReverseSetting, reducePosition, closePosition bool) error { | ||||||
|  | 	futApiV2 := FuturesResetV2{Service: e.Service} | ||||||
|  | 	orderType := setting.ReverseOrderType | ||||||
|  |  | ||||||
|  | 	if orderType == "" { | ||||||
|  | 		orderType = "LIMIT" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params := FutOrderPlace{ | ||||||
|  | 		ApiId:            apiInfo.Id, | ||||||
|  | 		Symbol:           order.Symbol, | ||||||
|  | 		PositionSide:     order.PositionSide, | ||||||
|  | 		Side:             order.Side, | ||||||
|  | 		OrderType:        orderType, | ||||||
|  | 		Quantity:         order.TotalNum, | ||||||
|  | 		Price:            order.Price, | ||||||
|  | 		NewClientOrderId: order.OrderSn, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := futApiV2.OrderPlaceLoop(apiInfo, params) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", order.Symbol, order.OrderSn, err) | ||||||
|  | 		remark := err.Error() | ||||||
|  |  | ||||||
|  | 		if len(remark) > 255 { | ||||||
|  | 			remark = remark[:254] | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err1 := e.Orm.Model(&order).Where("id =? and status !=3", order.Id).Updates(map[string]interface{}{"status": 8, "remark": remark, "updated_at": time.Now()}).Error; err1 != nil { | ||||||
|  | 			e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", err1) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 重下止盈止损 | ||||||
|  | // mapData: 主单止盈止损回调 | ||||||
|  | func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser) error { | ||||||
|  | 	symbol, err := maphelper.GetString(*mapData, "s") | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	side, err := maphelper.GetString(*mapData, "S") | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ot, err := maphelper.GetString(*mapData, "ot") | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//反单止盈止损方向相反 | ||||||
|  | 	if side == "SELL" { | ||||||
|  | 		side = "BUY" | ||||||
|  | 	} else { | ||||||
|  | 		side = "SELL" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	positionSide, err := maphelper.GetString(*mapData, "ps") | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if positionSide == "LONG" { | ||||||
|  | 		positionSide = "SHORT" | ||||||
|  | 	} else { | ||||||
|  | 		positionSide = "LONG" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	apiInfo, err := GetApiInfo(mainApiInfo.ReverseApiId) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("根据主单api获取反单api失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var reverseOrder DbModels.LineReverseOrder | ||||||
|  | 	e.Orm.Model(&DbModels.LineReverseOrder{}). | ||||||
|  | 		Where("symbol =? and api_id =? and position_side =? and side = ? and status =1", symbol, mainApiInfo.ReverseApiId, positionSide, side). | ||||||
|  | 		First(&reverseOrder) | ||||||
|  |  | ||||||
|  | 	//取消旧止盈止损 | ||||||
|  | 	if reverseOrder.OrderSn != "" { | ||||||
|  | 		futApi := FutRestApi{} | ||||||
|  | 		err := futApi.CancelFutOrderRetry(apiInfo, symbol, reverseOrder.OrderSn) | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			e.Log.Errorf("币安撤单失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params := FutOrderPlace{ | ||||||
|  | 		ApiId:            apiInfo.Id, | ||||||
|  | 		Symbol:           symbol, | ||||||
|  | 		PositionSide:     positionSide, | ||||||
|  | 		Side:             side, | ||||||
|  | 		OrderType:        ot, | ||||||
|  | 		Quantity:         reverseOrder.TotalNum, | ||||||
|  | 		Price:            reverseOrder.Price, | ||||||
|  | 		NewClientOrderId: reverseOrder.OrderSn, | ||||||
|  | 	} | ||||||
|  | 	futApiV2 := FuturesResetV2{Service: e.Service} | ||||||
|  | 	futApiV2.OrderPlace(&apiInfo, params) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								services/binanceservice/reverse_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/binanceservice/reverse_service_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | package binanceservice | ||||||
| @ -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() | ||||||
|  | |||||||
							
								
								
									
										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,32 @@ 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: | ||||||
|  | 			// 防止阻塞,如果通道满了就跳过 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| 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 +226,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 +249,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 +356,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 +463,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,8 +482,9 @@ 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) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -447,6 +555,7 @@ func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) { | |||||||
| 					retryCount++ | 					retryCount++ | ||||||
|  |  | ||||||
| 					if retryCount >= maxRetries { | 					if retryCount >= maxRetries { | ||||||
|  | 						wm.reconnecting.Store(false) | ||||||
| 						log.Error("重连失败次数过多,退出重连逻辑") | 						log.Error("重连失败次数过多,退出重连逻辑") | ||||||
| 						return | 						return | ||||||
| 					} | 					} | ||||||
| @ -455,51 +564,88 @@ func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) { | |||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				// 重连成功,清除标记 | ||||||
|  | 				wm.reconnecting.Store(false) | ||||||
|  | 				retryCount = 0 | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // 定期删除listenkey 并重启ws | // 假死检测 | ||||||
| func (wm *BinanceWebSocketManager) startListenKeyRenewal(ctx context.Context, listenKey string) { | func (wm *BinanceWebSocketManager) DeadCheck() { | ||||||
| 	time.Sleep(30 * time.Minute) | 	subKey := fmt.Sprintf(global.USER_SUBSCRIBE, wm.apiKey) | ||||||
|  | 	val, _ := helper.DefaultRedis.GetString(subKey) | ||||||
| 	select { | 	if val == "" { | ||||||
| 	case <-ctx.Done(): | 		log.Warnf("没有订阅信息,无法进行假死检测") | ||||||
| 		return | 		return | ||||||
| 	default: |  | ||||||
| 		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{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// ticker := time.NewTicker(5 * time.Minute) | 	var data binancedto.UserSubscribeState | ||||||
| 	// defer ticker.Stop() | 	_ = sonic.Unmarshal([]byte(val), &data) | ||||||
|  |  | ||||||
| 	// for { | 	var lastTime *time.Time | ||||||
| 	// 	select { | 	if wm.wsType == 0 { | ||||||
| 	// 	case <-ticker.C: | 		lastTime = data.SpotLastTime | ||||||
| 	// 		if wm.isStopped { | 	} else { | ||||||
| 	// 			return | 		lastTime = data.FuturesLastTime | ||||||
| 	// 		} | 	} | ||||||
|  |  | ||||||
| 	// 		if err := wm.deleteListenKey(listenKey); err != nil { | 	// 定义最大静默时间(超出视为假死) | ||||||
| 	// 			log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err) | 	var timeout time.Duration | ||||||
| 	// 		} else { | 	if wm.wsType == 0 { | ||||||
| 	// 			log.Debug("Successfully delete listenKey") | 		timeout = 40 * time.Second // Spot 每 20s ping,40s 足够 | ||||||
| 	// 			wm.reconnect <- struct{}{} | 	} else { | ||||||
| 	// 			return | 		timeout = 6 * time.Minute // Futures 每 3 分钟 ping | ||||||
| 	// 		} | 	} | ||||||
| 	// 	case <-ctx.Done(): |  | ||||||
| 	// 		return | 	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 | ||||||
|  | // func (wm *BinanceWebSocketManager) startListenKeyRenewal(ctx context.Context, listenKey string) { | ||||||
|  | // 	time.Sleep(30 * time.Minute) | ||||||
|  |  | ||||||
|  | // 	select { | ||||||
|  | // 	case <-ctx.Done(): | ||||||
|  | // 		return | ||||||
|  | // 	default: | ||||||
|  | // 		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.triggerReconnect() | ||||||
|  | // 		} | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // } | ||||||
|  |  | ||||||
| // 定时续期 | // 定时续期 | ||||||
| func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) { | func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) { | ||||||
| 	ticker := time.NewTicker(30 * time.Minute) | 	ticker := time.NewTicker(30 * time.Minute) | ||||||
| @ -527,49 +673,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