This commit is contained in:
2025-03-21 20:44:35 +08:00
parent bac4fd8b11
commit 8cede57a70
21 changed files with 430 additions and 120 deletions

View File

@ -1,6 +1,7 @@
package apis package apis
import ( import (
"errors"
"fmt" "fmt"
"go-admin/common/const/rediskey" "go-admin/common/const/rediskey"
"go-admin/common/global" "go-admin/common/global"
@ -284,7 +285,15 @@ func (e LinePreOrder) AddPreOrder(c *gin.Context) {
return return
} }
userId := user.GetUserId(c)
if userId <= 0 {
e.Error(500, errors.New("用户不存在"), "用户不存在")
return
}
p := actions.GetPermissionFromContext(c) p := actions.GetPermissionFromContext(c)
req.SetCreateBy(userId)
errs := make([]error, 0) errs := make([]error, 0)
errStr := make([]string, 0) errStr := make([]string, 0)
var tickerSymbol string var tickerSymbol string
@ -358,6 +367,13 @@ func (e LinePreOrder) BatchAddOrder(c *gin.Context) {
p := actions.GetPermissionFromContext(c) p := actions.GetPermissionFromContext(c)
errs := make([]error, 0) errs := make([]error, 0)
errStr := make([]string, 0) errStr := make([]string, 0)
userId := user.GetUserId(c)
if userId <= 0 {
e.Error(500, nil, "用户不存在")
return
}
req.SetCreateBy(userId)
s.AddBatchPreOrder(&req, p, &errs) s.AddBatchPreOrder(&req, p, &errs)
if len(errs) > 0 { if len(errs) > 0 {
//e.Logger.Error(err) //e.Logger.Error(err)
@ -392,7 +408,14 @@ func (e LinePreOrder) QuickAddPreOrder(c *gin.Context) {
p := actions.GetPermissionFromContext(c) p := actions.GetPermissionFromContext(c)
errs := make([]error, 0) errs := make([]error, 0)
errStr := make([]string, 0) errStr := make([]string, 0)
err = s.QuickAddPreOrder(&req, p, &errs) userId := user.GetUserId(c)
if userId <= 0 {
e.Error(500, nil, "用户不存在")
return
}
err = s.QuickAddPreOrder(&req, p, userId, &errs)
if len(errs) > 0 { if len(errs) > 0 {
//e.Logger.Error(err) //e.Logger.Error(err)
for _, err2 := range errs { for _, err2 := range errs {

View File

@ -26,7 +26,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"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/api" "github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/shopspring/decimal"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -364,6 +363,11 @@ func (e LineUserApi) Info(c *gin.Context) {
binanceAccount := service.BinanceAccount{Service: s.Service} binanceAccount := service.BinanceAccount{Service: s.Service}
//获取用户资金账户资产 //获取用户资金账户资产
totalAsset, err := binanceAccount.GetTotalAsset(userId)
if err != nil {
e.Logger.Errorf("获取用户资金账户资产失败:%v", err)
}
resp, err := binanceAccount.GetFundingAsset(userId) resp, err := binanceAccount.GetFundingAsset(userId)
if err != nil { if err != nil {
e.Logger.Error(500, err, err.Error()) e.Logger.Error(500, err, err.Error())
@ -375,28 +379,26 @@ func (e LineUserApi) Info(c *gin.Context) {
if val != "" { if val != "" {
sonic.Unmarshal([]byte(val), &tickerData) sonic.Unmarshal([]byte(val), &tickerData)
} }
var usdtBalance decimal.Decimal // var usdtBalance decimal.Decimal
for i, asset := range resp { for i, asset := range resp {
symbol := asset.Asset + "USDT" symbol := asset.Asset + "USDT"
for _, datum := range tickerData { for _, datum := range tickerData {
if datum.Symbol == symbol { if datum.Symbol == symbol {
mul := utility.StringToDecimal(datum.Price).Mul(utility.StringToDecimal(asset.Free)) // mul := utility.StringToDecimal(datum.Price).Mul(utility.StringToDecimal(asset.Free))
usdtBalance = usdtBalance.Add(mul) // usdtBalance = usdtBalance.Add(mul)
resp[i].UsdtValuation = datum.Price resp[i].UsdtValuation = datum.Price
} }
} }
//if asset.Asset == "USDT" {
// usdt = asset.Free
//}
} }
// 邀请人数 // 邀请人数
//var inviteNum int64 var inviteNum int64
var userinfo models.LineUser var userinfo models.LineUser
e.Orm.Model(&models.LineUser{}).Where("id = ?", userId).Find(&userinfo) e.Orm.Model(&models.LineUser{}).Where("id = ?", userId).Find(&userinfo)
e.Orm.Model(&models.LineUser{}).Where("pid =? or top_referrer_id =?", userId, userId).Count(&inviteNum)
var apiUserinfo models.LineApiUser var apiUserinfo models.LineApiUser
e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUserinfo) e.Orm.Model(&models.LineApiUser{}).Where("user_id = ? ", userId).Find(&apiUserinfo)
var isAuth bool var isAuth bool
if apiUserinfo.ApiKey != "" && apiUserinfo.ApiSecret != "" { if apiUserinfo.ApiKey != "" && apiUserinfo.ApiSecret != "" {
isAuth = true isAuth = true
@ -418,7 +420,7 @@ func (e LineUserApi) Info(c *gin.Context) {
"avatar": userinfo.Avatar, "avatar": userinfo.Avatar,
"user_id": userinfo.Id, "user_id": userinfo.Id,
"user_name": userinfo.Nickname, "user_name": userinfo.Nickname,
"invite_num": userinfo.RecommendNum, "invite_num": inviteNum,
"open_status": apiUserinfo.OpenStatus, "open_status": apiUserinfo.OpenStatus,
"is_auth": isAuth, "is_auth": isAuth,
"invite_url": fmt.Sprintf("%s/invice_url?invite_code=%s", ext.ExtConfig.Domain, userinfo.InviteCode), "invite_url": fmt.Sprintf("%s/invice_url?invite_code=%s", ext.ExtConfig.Domain, userinfo.InviteCode),
@ -428,7 +430,7 @@ func (e LineUserApi) Info(c *gin.Context) {
"api_secret": inttostring.EncryptString(apiUserinfo.ApiSecret, 4, 4), "api_secret": inttostring.EncryptString(apiUserinfo.ApiSecret, 4, 4),
} }
returnMap := map[string]interface{}{ returnMap := map[string]interface{}{
"u_balance": usdtBalance.Truncate(2), "u_balance": totalAsset.Truncate(2),
"margin": userinfo.Money, "margin": userinfo.Money,
"userinfo": user, "userinfo": user,
"funding_asset": fundingAsset, "funding_asset": fundingAsset,

View File

@ -9,3 +9,9 @@ type FundingAsset struct {
BtcValuation string `json:"btcValuation"` BtcValuation string `json:"btcValuation"`
UsdtValuation string `json:"usdt_valuation"` UsdtValuation string `json:"usdt_valuation"`
} }
type BinanceWalletBalance struct {
Active bool `json:"active"`
Balance string `json:"balance"`
WalletName string `json:"walletName"`
}

View File

@ -3,12 +3,19 @@ package service
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/sdk/service"
"go-admin/app/admin/models" "go-admin/app/admin/models"
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper" "go-admin/common/helper"
ext "go-admin/config" ext "go-admin/config"
"go-admin/pkg/utility"
"net/http" "net/http"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/service"
"github.com/shopspring/decimal"
) )
const ProxyType = "socks5" const ProxyType = "socks5"
@ -62,3 +69,90 @@ func (e *BinanceAccount) GetFundingAsset(userId int) (resp []models.FundingAsset
return resp, nil return resp, nil
} }
/*
获取钱包余额
- @param userId 用户id
- @param quoteAsset 币种 USDT, ETH, USDC, BNB等。 默认 BTC
- @return []models.FundingAsset 钱包余额
*/
func (e *BinanceAccount) GetWalletBalance(userId int, quoteAsset string) (resp []models.BinanceWalletBalance, err error) {
if quoteAsset == "" {
quoteAsset = "BTC"
}
var proxyUrl, proxyType string
var apiUser models.LineApiUser
url := "/sapi/v1/asset/wallet/balance"
err = e.Orm.Where(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUser).Error
if err != nil {
e.Log.Errorf("Service LineApiUser error:%s \r\n", err)
return []models.BinanceWalletBalance{}, err
}
if apiUser.Id <= 0 {
e.Log.Errorf("Service LineApiUser error:%s \r\n", "用户未找到")
return []models.BinanceWalletBalance{}, err
}
if ext.ExtConfig.ProxyUrl != "" {
proxyUrl = "127.0.0.1:7890"
proxyType = "http"
}
if apiUser.IpAddress != "" && apiUser.UserPass != "" {
proxyUrl = fmt.Sprintf("%s:%s", apiUser.IpAddress, apiUser.UserPass)
proxyType = ProxyType
}
clinet, err := helper.NewBinanceClient(apiUser.ApiKey, apiUser.ApiSecret, proxyType, proxyUrl)
if err != nil {
e.Log.Errorf("Service NewBinanceClient error:%s \r\n", err)
return []models.BinanceWalletBalance{}, err
}
req := map[string]string{
"quoteAsset": quoteAsset,
"recvWindow": "10000",
}
httpResp, statusCode, err := clinet.SendSpotAuth(url, "GET", req)
if err != nil {
e.Log.Errorf("Service SendSpotAuth error:%s \r\n", err)
return []models.BinanceWalletBalance{}, err
}
if statusCode != http.StatusOK {
e.Log.Errorf("Service 请求失败 error:%s \r\n", err)
return []models.BinanceWalletBalance{}, errors.New("请求失败")
}
sonic.Unmarshal(httpResp, &resp)
return resp, nil
}
/*
获取用户总资产
*/
func (e *BinanceAccount) GetTotalAsset(userId int) (decimal.Decimal, error) {
key := fmt.Sprintf(rediskey.User_Total_Asset, global.EXCHANGE_BINANCE, userId)
totalStr, _ := helper.DefaultRedis.GetString(key)
result := decimal.Zero
if totalStr != "" {
result = utility.StrToDecimal(totalStr)
} else {
assets, err := e.GetWalletBalance(userId, "USDT")
if err != nil {
logger.Errorf("GetTotalAsset error:%s", err)
return decimal.Decimal{}, err
}
for _, asset := range assets {
result = result.Add(utility.StrToDecimal(asset.Balance))
}
helper.DefaultRedis.SetStringExpire(key, result.String(), 30*time.Second)
}
return result, nil
}

View File

@ -209,6 +209,7 @@ type LineAddPreOrderReq struct {
ReduceReTakeProfitRatio decimal.Decimal `json:"re_take_profit_ratio" comment:"减仓后亏损回本止盈百分比"` ReduceReTakeProfitRatio decimal.Decimal `json:"re_take_profit_ratio" comment:"减仓后亏损回本止盈百分比"`
Ext []LineAddPreOrderExtReq `json:"ext" ` //拓展字段 Ext []LineAddPreOrderExtReq `json:"ext" ` //拓展字段
common.ControlBy
} }
type LinePreOrderAddPositionReq struct { type LinePreOrderAddPositionReq struct {
@ -389,6 +390,7 @@ type LineBatchAddPreOrderReq struct {
ReduceTakeProfitRatio decimal.Decimal `json:"reduce_take_profit"` //主单减仓后止盈价百分比 ReduceTakeProfitRatio decimal.Decimal `json:"reduce_take_profit"` //主单减仓后止盈价百分比
ReduceStopLossRatio decimal.Decimal `json:"reduce_stop_price"` //主单减仓后止损价百分比 ReduceStopLossRatio decimal.Decimal `json:"reduce_stop_price"` //主单减仓后止损价百分比
Ext []LineAddPreOrderExtReq `json:"ext"` //拓展字段 Ext []LineAddPreOrderExtReq `json:"ext"` //拓展字段
common.ControlBy
} }
func (req LineBatchAddPreOrderReq) CheckParams() error { func (req LineBatchAddPreOrderReq) CheckParams() error {

View File

@ -18,10 +18,12 @@ type LineSymbolGetPageReq struct {
} }
type LineSymbolExportResp struct { type LineSymbolExportResp struct {
Symbol string `json:"symbol" excel:"交易"` ExchangeType string `json:"exchangeType" excel:"交易"`
Coin string `json:"coin" excel:"基础货币"` Symbol string `json:"symbol" excel:"交易对"`
Currency string `json:"currency" excel:"计价货币"` Coin string `json:"coin" excel:"基础货币"`
SymbolType string `json:"symbolType" excel:"交易对类型"` Currency string `json:"currency" excel:"计价货币"`
SymbolType string `json:"symbolType" excel:"交易对类型"`
LastPrice string `json:"lastPrice" excel:"最新价"`
} }
type LineSymbolOrder struct { type LineSymbolOrder struct {

View File

@ -278,14 +278,15 @@ func (receiver FrontedLoginReq) CheckParams() int {
} }
type AddApiKeyReq struct { type AddApiKeyReq struct {
ApiName string `json:"api_name"` ExchangeType string `json:"exchange_type"`
ApiKey string `json:"api_key"` ApiName string `json:"api_name"`
ApiSecret string `json:"api_secret"` ApiKey string `json:"api_key"`
ApiIp string `json:"api_ip"` ApiSecret string `json:"api_secret"`
ApiIp string `json:"api_ip"`
} }
func (a AddApiKeyReq) CheckParams() int { func (a AddApiKeyReq) CheckParams() int {
if a.ApiKey == "" || a.ApiSecret == "" { if a.ExchangeType == "" || a.ApiKey == "" || a.ApiSecret == "" {
return statuscode.ParamErr return statuscode.ParamErr
} }
return statuscode.OK return statuscode.OK

View File

@ -29,7 +29,7 @@ func (e *LineDirection) GetPage(c *dto.LineDirectionGetPageReq, p *actions.DataP
Scopes( Scopes(
cDto.MakeCondition(c.GetNeedSearch()), cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p), // actions.Permission(data.TableName(), p),
). ).
Find(list).Limit(-1).Offset(-1). Find(list).Limit(-1).Offset(-1).
Count(count).Error Count(count).Error
@ -45,9 +45,9 @@ func (e *LineDirection) Get(d *dto.LineDirectionGetReq, p *actions.DataPermissio
var data models.LineDirection var data models.LineDirection
err := e.Orm.Model(&data). err := e.Orm.Model(&data).
Scopes( // Scopes(
actions.Permission(data.TableName(), p), // actions.Permission(data.TableName(), p),
). // ).
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("查看对象不存在或无权查看")

View File

@ -391,6 +391,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP
Type: 1, Type: 1,
Switch: "0", Switch: "0",
} }
saveTemplateParams.CreateBy = req.CreateBy
e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams) e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams)
} }
if req.SaveTemplate == "2" { if req.SaveTemplate == "2" {
@ -426,6 +427,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP
continue continue
} }
AddOrder.CreateBy = req.CreateBy
AddOrder.ExchangeType = req.ExchangeType AddOrder.ExchangeType = req.ExchangeType
AddOrder.OrderCategory = 1 AddOrder.OrderCategory = 1
AddOrder.SignPriceType = "new" AddOrder.SignPriceType = "new"
@ -598,6 +600,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP
ext.TotalBefore = mainParam.RemainingQuantity //初始数量 ext.TotalBefore = mainParam.RemainingQuantity //初始数量
ext.TotalAfter = calculateResp.RemainingQuantity //计算后数量 ext.TotalAfter = calculateResp.RemainingQuantity //计算后数量
ext.ReTakeRatio = calculateResp.Ratio ext.ReTakeRatio = calculateResp.Ratio
ext.CreateBy = req.CreateBy
mainParam.LossBeginPercent = addPosition.PriceRatio mainParam.LossBeginPercent = addPosition.PriceRatio
mainParam.RemainingQuantity = calculateResp.RemainingQuantity mainParam.RemainingQuantity = calculateResp.RemainingQuantity
mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU
@ -637,18 +640,29 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP
//是否有止盈止损订单 //是否有止盈止损订单
if req.Profit != "" { if req.Profit != "" {
if strings.ToUpper(req.Site) == "BUY" { if req.PricePattern == "mixture" {
profitOrder.Site = "SELL" mixturePrice := utility.StrToDecimal(req.Profit)
profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 + utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String()
if mixturePrice.Cmp(decimal.Zero) <= 0 {
return fmt.Errorf("止盈价不能小于等于0")
}
profitOrder.Price = mixturePrice.Truncate(int32(tradeSet.PriceDigit)).String()
profitOrder.Rate = "0"
} else { } else {
profitOrder.Site = "BUY" if strings.ToUpper(req.Site) == "BUY" {
profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 - utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String() profitOrder.Site = "SELL"
profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 + utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String()
} else {
profitOrder.Site = "BUY"
profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 - utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String()
}
profitOrder.Rate = req.Profit
} }
profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
profitOrder.Pid = AddOrder.Id profitOrder.Pid = AddOrder.Id
profitOrder.OrderType = 1 profitOrder.OrderType = 1
profitOrder.Status = 0 profitOrder.Status = 0
profitOrder.Rate = req.Profit
profitOrder.MainId = AddOrder.Id profitOrder.MainId = AddOrder.Id
if req.ProfitNumRatio.Cmp(decimal.Zero) > 0 { if req.ProfitNumRatio.Cmp(decimal.Zero) > 0 {
@ -673,12 +687,23 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP
} }
if req.ReducePriceRatio.Cmp(decimal.Zero) > 0 { if req.ReducePriceRatio.Cmp(decimal.Zero) > 0 {
if strings.ToUpper(req.Site) == "BUY" { if req.PricePattern == "mixture" {
stopOrder.Site = "SELL" if req.ReducePriceRatio.Cmp(decimal.Zero) <= 0 {
stopOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Sub(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() return errors.New("检查价格不能小于等于0")
}
stopOrder.Price = req.ReducePriceRatio.Truncate(int32(tradeSet.PriceDigit)).String()
stopOrder.Rate = "0"
} else { } else {
stopOrder.Site = "BUY" if strings.ToUpper(req.Site) == "BUY" {
stopOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Add(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() stopOrder.Site = "SELL"
stopOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Sub(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
} else {
stopOrder.Site = "BUY"
stopOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Add(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
}
stopOrder.Rate = req.ReducePriceRatio.String()
} }
stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
stopOrder.Pid = AddOrder.Id stopOrder.Pid = AddOrder.Id
@ -686,7 +711,6 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP
stopOrder.OrderType = 4 stopOrder.OrderType = 4
stopOrder.Status = 0 stopOrder.Status = 0
stopOrder.BuyPrice = "0" stopOrder.BuyPrice = "0"
stopOrder.Rate = req.ReducePriceRatio.String()
stopNum := utility.StrToDecimal(AddOrder.Num).Mul(req.ReduceNumRatio.Div(decimal.NewFromInt(100)).Truncate(4)) stopNum := utility.StrToDecimal(AddOrder.Num).Mul(req.ReduceNumRatio.Div(decimal.NewFromInt(100)).Truncate(4))
stopOrder.Num = stopNum.Truncate(int32(tradeSet.AmountDigit)).String() stopOrder.Num = stopNum.Truncate(int32(tradeSet.AmountDigit)).String()
stopOrder.ExpireTime = time.Now().AddDate(10, 0, 0) stopOrder.ExpireTime = time.Now().AddDate(10, 0, 0)
@ -1068,6 +1092,8 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p
Type: 2, Type: 2,
Switch: "0", Switch: "0",
} }
saveTemplateParams.CreateBy = p.UserId
e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams) e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams)
} }
@ -1149,6 +1175,7 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p
req.ReduceNumRatio = batchReq.ReduceNumRatio req.ReduceNumRatio = batchReq.ReduceNumRatio
req.ReduceStopLossRatio = batchReq.ReduceStopLossRatio req.ReduceStopLossRatio = batchReq.ReduceStopLossRatio
req.ReduceTakeProfitRatio = batchReq.ReduceTakeProfitRatio req.ReduceTakeProfitRatio = batchReq.ReduceTakeProfitRatio
req.CreateBy = batchReq.CreateBy
e.AddPreOrder(&req, p, errs, tickerSymbol) e.AddPreOrder(&req, p, errs, tickerSymbol)
} }
@ -1162,7 +1189,7 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p
} }
// QuickAddPreOrder 模板快速下单 // QuickAddPreOrder 模板快速下单
func (e *LinePreOrder) QuickAddPreOrder(quickReq *dto.QuickAddPreOrderReq, p *actions.DataPermission, errs *[]error) error { func (e *LinePreOrder) QuickAddPreOrder(quickReq *dto.QuickAddPreOrderReq, p *actions.DataPermission, userId int, errs *[]error) error {
templateLogs := make([]models.LineOrderTemplateLogs, 0) templateLogs := make([]models.LineOrderTemplateLogs, 0)
e.Orm.Model(&models.LineOrderTemplateLogs{}).Where("id in ?", strings.Split(quickReq.Ids, ",")).Find(&templateLogs) e.Orm.Model(&models.LineOrderTemplateLogs{}).Where("id in ?", strings.Split(quickReq.Ids, ",")).Find(&templateLogs)
for _, log := range templateLogs { for _, log := range templateLogs {
@ -1188,6 +1215,13 @@ func (e *LinePreOrder) QuickAddPreOrder(quickReq *dto.QuickAddPreOrderReq, p *ac
if log.Type == 2 { if log.Type == 2 {
var batchAddPreOrder dto.LineBatchAddPreOrderReq var batchAddPreOrder dto.LineBatchAddPreOrderReq
sonic.Unmarshal([]byte(log.Params), &batchAddPreOrder) sonic.Unmarshal([]byte(log.Params), &batchAddPreOrder)
if userId > 0 {
batchAddPreOrder.CreateBy = userId
} else {
batchAddPreOrder.CreateBy = log.CreateBy
}
e.AddBatchPreOrder(&batchAddPreOrder, p, errs) e.AddBatchPreOrder(&batchAddPreOrder, p, errs)
} }
} }

View File

@ -20,6 +20,7 @@ import (
"go-admin/common/global" "go-admin/common/global"
"go-admin/common/helper" "go-admin/common/helper"
commonModels "go-admin/models" commonModels "go-admin/models"
models2 "go-admin/models"
"go-admin/pkg/utility" "go-admin/pkg/utility"
"go-admin/services/binanceservice" "go-admin/services/binanceservice"
) )
@ -37,7 +38,7 @@ func (e *LineSymbol) GetPage(c *dto.LineSymbolGetPageReq, p *actions.DataPermiss
Scopes( Scopes(
cDto.MakeCondition(c.GetNeedSearch()), cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p), // actions.Permission(data.TableName(), p),
). ).
Find(list).Limit(-1).Offset(-1). Find(list).Limit(-1).Offset(-1).
Count(count).Error Count(count).Error
@ -126,7 +127,7 @@ func (e *LineSymbol) ExportExcel(c *gin.Context, p *actions.DataPermission, req
err := e.Orm.Model(&data). err := e.Orm.Model(&data).
Scopes( Scopes(
cDto.MakeCondition(req.GetNeedSearch()), cDto.MakeCondition(req.GetNeedSearch()),
actions.Permission(data.TableName(), p), // actions.Permission(data.TableName(), p),
). ).
Find(&list).Error Find(&list).Error
@ -134,17 +135,64 @@ func (e *LineSymbol) ExportExcel(c *gin.Context, p *actions.DataPermission, req
return err return err
} }
exchanges := []string{}
exchangeFutMap := make(map[string]models2.TradeSet)
exchangeSpotMap := make(map[string]models2.TradeSet)
for _, v := range list {
if !utility.ContainsStr(exchanges, v.ExchangeType) {
key := fmt.Sprintf(global.TICKER_FUTURES, v.ExchangeType, "*")
key2 := fmt.Sprintf(global.TICKER_SPOT, v.ExchangeType, "*")
futs, _ := helper.DefaultRedis.GetAllKeysAndValues(key)
spots, _ := helper.DefaultRedis.GetAllKeysAndValues(key2)
trade := models2.TradeSet{}
for _, v2 := range futs {
if v2 != "" {
sonic.Unmarshal([]byte(v2), &trade)
if trade.LastPrice != "" {
exchangeFutMap[v.ExchangeType+"_"+trade.Coin+trade.Currency] = trade
}
}
}
for _, v2 := range spots {
if v2 != "" {
sonic.Unmarshal([]byte(v2), &trade)
if trade.LastPrice != "" {
exchangeSpotMap[v.ExchangeType+"_"+trade.Coin+trade.Currency] = trade
}
}
}
exchanges = append(exchanges, v.ExchangeType)
}
}
for _, v := range list { for _, v := range list {
item := dto.LineSymbolExportResp{ item := dto.LineSymbolExportResp{
Symbol: v.Symbol, ExchangeType: v.ExchangeType,
Coin: v.BaseAsset, Symbol: v.Symbol,
Currency: v.QuoteAsset, Coin: v.BaseAsset,
Currency: v.QuoteAsset,
} }
if v.Type == "1" { if v.Type == "1" {
item.SymbolType = "现货" item.SymbolType = "现货"
if v, ok := exchangeSpotMap[v.ExchangeType+"_"+v.Symbol]; ok {
if v.LastPrice != "" {
item.LastPrice = v.LastPrice
}
}
} else { } else {
item.SymbolType = "合约" item.SymbolType = "合约"
if v, ok := exchangeFutMap[v.ExchangeType+"_"+v.Symbol]; ok {
if v.LastPrice != "" {
item.LastPrice = v.LastPrice
}
}
} }
datas = append(datas, item) datas = append(datas, item)

View File

@ -51,7 +51,7 @@ func (e *LineUser) GetPage(c *dto.LineUserGetPageReq, p *actions.DataPermission,
Scopes( Scopes(
cDto.MakeCondition(c.GetNeedSearch()), cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p), // actions.Permission(data.TableName(), p),
). ).
Find(list).Limit(-1).Offset(-1). Find(list).Limit(-1).Offset(-1).
Count(count).Error Count(count).Error
@ -225,15 +225,16 @@ func (e *LineUser) AddApiKey(userId int, req *dto.AddApiKeyReq) int {
} }
err = e.Orm.Model(&models.LineApiUser{}).Create(&models.LineApiUser{ err = e.Orm.Model(&models.LineApiUser{}).Create(&models.LineApiUser{
UserId: int64(userId), ExchangeType: req.ExchangeType,
ApiName: req.ApiName, UserId: int64(userId),
ApiKey: req.ApiKey, ApiName: req.ApiName,
ApiSecret: req.ApiSecret, ApiKey: req.ApiKey,
Affiliation: 3, ApiSecret: req.ApiSecret,
AdminShow: 0, Affiliation: 3,
Site: "3", AdminShow: 0,
Subordinate: "0", Site: "3",
OpenStatus: 0, Subordinate: "0",
OpenStatus: 0,
}).Error }).Error
if err != nil { if err != nil {
@ -743,6 +744,8 @@ func (e *LineUser) OpenStatus(req *dto.OpenStatusReq, userId int) int {
propperty := dto.LineUserPropertyResp{} propperty := dto.LineUserPropertyResp{}
e.GetProperty(userId, &propperty) e.GetProperty(userId, &propperty)
logger.Infof("合约可用资产:%v", propperty.FuturesFreeAmount)
logger.Infof("现货可用资产:%v", propperty.SpotFreeAmount)
//可用资产不足 //可用资产不足
if propperty.FuturesFreeAmount.Cmp(userSet.MinOrderAmount) < 0 && propperty.SpotFreeAmount.Cmp(userSet.MinOrderAmount) < 0 { if propperty.FuturesFreeAmount.Cmp(userSet.MinOrderAmount) < 0 && propperty.SpotFreeAmount.Cmp(userSet.MinOrderAmount) < 0 {
return statuscode.PropertyInsufficient return statuscode.PropertyInsufficient

View File

@ -142,7 +142,7 @@ func (t AutoPlaceOrder) Exec(arg interface{}) error {
preOrderService.Orm = db preOrderService.Orm = db
errs := make([]error, 0) errs := make([]error, 0)
errStr := make([]string, 0) errStr := make([]string, 0)
err := preOrderService.QuickAddPreOrder(&req, nil, &errs) err := preOrderService.QuickAddPreOrder(&req, nil, 0, &errs)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,6 @@
package rediskey
const (
//用户总资产 {exchange_type,user_id}
User_Total_Asset = "u_total_asset:%s:%v"
)

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
) )
@ -20,7 +21,9 @@ import (
*/ */
func CreateHtppProxy(proxyType, proxyAddr string, client *http.Client) error { func CreateHtppProxy(proxyType, proxyAddr string, client *http.Client) error {
// Set up proxy based on type (HTTP, HTTPS, SOCKS5) // Set up proxy based on type (HTTP, HTTPS, SOCKS5)
transport := &http.Transport{} transport := &http.Transport{
TLSHandshakeTimeout: 10 * time.Second,
}
if proxyAddr != "" { if proxyAddr != "" {
if !strings.HasPrefix(proxyAddr, "http://") && !strings.HasPrefix(proxyAddr, "https://") && !strings.HasPrefix(proxyAddr, "socks5://") { if !strings.HasPrefix(proxyAddr, "http://") && !strings.HasPrefix(proxyAddr, "https://") && !strings.HasPrefix(proxyAddr, "socks5://") {
proxyAddr = proxyType + "://" + proxyAddr proxyAddr = proxyType + "://" + proxyAddr

View File

@ -1,18 +1,13 @@
package authservice package authservice
import ( import (
"bytes"
"fmt" "fmt"
"go-admin/common/const/rediskey" "go-admin/common/const/rediskey"
"go-admin/common/helper" "go-admin/common/helper"
statuscode "go-admin/common/status_code" statuscode "go-admin/common/status_code"
ext "go-admin/config"
"go-admin/pkg/cryptohelper/inttostring" "go-admin/pkg/cryptohelper/inttostring"
"io/ioutil"
"net/http"
"time" "time"
"github.com/bytedance/sonic"
log "github.com/go-admin-team/go-admin-core/logger" log "github.com/go-admin-team/go-admin-core/logger"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -496,60 +491,8 @@ func SendGoToneSms(phone, area string, smsType int) int {
} }
//ext.ExtConfig.GoToneSmsConfig //ext.ExtConfig.GoToneSmsConfig
// SmsRequest 用于构建发送短信请求的结构体 // SmsRequest 用于构建发送短信请求的结构体
type SmsRequest struct { // todo 短信cangchu
Recipient string `json:"recipient"` // 收件人电话号码
Message string `json:"message"` // 短信内容
SenderId string `json:"sender_id"` // 发送者名称
Type string `json:"type"`
}
// 创建请求数据
smsRequest := SmsRequest{
Recipient: "+" + area + phone,
SenderId: ext.ExtConfig.GoToneSmsConfig.SenderId,
Message: fmt.Sprintf("欢迎使用 GoTone SMS高速稳定地发送短信至中国大陆及全球用户体验验证码%s。如非本人操作请忽略此信息", smsString),
Type: "plain",
}
// 将请求数据编码为 JSON
requestBody, err := sonic.Marshal(smsRequest)
if err != nil {
log.Error("GoToneSms requestBody Error:", err)
return statuscode.ServerError
}
// 创建 HTTP 请求
req, err := http.NewRequest("POST", ext.ExtConfig.GoToneSmsConfig.APIEndpoint, bytes.NewBuffer(requestBody))
if err != nil {
log.Error("GoToneSms http.NewRequest Error:", err)
return statuscode.ServerError
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+ext.ExtConfig.GoToneSmsConfig.Authorization) // 使用 API 密钥进行身份验证
// 创建 HTTP 客户端并发送请求
client := &http.Client{
Timeout: 10 * time.Second, // 设置请求超时时间
}
resp, err := client.Do(req)
fmt.Println("resp:", resp)
if err != nil {
log.Error("GoToneSms do NewRequest Error:", err)
return statuscode.CaptchaFailInSend
}
defer resp.Body.Close()
// 检查响应状态
if resp.StatusCode != http.StatusOK {
log.Error("GoToneSms do NewRequest Error:", err)
return statuscode.CaptchaFailInSend
}
// 读取响应体
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Error("读取响应体失败:", err)
fmt.Printf("响应体: %s", string(body))
return statuscode.CaptchaFailInSend
//return fmt.Errorf("读取响应体失败: %v", err)
}
// 打印响应内容(调试用) // 打印响应内容(调试用)
//记录短信发送操作 //记录短信发送操作
helper.DefaultRedis.SetStringExpire(registerKey, "1", time.Second*60) helper.DefaultRedis.SetStringExpire(registerKey, "1", time.Second*60)

View File

@ -16,11 +16,11 @@ type Extend struct {
EmailConfig EmailConfig `mapstructure:"emailConfig"` EmailConfig EmailConfig `mapstructure:"emailConfig"`
BinanceSet BinanceConfig `mapstructure:"binanceSet"` //binance配置 BinanceSet BinanceConfig `mapstructure:"binanceSet"` //binance配置
Domain string //网站域名 Domain string //网站域名
GoToneSmsConfig GoToneSmsConfig `mapstructure:"GoToneSmsConfig"`
UDunConfig UDunConfig `mapstructure:"UDunConfig"` UDunConfig UDunConfig `mapstructure:"UDunConfig"`
ProxyUrl string //代理地址 ProxyUrl string //代理地址
CoinGate CoinGateConfig `mapstructure:"coingate"` //coingate钱包 CoinGate CoinGateConfig `mapstructure:"coingate"` //coingate钱包
GoToneSmsConfig GoToneSmsConfig `mapstructure:"GoToneSmsConfig"`
InnoPaas InnoPaasConfig `mapstructure:"innoPaas"` //创蓝短信
} }
type CoinGateConfig struct { type CoinGateConfig struct {
@ -70,6 +70,12 @@ type GoToneSmsConfig struct {
Authorization string `json:"authorization"` Authorization string `json:"authorization"`
} }
type InnoPaasConfig struct {
Url string `json:"url"`
ApiKey string `json:"apiKey"`
Password string `json:"password"`
}
type UDunConfig struct { type UDunConfig struct {
UDunUrl string `json:"UDunUrl"` UDunUrl string `json:"UDunUrl"`
UDunMerchantID string `json:"UDunMerchantID"` UDunMerchantID string `json:"UDunMerchantID"`

View File

@ -11,7 +11,7 @@ settings:
readtimeout: 1 readtimeout: 1
writertimeout: 2 writertimeout: 2
# 数据权限功能开关 # 数据权限功能开关
enabledp: false enabledp: true
logger: logger:
# 日志存放路径 # 日志存放路径
path: temp/logs path: temp/logs
@ -82,6 +82,10 @@ settings:
sender_id: "GoTone SMS" sender_id: "GoTone SMS"
api_endpoint: "https://gosms.one/api/v3/sms/send" api_endpoint: "https://gosms.one/api/v3/sms/send"
authorization: "CVZgh3iIAQpJuvaakQmxOo9q2uOb7Veqs7ls5KIX263d87ee" authorization: "CVZgh3iIAQpJuvaakQmxOo9q2uOb7Veqs7ls5KIX263d87ee"
InnoPaas:
url: "http://intapi.sgap.253.com"
apiKey: "OI1706483"
password: "N4R84hhVvP6505"
#UDun 配置 #UDun 配置
UDunConfig: UDunConfig:

View File

@ -739,7 +739,7 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd
for _, order := range orders { for _, order := range orders {
order.Num = num.String() order.Num = num.String()
if fist && order.OrderCategory == 1 && order.OrderType == 1 && orderExt.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 && orderExt.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) != 0 { if fist && (order.OrderCategory == 3 || order.OrderCategory == 1) && order.OrderType == 1 && orderExt.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 && orderExt.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) != 0 {
//主单第一次且止盈数量不是100% 止盈数量 //主单第一次且止盈数量不是100% 止盈数量
order.Num = num.Mul(orderExt.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String() order.Num = num.Mul(orderExt.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String()
} }
@ -761,7 +761,7 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd
order.Rate = percentag.String() order.Rate = percentag.String()
percentag = percentag.Div(decimal.NewFromInt(100)) percentag = percentag.Div(decimal.NewFromInt(100))
order.Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() order.Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String()
} else if orderExt.Id > 0 { } else if !fist && orderExt.TpTpPriceRatio.Cmp(decimal.Zero) > 0 {
percentag := orderExt.TpTpPriceRatio percentag := orderExt.TpTpPriceRatio
order.Rate = percentag.String() order.Rate = percentag.String()
order.Price = price.Mul(decimal.NewFromInt(1).Add(percentag.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() order.Price = price.Mul(decimal.NewFromInt(1).Add(percentag.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()

View File

@ -0,0 +1,76 @@
package smsservice
import (
"bytes"
"fmt"
statuscode "go-admin/common/status_code"
"go-admin/config"
"io/ioutil"
"net/http"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
)
type GotoneService struct {
}
func (s *GotoneService) SendSMS(area, phonenumber string, content string) int {
type SmsRequest struct {
Recipient string `json:"recipient"` // 收件人电话号码
Message string `json:"message"` // 短信内容
SenderId string `json:"sender_id"` // 发送者名称
Type string `json:"type"`
}
// 创建请求数据
smsRequest := SmsRequest{
Recipient: "+" + area + phonenumber,
SenderId: config.ExtConfig.GoToneSmsConfig.SenderId,
Message: fmt.Sprintf("欢迎使用 GoTone SMS高速稳定地发送短信至中国大陆及全球用户体验验证码%s。如非本人操作请忽略此信息", content),
Type: "plain",
}
// 将请求数据编码为 JSON
requestBody, err := sonic.Marshal(smsRequest)
if err != nil {
logger.Error("GoToneSms requestBody Error:", err)
return statuscode.ServerError
}
// 创建 HTTP 请求
req, err := http.NewRequest("POST", config.ExtConfig.GoToneSmsConfig.APIEndpoint, bytes.NewBuffer(requestBody))
if err != nil {
logger.Error("GoToneSms http.NewRequest Error:", err)
return statuscode.ServerError
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+config.ExtConfig.GoToneSmsConfig.Authorization) // 使用 API 密钥进行身份验证
// 创建 HTTP 客户端并发送请求
client := &http.Client{
Timeout: 10 * time.Second, // 设置请求超时时间
}
resp, err := client.Do(req)
fmt.Println("resp:", resp)
if err != nil {
logger.Error("GoToneSms do NewRequest Error:", err)
return statuscode.CaptchaFailInSend
}
defer resp.Body.Close()
// 检查响应状态
if resp.StatusCode != http.StatusOK {
logger.Error("GoToneSms do NewRequest Error:", err)
return statuscode.CaptchaFailInSend
}
// 读取响应体
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logger.Error("读取响应体失败:", err)
fmt.Printf("响应体: %s", string(body))
return statuscode.CaptchaFailInSend
//return fmt.Errorf("读取响应体失败: %v", err)
}
return statuscode.OK
}

View File

@ -0,0 +1,44 @@
package smsservice
import (
"fmt"
statuscode "go-admin/common/status_code"
"go-admin/config"
"io"
"net/http"
"strings"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
)
type InnopaasService struct {
}
func (i *InnopaasService) SendSMS(area, phoneNumber string, message string) int {
// Implement the logic to send SMS using Innopass API
params := map[string]string{
"account": config.ExtConfig.InnoPaas.ApiKey,
"password": config.ExtConfig.InnoPaas.Password,
"mobile": area + phoneNumber,
"msg": message,
}
payload, _ := sonic.MarshalString(params) // strings.NewReader("{\"account\":\"I7145744\",\"password\":\"password\",\"mobile\":\"0012012074149,0012012074142\",\"msg\":\"Your verification code is 8888\"}")
req, _ := http.NewRequest("POST", config.ExtConfig.InnoPaas.Url, strings.NewReader(payload))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
logger.Error("读取响应体失败:", err)
fmt.Printf("响应体: %s", string(body))
return statuscode.CaptchaFailInSend
//return fmt.Errorf("读取响应体失败: %v", err)
}
return statuscode.OK
}

View File

@ -0,0 +1,13 @@
package smsservice
type SMSService interface {
// SendSMS 发送短信
// area 区号
// phoneNumber 手机号
// message 短信内容
SendSMS(area, phoneNumber string, message string) int
}
func NewSMSService() SMSService {
return &GotoneService{}
}