diff --git a/app/admin/apis/line_symbol.go b/app/admin/apis/line_symbol.go index d0c43ae..f4ca283 100644 --- a/app/admin/apis/line_symbol.go +++ b/app/admin/apis/line_symbol.go @@ -70,6 +70,60 @@ func (e LineSymbol) GetPage(c *gin.Context) { e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") } +// Export 导出交易对列表 +// @Summary 导出交易对列表 +// @Description 导出交易对列表 +// @Tags 交易对列表 +// @Param type query string false "类型:1=现货,2=合约" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol-group/export [get] +func (e LineSymbol) Export(c *gin.Context) { + req := dto.LineSymbolGetPageReq{} + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + err = s.ExportExcel(c, p, &req) + + if err != nil { + e.Error(500, err, fmt.Sprintf("导出交易对列表失败,\r\n失败信息 %s", err.Error())) + return + } +} + +// GetAll 获取所有交易对名 +func (e LineSymbol) GetAll(c *gin.Context) { + req := dto.LineSymbolGetListReq{} + s := service.LineSymbol{} + 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 + } + + datas, err := s.GetAll(&req) + + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(datas, "查询成功") +} + // Get 获取交易对管理 // @Summary 获取交易对管理 // @Description 获取交易对管理 diff --git a/app/admin/router/line_pre_order.go b/app/admin/router/line_pre_order.go index afc9f68..0dfca73 100644 --- a/app/admin/router/line_pre_order.go +++ b/app/admin/router/line_pre_order.go @@ -39,6 +39,7 @@ func registerLinePreOrderRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTM r.POST("aiCoinPrice", actions.PermissionAction(), api.QueryAiCoinPrice) //获取aiCoin买入点 r.POST("/calculate", api.CalculateBreakEevenRatio) //计算亏损后止盈百分比 + } } diff --git a/app/admin/router/line_symbol.go b/app/admin/router/line_symbol.go index fef0e5c..892f14b 100644 --- a/app/admin/router/line_symbol.go +++ b/app/admin/router/line_symbol.go @@ -28,5 +28,8 @@ func registerLineSymbolRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMid r.POST("syncSpotSymbol", api.SyncSpotSymbol) //同步现货交易对 r.POST("syncFutSymbol", api.SyncFutSymbol) //同步合约交易对 r.POST("getSymbol", api.GetSymbol) //获取现货和合约都有的交易对 + r.GET("/export", api.Export) //导出交易对 + + r.GET("/all", api.GetAll) //获取所有交易对 } } diff --git a/app/admin/service/dto/line_pre_order.go b/app/admin/service/dto/line_pre_order.go index b5eefe6..0d43269 100644 --- a/app/admin/service/dto/line_pre_order.go +++ b/app/admin/service/dto/line_pre_order.go @@ -13,7 +13,8 @@ import ( type LinePreOrderGetPageReq struct { dto.Pagination `search:"-"` - ExchangeType string `json:"exchangeType" search:"type:exact;column:exchange_type;table:line_pre_order" comment:"交易所类型 字典exchange_type"` + ExchangeType string `json:"exchangeType" form:"exchangeType" search:"type:exact;column:exchange_type;table:line_pre_order" comment:"交易所类型 字典exchange_type"` + SymbolType int `json:"symbolType" form:"symbolType" search:"type:exact;column:symbol_type;table:line_pre_order"` ApiId string `form:"apiId" search:"type:exact;column:api_id;table:line_pre_order" comment:"api用户"` Symbol string `form:"symbol" search:"type:exact;column:symbol;table:line_pre_order" comment:"交易对"` QuoteSymbol string `form:"quoteSymbol" search:"type:exact;column:quote_symbol;table:line_pre_order" comment:"计较货币"` diff --git a/app/admin/service/dto/line_symbol.go b/app/admin/service/dto/line_symbol.go index e055299..22a8cf5 100644 --- a/app/admin/service/dto/line_symbol.go +++ b/app/admin/service/dto/line_symbol.go @@ -17,6 +17,13 @@ type LineSymbolGetPageReq struct { LineSymbolOrder } +type LineSymbolExportResp struct { + Symbol string `json:"symbol" excel:"交易对"` + Coin string `json:"coin" excel:"基础货币"` + Currency string `json:"currency" excel:"计价货币"` + SymbolType string `json:"symbolType" excel:"交易对类型"` +} + type LineSymbolOrder struct { Id string `form:"idOrder" search:"type:order;column:id;table:line_symbol"` ApiId string `form:"apiIdOrder" search:"type:order;column:api_id;table:line_symbol"` @@ -34,6 +41,7 @@ type LineSymbolOrder struct { type LineSymbolGetListReq struct { ExchangeType string `json:"exchangeType" form:"exchangeType"` + Type string `json:"type" form:"type"` } func (req *LineSymbolGetListReq) Valid() error { diff --git a/app/admin/service/dto/line_symbol_group.go b/app/admin/service/dto/line_symbol_group.go index 45b4161..a383c69 100644 --- a/app/admin/service/dto/line_symbol_group.go +++ b/app/admin/service/dto/line_symbol_group.go @@ -4,6 +4,7 @@ import ( "go-admin/app/admin/models" "go-admin/common/dto" common "go-admin/common/models" + "strings" ) type LineSymbolGroupGetPageReq struct { @@ -46,8 +47,11 @@ func (s *LineSymbolGroupInsertReq) Generate(model *models.LineSymbolGroup) { model.Model = common.Model{Id: s.Id} } // model.ExchangeType = s.ExchangeType + model.ExchangeType = s.ExchangeType model.GroupName = s.GroupName - model.Symbol = s.Symbol + model.Symbol = strings.ReplaceAll(strings.ReplaceAll(s.Symbol, ",", ","), " ", "") + model.Symbol = strings.ReplaceAll(model.Symbol, "\r\n", ",") + model.Symbol = strings.ReplaceAll(model.Symbol, "\n", ",") model.GroupType = s.GroupType model.Type = s.Type model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 @@ -73,8 +77,11 @@ func (s *LineSymbolGroupUpdateReq) Generate(model *models.LineSymbolGroup) { } // model.ExchangeType = s.ExchangeType model.GroupName = s.GroupName - model.Symbol = s.Symbol + model.Symbol = strings.ReplaceAll(strings.ReplaceAll(s.Symbol, ",", ","), " ", "") + model.Symbol = strings.ReplaceAll(model.Symbol, "\r\n", ",") + model.Symbol = strings.ReplaceAll(model.Symbol, "\n", ",") model.GroupType = s.GroupType + model.ExchangeType = s.ExchangeType model.Type = s.Type model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 } diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index f4c258c..2d7a214 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -509,18 +509,14 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP //加仓、减仓状态 tx.Model(&models.LinePreOrderStatus{}).Create(&preOrderStatus) - for index := range preOrderExts { - if index == 0 { - preOrderExts[index].OrderId = AddOrder.Id - } + // for index := range preOrderExts { + // if index == 0 { + // preOrderExts[index].OrderId = AddOrder.Id + // } - preOrderExts[index].MainOrderId = AddOrder.Id - } + // preOrderExts[index].MainOrderId = AddOrder.Id + // } - err = tx.Model(&models.LinePreOrderExt{}).Create(&preOrderExts).Error - if err != nil { - return err - } list := dto.PreOrderRedisList{ Id: AddOrder.Id, Symbol: AddOrder.Symbol, @@ -605,12 +601,12 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP continue } - preOrderExts[index].OrderId = addPosition.Id if err := e.Orm.Create(&addPosition).Error; err != nil { logger.Error("保存加仓单失败") return err } + preOrderExts[index].OrderId = addPosition.Id //止盈、减仓 orders, err := makeFuturesTakeAndReduce(&addPosition, v, tradeSet) @@ -645,6 +641,11 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP } } } + + err = tx.Model(&models.LinePreOrderExt{}).Create(&preOrderExts).Error + if err != nil { + return err + } return nil }) } diff --git a/app/admin/service/line_symbol.go b/app/admin/service/line_symbol.go index c4ece3e..9a8f2d7 100644 --- a/app/admin/service/line_symbol.go +++ b/app/admin/service/line_symbol.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/bytedance/sonic" + "github.com/gin-gonic/gin" "github.com/go-admin-team/go-admin-core/logger" "github.com/go-admin-team/go-admin-core/sdk/service" "github.com/shopspring/decimal" @@ -115,6 +116,66 @@ func (e *LineSymbol) GetSamePage(c *dto.LineSymbolGetPageReq, p *actions.DataPer return nil } +// 导出excel +func (e *LineSymbol) ExportExcel(c *gin.Context, p *actions.DataPermission, req *dto.LineSymbolGetPageReq) error { + list := make([]models.LineSymbol, 0) + datas := make([]dto.LineSymbolExportResp, 0) + var data models.LineSymbol + var fileName string + + err := e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(req.GetNeedSearch()), + actions.Permission(data.TableName(), p), + ). + Find(&list).Error + + if err != nil { + return err + } + + for _, v := range list { + item := dto.LineSymbolExportResp{ + Symbol: v.Symbol, + Coin: v.BaseAsset, + Currency: v.QuoteAsset, + } + + if v.Type == "1" { + item.SymbolType = "现货" + } else { + item.SymbolType = "合约" + } + + datas = append(datas, item) + } + + if len(datas) == 0 { + return errors.New("无数据") + } + + if req.Type == "1" { + fileName = "现货交易对" + } else { + fileName = "合约交易对" + } + + err = helper.ExportExcel(c, fileName, datas, []string{}) + + return err +} + +// 获取所有交易对 +func (e *LineSymbol) GetAll(req *dto.LineSymbolGetListReq) ([]string, error) { + result := make([]string, 0) + + if err := e.Orm.Model(&models.LineSymbol{}).Where("type = ? AND exchange_type =?", req.Type, req.ExchangeType).Pluck("symbol", &result).Error; err != nil { + return nil, err + } + + return result, nil +} + // Get 获取LineSymbol对象 func (e *LineSymbol) Get(d *dto.LineSymbolGetReq, p *actions.DataPermission, model *models.LineSymbol) error { var data models.LineSymbol diff --git a/app/admin/service/line_symbol_group.go b/app/admin/service/line_symbol_group.go index 79cbcfc..33ac409 100644 --- a/app/admin/service/line_symbol_group.go +++ b/app/admin/service/line_symbol_group.go @@ -11,6 +11,7 @@ import ( "go-admin/app/admin/service/dto" "go-admin/common/actions" cDto "go-admin/common/dto" + "go-admin/pkg/utility" ) type LineSymbolGroup struct { @@ -73,6 +74,14 @@ func (e *LineSymbolGroup) Insert(c *dto.LineSymbolGroupInsertReq) error { var err error var data models.LineSymbolGroup c.Generate(&data) + + symbols := availableSymbols(data.Symbol, c.Type, e) + + if len(symbols) == 0 { + return errors.New("全部交易对不可用") + } + + data.Symbol = strings.Join(symbols, ",") err = e.Orm.Create(&data).Error if err != nil { e.Log.Errorf("LineSymbolGroupService Insert error:%s \r\n", err) @@ -81,6 +90,25 @@ func (e *LineSymbolGroup) Insert(c *dto.LineSymbolGroupInsertReq) error { return nil } +// 可用交易对 +func availableSymbols(symbols, symbolType string, e *LineSymbolGroup) []string { + blacks := make([]string, 0) + newSymbols := make([]string, 0) + reqSymbols := strings.Split(symbols, ",") + e.Orm.Model(&models.LineSymbolBlack{}).Where("type=?", symbolType).Select("symbol").Find(&blacks) + for _, v := range reqSymbols { + symbol := strings.ToUpper(v) + + //黑名单跳过 + if utility.ContainsStr(blacks, symbol) { + continue + } + + newSymbols = append(newSymbols, symbol) + } + return newSymbols +} + // Update 修改LineSymbolGroup对象 func (e *LineSymbolGroup) Update(c *dto.LineSymbolGroupUpdateReq, p *actions.DataPermission) error { var err error @@ -89,7 +117,13 @@ func (e *LineSymbolGroup) Update(c *dto.LineSymbolGroupUpdateReq, p *actions.Dat actions.Permission(data.TableName(), p), ).First(&data, c.GetId()) c.Generate(&data) + symbols := availableSymbols(data.Symbol, c.Type, e) + if len(symbols) == 0 { + return errors.New("全部交易对不可用") + } + + data.Symbol = strings.Join(symbols, ",") db := e.Orm.Save(&data) if err = db.Error; err != nil { e.Log.Errorf("LineSymbolGroupService Save error:%s \r\n", err) diff --git a/common/helper/excel_helper.go b/common/helper/excel_helper.go new file mode 100644 index 0000000..e02f6d1 --- /dev/null +++ b/common/helper/excel_helper.go @@ -0,0 +1,168 @@ +package helper + +import ( + "encoding/csv" + "errors" + "fmt" + "log" + "reflect" + "strings" + + "github.com/xuri/excelize/v2" + + "github.com/gin-gonic/gin" +) + +/* +导出csv文件 + + - @fileName 文件名 不带拓展名 + - @header 文件头 + - @records 内容 +*/ +func ExportCSV(c *gin.Context, fileName string, header []string, records [][]string) error { + disposition := fmt.Sprintf("attachment; filename=%s.csv", fileName) + + // Set headers + c.Header("Content-Description", "File Transfer") + c.Header("Content-Disposition", disposition) + c.Header("Content-Type", "text/csv") + + // Create a CSV writer using the response writer + writer := csv.NewWriter(c.Writer) + defer writer.Flush() + + // Write CSV header + writer.Write(header) + + for _, record := range records { + writer.Write(record) + } + + return nil +} + +/* +导出excel + + - @fileName 文件名称 + - @data 数据源 + - @ingore 忽略header +*/ +func ExportExcel[T any](c *gin.Context, fileName string, data []T, ingore []string) error { + if len(data) == 0 { + return errors.New("无导出记录") + } + // Create a new Excel file + f := excelize.NewFile() + // Use reflection to get the header from struct tags + t := reflect.TypeOf(data[0]) + headers := []string{} + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + excelTag := field.Tag.Get("excel") + if excelTag != "" && !ArrayAny(ingore, excelTag) { + headers = append(headers, excelTag) + } + } + // Set headers + for i, header := range headers { + col := string('A' + i) + cell := fmt.Sprintf("%s1", col) + f.SetCellValue("Sheet1", cell, header) + } + + // Fill rows with data + for rowIndex, item := range data { + rowValue := reflect.ValueOf(item) + rowType := rowValue.Type() + for colIndex, header := range headers { + col := string('A' + colIndex) + cell := fmt.Sprintf("%s%d", col, rowIndex+2) + var fieldValue reflect.Value + + for i := 0; i < rowType.NumField(); i++ { + field := rowType.Field(i) + if strings.EqualFold(field.Tag.Get("excel"), header) { + fieldValue = rowValue.Field(i) + break + } + } + + // Check if the fieldValue is valid before accessing it + if fieldValue.IsValid() && fieldValue.CanInterface() { + //f.SetCellValue("Sheet1", cell, fieldValue.Interface()) + value := fieldValue.Interface() + + // Ensure the value is a string, convert it if necessary + var stringValue string + if v, ok := value.(string); ok { + stringValue = v // If it's a string, use it directly + } else { + stringValue = fmt.Sprintf("%v", value) // Otherwise, convert to string + } + f.SetCellValue("Sheet1", cell, stringValue) + } else { + // Handle the case where fieldValue is invalid or nil + f.SetCellValue("Sheet1", cell, "") + } + } + } + // Set response headers and send the file to the client + + // c.Writer.Header().Set("Content-Disposition", "attachment; filename=test.xlsx") + // c.Writer.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=binary") + // c.Writer.Header().Set("Content-Transfer-Encoding", "binary") + // c.Writer.Header().Set("Expires", "0") + // c.Writer.Header().Set("Cache-Control", "must-revalidate") + // c.Writer.Header().Set("Pragma", "public") + c.Header("Content-Description", "File Transfer") + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.xlsx", fileName)) + c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + c.Header("Content-Transfer-Encoding", "binary") + c.Header("Expires", "0") + c.Header("Cache-Control", "must-revalidate") + c.Header("Pragma", "public") + c.Header("Content-Encoding", "") + //fmt.Println("c.Writer.Header():", c.Writer.Header()) + if _, err := f.WriteTo(c.Writer); err != nil { + log.Println("Error writing file:", err) + return err + } + return nil + //return f.WriteTo(c.Writer) +} + +func MapExcelToStruct[T any](rows [][]string, headers []string) ([]T, error) { + var results []T + if len(rows) == 0 { + return results, nil + } + + for _, row := range rows { + var result T + v := reflect.ValueOf(&result).Elem() + + for i, header := range headers { + fieldName := "" + for j := 0; j < v.NumField(); j++ { + field := v.Type().Field(j) + tag := field.Tag.Get("excel") + if strings.EqualFold(tag, header) { + fieldName = field.Name + break + } + } + + if fieldName != "" && i < len(row) { + field := v.FieldByName(fieldName) + if field.IsValid() && field.CanSet() { + field.Set(reflect.ValueOf(row[i]).Convert(field.Type())) + } + } + } + results = append(results, result) + } + + return results, nil +} diff --git a/common/helper/extension_helper.go b/common/helper/extension_helper.go new file mode 100644 index 0000000..612216e --- /dev/null +++ b/common/helper/extension_helper.go @@ -0,0 +1,52 @@ +package helper + +/* +判断是否存在 + + - @arr 数组 + - @value 值 +*/ +func ArrayAny[T comparable](arr []T, value T) bool { + for _, v := range arr { + if v == value { + return true + } + } + return false +} + +// 定义一个条件函数类型 +type ConditionFunc[T any] func(T) bool + +/* +判断是否存在 + + - @arr 数组 + + - @condition 判断函数 + + @return 对象指针 +*/ +func ArrayAnyExtension[T any](arr *[]T, condition ConditionFunc[T]) *T { + for _, v := range *arr { + if condition(v) { + return &v + } + } + + return nil +} + +func RemoveDuplicates(nums []int64) []int64 { + m := make(map[int64]bool) + result := []int64{} + + for _, num := range nums { + if !m[num] { + m[num] = true + result = append(result, num) + } + } + + return result +} diff --git a/go.mod b/go.mod index 7a05999..348d207 100644 --- a/go.mod +++ b/go.mod @@ -134,6 +134,7 @@ require ( github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mojocn/base64Captcha v1.3.5 // indirect github.com/nsqio/go-nsq v1.1.0 // indirect github.com/nyaruka/phonenumbers v1.0.55 // indirect @@ -143,6 +144,8 @@ require ( github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/redis/go-redis/v9 v9.3.0 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shamsher31/goimgext v1.0.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -161,10 +164,13 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect + github.com/xuri/excelize/v2 v2.9.0 // indirect + github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/image v0.13.0 // indirect + golang.org/x/image v0.18.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect