This commit is contained in:
2025-02-21 20:52:51 +08:00
parent 28b5406028
commit ed4456cd06
12 changed files with 411 additions and 15 deletions

View File

@ -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 获取交易对管理

View File

@ -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) //计算亏损后止盈百分比
}
}

View File

@ -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) //获取所有交易对
}
}

View File

@ -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:"计较货币"`

View File

@ -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 {

View File

@ -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 // 添加这而,需要记录是被谁更新的
}

View File

@ -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
})
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

8
go.mod
View File

@ -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