Compare commits

...

3 Commits

Author SHA1 Message Date
9a954cedc0 1、
Some checks failed
Build / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
build / Build (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
Issue Check Inactive / issue-check-inactive (push) Has been cancelled
2025-08-28 11:43:11 +08:00
1f2b337642 1、去掉限流
Some checks failed
Build / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
build / Build (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
Issue Check Inactive / issue-check-inactive (push) Has been cancelled
2、支付回调
2025-07-12 17:21:11 +08:00
f3ca87fb54 1、在线支付
Some checks failed
Build / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
build / Build (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
2、查询平台剩余字符数
2025-07-07 19:01:54 +08:00
30 changed files with 1176 additions and 151 deletions

View File

@ -116,6 +116,12 @@ func (e TmPlatform) Insert(c *gin.Context) {
e.Error(500, err, err.Error())
return
}
if err1 := req.Validate(); err1 != nil {
e.Error(500, err1, err1.Error())
return
}
// 设置创建人
req.SetCreateBy(user.GetUserId(c))
@ -152,6 +158,12 @@ func (e TmPlatform) Update(c *gin.Context) {
e.Error(500, err, err.Error())
return
}
if err1 := req.Validate(); err1 != nil {
e.Error(500, err1, err1.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
p := actions.GetPermissionFromContext(c)

View File

@ -190,3 +190,28 @@ func (e TmPlatformAccount) Delete(c *gin.Context) {
}
e.OK(req.GetId(), "删除成功")
}
// checkRemain 查询剩余翻译条数
func (e TmPlatformAccount) QueryRemain(c *gin.Context) {
req := dto.TmPlatformAccountQueryRemainReq{}
s := service.TmPlatformAccount{}
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.QueryRemain(&req, p)
if err != nil {
e.Error(500, err, fmt.Sprintf("查询剩余翻译条数失败,\r\n失败信息 %s", err.Error()))
return
}
e.OK(nil, "查询成功")
}

View File

@ -60,19 +60,22 @@ func (e TmRechargeLog) CreateOrder(c *gin.Context) {
return
}
userId := user.GetUserId(c)
apiKey := c.GetString("apiKey")
if code := req.Validate(); code != statuscode.Success {
e.Error(code, nil, statuscode.ErrorMessage[code])
}
code := s.CreateOrder(&req, apiKey)
req.UserId = userId
resp := dto.CustomCreateOrderResp{}
code := s.CreateOrder(&req, &resp, apiKey)
if code != statuscode.Success {
e.OK(code, statuscode.ErrorMessage[code])
e.Error(code, nil, statuscode.ErrorMessage[code])
return
}
e.OK(nil, "success")
e.OK(resp, "success")
}
// 后台充值
@ -107,6 +110,29 @@ func (e TmRechargeLog) ManagerRecharge(c *gin.Context) {
e.OK(nil, "充值成功")
}
// 后台扣除
func (e TmRechargeLog) ManagerDeduct(c *gin.Context) {
s := service.TmRechargeLog{}
req := dto.TmRechargeManageDeductReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Error(500, err, "")
return
}
err = s.ManagerDeduct(&req)
if err != nil {
e.Error(500, err, "")
return
}
e.OK(nil, "扣除成功")
}
// 获取即将过期充值记录
func (e TmMember) GetMemberAdvent(c *gin.Context) {
s := service.TmRechargeLog{}
@ -133,3 +159,28 @@ func (e TmMember) GetMemberAdvent(c *gin.Context) {
e.OK(datas, "success")
}
// 获取充值订单详情
func (e *TmRechargeLog) GetOrderInfo(c *gin.Context) {
req := dto.TmRechargeLogGetOrderInfoReq{}
s := service.TmRechargeLog{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Error(500, err, "")
return
}
resp := dto.TmRechargeLogGetOrderInfoResp{}
err = s.GetOrderInfo(&req, &resp)
if err != nil {
e.Error(500, nil, err.Error())
return
}
e.OK(resp, "success")
}

View File

@ -42,7 +42,7 @@ func (e Translate) Translate(c *gin.Context) {
if code != statuscode.Success {
e.Logger.Error(err)
e.OK(code, statuscode.ErrorMessage[code])
e.Error(code, nil, statuscode.ErrorMessage[code])
return
}

View File

@ -16,6 +16,8 @@ type TmPlatform struct {
Code string `json:"code" gorm:"type:varchar(20);comment:平台编码(字典 tm_platform)"`
Character int `json:"character" gorm:"type:int;comment:字符数"`
Price decimal.Decimal `json:"price" gorm:"type:decimal(10,2);comment:单价"`
BlockChain string `json:"blockChain" gorm:"type:varchar(20);comment:区块链(全小写)"`
ReceiveAddress string `json:"receiveAddress" gorm:"type:varchar(100);comment:接收地址"`
models.ModelTime
models.ControlBy
}

View File

@ -26,7 +26,8 @@ func registerTmMemberRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddl
r.DELETE("", api.Delete)
// r.POST("recharge", actions.PermissionAction(), api.Recharge) //字符充值
r.POST("manager-recharge", actions.PermissionAction(), rechargeApi.ManagerRecharge)
r.POST("manager-recharge", actions.PermissionAction(), rechargeApi.ManagerRecharge) //手动充值
r.PUT("manager-deduct", actions.PermissionAction(), rechargeApi.ManagerDeduct) //手动扣除
r.PUT("status", actions.PermissionAction(), api.ChangeStatus) //状态变更
}
@ -35,5 +36,8 @@ func registerTmMemberRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddl
r2.GET("/api-key", api.GetMyApiKey)
r2.GET("platforms", api.GetPlatforms)
r2.GET("member-advent", api.GetMemberAdvent) //获取用户即将过期的充值信息
r2.POST("recharge", rechargeApi.CreateOrder) //用户发起充值
r2.GET("order", rechargeApi.GetOrderInfo) //获取充值订单信息
}
}

View File

@ -5,8 +5,8 @@ import (
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
"go-admin/common/actions"
"go-admin/common/middleware"
)
func init() {
@ -23,5 +23,7 @@ func registerTmPlatformAccountRouter(v1 *gin.RouterGroup, authMiddleware *jwt.Gi
r.POST("", api.Insert)
r.PUT("/:id", actions.PermissionAction(), api.Update)
r.DELETE("", api.Delete)
r.PUT("query-remain/:id", actions.PermissionAction(), api.QueryRemain) //查询平台剩余字符
}
}

View File

@ -1,9 +1,11 @@
package dto
import (
"errors"
"go-admin/app/admin/models"
"go-admin/common/dto"
common "go-admin/common/models"
"go-admin/utils/chainhelper"
"github.com/shopspring/decimal"
)
@ -48,9 +50,35 @@ type TmPlatformInsertReq struct {
Code string `json:"code" comment:"平台编码(字典 tm_platform)"`
Character int `json:"character" comment:"字符数"`
Price decimal.Decimal `json:"price" comment:"单价"`
BlockChain string `json:"blockChain" comment:"链区块"`
ReceiveAddress string `json:"receiveAddress" comment:"接收地址"`
common.ControlBy
}
func (s *TmPlatformInsertReq) Validate() error {
if s.BlockChain == "" {
return errors.New("收款区块不能为空")
}
if s.ReceiveAddress == "" {
return errors.New("接收地址不能为空")
}
if s.Name == "" {
return errors.New("平台名称不能为空")
}
if s.ShowName == "" {
return errors.New("展示名称不能为空")
}
if s.Code == "" {
return errors.New("平台编码不能为空")
}
return chainhelper.JudgeChainAddress(s.BlockChain, s.ReceiveAddress)
}
func (s *TmPlatformInsertReq) Generate(model *models.TmPlatform) {
if s.Id == 0 {
model.Model = common.Model{Id: s.Id}
@ -63,6 +91,8 @@ func (s *TmPlatformInsertReq) Generate(model *models.TmPlatform) {
model.Character = s.Character
model.Price = s.Price
model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
model.BlockChain = s.BlockChain
model.ReceiveAddress = s.ReceiveAddress
}
func (s *TmPlatformInsertReq) GetId() interface{} {
@ -85,9 +115,35 @@ type TmPlatformUpdateReq struct {
Code string `json:"code" comment:"平台编码(字典 tm_platform)"`
Character int `json:"character" comment:"字符数"`
Price decimal.Decimal `json:"price" comment:"单价"`
BlockChain string `json:"blockChain" comment:"链区块"`
ReceiveAddress string `json:"receiveAddress" comment:"接收地址"`
common.ControlBy
}
func (s *TmPlatformUpdateReq) Validate() error {
if s.BlockChain == "" {
return errors.New("收款区块不能为空")
}
if s.ReceiveAddress == "" {
return errors.New("接收地址不能为空")
}
if s.Name == "" {
return errors.New("平台名称不能为空")
}
if s.ShowName == "" {
return errors.New("展示名称不能为空")
}
if s.Code == "" {
return errors.New("平台编码不能为空")
}
return chainhelper.JudgeChainAddress(s.BlockChain, s.ReceiveAddress)
}
func (s *TmPlatformUpdateReq) Generate(model *models.TmPlatform) {
if s.Id == 0 {
model.Model = common.Model{Id: s.Id}
@ -100,6 +156,8 @@ func (s *TmPlatformUpdateReq) Generate(model *models.TmPlatform) {
model.Character = s.Character
model.Price = s.Price
model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
model.BlockChain = s.BlockChain
model.ReceiveAddress = s.ReceiveAddress
}
func (s *TmPlatformUpdateReq) GetId() interface{} {

View File

@ -95,3 +95,7 @@ type TmPlatformAccountDeleteReq struct {
func (s *TmPlatformAccountDeleteReq) GetId() interface{} {
return s.Ids
}
type TmPlatformAccountQueryRemainReq struct {
Id int `uri:"id"`
}

View File

@ -109,16 +109,45 @@ type TmRechargeCreateOrderReq struct {
common.ControlBy
}
// 后台扣除字符
type TmRechargeManageDeductReq struct {
MemberId int `json:"memberId" comment:"会员id"`
UserId int `json:"userId" comment:"用户id"`
PlatformId int `json:"platformId" comment:"平台id"`
TotalChars decimal.Decimal `json:"totalChars" comment:"扣减字符"`
}
func (e *TmRechargeManageDeductReq) Generate(entity *models.TmRechargeLog) error {
if entity == nil {
entity = &models.TmRechargeLog{}
}
if e.MemberId <= 0 {
return errors.New("会员不存在")
}
if e.PlatformId <= 0 {
return errors.New("平台不存在")
}
entity.MemberId = e.MemberId
entity.UserId = e.UserId
entity.PlatformId = e.PlatformId
entity.Type = 3
return nil
}
// 用户充值订单创建请求参数
type CustomCreateOrderReq struct {
UserId int `json:"userId" comment:"用户id"`
Amount decimal.Decimal `json:"amount" comment:"充值金额"`
PlatformId int `json:"platformId" comment:"平台id"`
Count int `json:"count" comment:"购买数量"`
}
func (e *CustomCreateOrderReq) Validate() int {
if e.Amount.Cmp(decimal.Zero) <= 0 {
return statuscode.RechargeAmountMustBeGreaterThanZero
if e.Count <= 0 {
return statuscode.RechargeNumberMustBeGreaterThanZero
}
if e.PlatformId <= 0 {
@ -128,6 +157,14 @@ func (e *CustomCreateOrderReq) Validate() int {
return statuscode.Success
}
type CustomCreateOrderResp struct {
OrderNo string `json:"orderNo"`
Amount string `json:"amount"`
BlockChain string `json:"blockChain"`
ReceiveAddress string `json:"receiveAddress"`
ExpireUnix int64 `json:"expireUnix" comment:"过期时间戳 秒"`
}
func (e *TmRechargeCreateOrderReq) Validate() error {
if e.ExpireDays <= 0 {
return errors.New("过期天数必须大于0")
@ -205,3 +242,13 @@ type TmRechargeCallbackReq struct {
ToAddress string `json:"to_address"`
TxHash string `json:"tx_hash"`
}
type TmRechargeLogGetOrderInfoReq struct {
OrderNo string `json:"orderNo" form:"orderNo" query:"orderNo" comment:"订单号"`
}
type TmRechargeLogGetOrderInfoResp struct {
Id int `json:"id"`
OrderNo string `json:"orderNo"`
Status int `json:"status"`
}

View File

@ -66,3 +66,11 @@ type TranslateUserInfoResp struct {
UserApiKey string `json:"userApiKey" comment:"用户API Key"`
RemainChars int `json:"remainChars" comment:"剩余可翻译字符数"`
}
type DenosiTranslateRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Temperature float64 `json:"temperature"`
MaxTokens int `json:"max_tokens"`
Stream bool `json:"stream"`
}

View File

@ -21,6 +21,128 @@ type TmPlatformAccount struct {
service.Service
}
func (e TmPlatformAccount) QueryRemain(req *dto.TmPlatformAccountQueryRemainReq, p *actions.DataPermission) error {
var entity models.TmPlatformAccount
err := e.Orm.Model(&entity).
Scopes(
actions.Permission(entity.TableName(), p),
).
First(&entity, req.Id).Error
if err != nil {
return errors.New("查看对象不存在或无权查看")
}
err = e.SyncPlatformAccount(entity)
return err
}
func (e *TmPlatformAccount) SyncPlatformAccount(entity models.TmPlatformAccount) error {
platformService := TmPlatform{Service: e.Service}
platform, err := platformService.GetById(entity.PlatformId)
if err != nil {
e.Log.Errorf("获取翻译平台消息失败 %v", err)
return errors.New("获取翻译平台消息失败")
}
provider := map[string]interface{}{
entity.PlatformKey: map[string]interface{}{
"apiKey": entity.ApiKey,
"endpoint": platform.ApiBaseUrl,
},
}
config := TranslatorServiceConfig{
DefaultProvider: platform.Code,
ProviderConfigs: provider,
}
translator, err := NewTranslatorService(&config)
if err != nil {
e.Log.Errorf("创建翻译服务失败 %v", err)
return errors.New("创建翻译服务失败")
}
remainCount, err := translator.providers[entity.PlatformKey].GetRemainCount()
if err != nil {
e.Log.Errorf("获取翻译平台剩余字符失败 %v", err)
return errors.New("获取翻译平台剩余字符失败")
}
if remainCount > 0 {
key := fmt.Sprintf(rediskey.TM_PLATEFORM_ACCOUNT_REMAIN_KEY, entity.PlatformKey, entity.ApiKey)
redishelper.DefaultRedis.SetString(key, strconv.Itoa(remainCount))
// //如果为禁用则写入队列
// if entity.Status == 2 {
// listKey := fmt.Sprintf(rediskey.TM_PLATFORM_ACCOUNT_LIST_KEY, entity.PlatformKey)
// vals, err := redishelper.DefaultRedis.GetAllList(listKey)
// if err != nil {
// e.Log.Errorf("获取redis列表失败 %v", err)
// return errors.New("获取redis列表失败")
// }
// item := models.TmPlatformAccount{}
// hasData := false
// for _, val := range vals {
// sonic.UnmarshalString(val, &item)
// if item.ApiKey == entity.ApiKey {
// hasData = true
// break
// }
// }
// if !hasData {
// entity.Status = 1
// val, err := sonic.MarshalString(entity)
// if err != nil {
// return err
// }
// err = e.Orm.Transaction(func(tx *gorm.DB) error {
// if err1 := redishelper.DefaultRedis.RPushList(listKey, val); err1 != nil {
// return err
// }
// if err1 := tx.Model(entity).Update("status", 1).Error; err1 != nil {
// return err1
// }
// return nil
// })
// return nil
// }
// }
}
return nil
}
// 同步所有第三方账号余额
func (e *TmPlatformAccount) SyncAll() error {
var datas []models.TmPlatformAccount
e.Orm.Model(&models.TmPlatformAccount{}).Where("status =1").Find(&datas)
platformService := TmPlatform{}
platformService.Orm = e.Orm
platformService.Log = e.Log
for _, item := range datas {
if err := e.SyncPlatformAccount(item); err != nil {
e.Log.Errorf("同步平台账号余额 密钥:%s 失败 %v", item.ApiKey, err)
}
}
return nil
}
// GetPage 获取TmPlatformAccount列表
func (e *TmPlatformAccount) GetPage(c *dto.TmPlatformAccountGetPageReq, p *actions.DataPermission, list *[]models.TmPlatformAccount, count *int64) error {
var err error
@ -38,6 +160,18 @@ func (e *TmPlatformAccount) GetPage(c *dto.TmPlatformAccountGetPageReq, p *actio
e.Log.Errorf("TmPlatformAccountService GetPage error:%s \r\n", err)
return err
}
if list != nil {
for index, item := range *list {
keu := fmt.Sprintf(rediskey.TM_PLATEFORM_ACCOUNT_REMAIN_KEY, item.PlatformKey, item.ApiKey)
val, err := redishelper.DefaultRedis.GetString(keu)
if err != nil {
continue
}
(*list)[index].RemainChars, _ = strconv.Atoi(val)
}
}
return nil
}

View File

@ -16,8 +16,10 @@ import (
"go-admin/utils/utility"
"sort"
"strconv"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/sdk/service"
"github.com/go-redis/redis/v8"
"github.com/shopspring/decimal"
@ -28,6 +30,42 @@ type TmRechargeLog struct {
service.Service
}
func (e TmRechargeLog) GetOrderInfo(req *dto.TmRechargeLogGetOrderInfoReq, resp *dto.TmRechargeLogGetOrderInfoResp) error {
var data models.TmRechargeLog
if err := e.Orm.Model(&models.TmRechargeLog{}).
Where("order_no =?", req.OrderNo).
First(&data).Error; err != nil {
e.Log.Errorf("TmRechargeLogService GetOrderInfo error:%s \r\n", err)
return err
}
resp.Id = data.Id
resp.OrderNo = data.OrderNo
resp.Status = data.Status
return nil
}
// 清理过期订单
func (e TmRechargeLog) CleanExpiredOrder() error {
expireTime := time.Now().Add(5 * time.Minute)
ctx := context.Background()
err := e.Orm.Transaction(func(tx *gorm.DB) error {
if err1 := tx.Model(&models.TmRechargeLog{}).Where("order_expire_time <? and status =1", expireTime).Updates(map[string]interface{}{"status": 6, "updated_at": time.Now()}).Error; err1 != nil {
return err1
}
if err1 := receiveaddressmanager.CleanExpiredAmountLocks(ctx, redishelper.DefaultRedis.GetClient()); err1 != nil {
return err1
}
return nil
})
return err
}
// 首页获取即将过期的数据
func (e *TmRechargeLog) GetMemberAdvent(req *dto.TmRechargeLogFrontReq, resp *[]dto.TmRechargeLogFrontResp, userId int) int {
var datas []models.TmRechargeLog
@ -68,7 +106,7 @@ func (e *TmRechargeLog) GetMemberAdvent(req *dto.TmRechargeLogFrontReq, resp *[]
func (e *TmRechargeLog) ManagerRecharge(req *dto.TmRechargeCreateOrderReq, p *actions.DataPermission) error {
ctx := context.Background()
var data models.TmRechargeLog
member, platform, memberPlatform, code := e.CreateOrderJudge(req)
member, platform, _, code := e.CreateOrderJudge(req.MemberId, req.PlatformId)
if code != statuscode.Success {
return errors.New(statuscode.ErrorMessage[code])
}
@ -79,6 +117,7 @@ func (e *TmRechargeLog) ManagerRecharge(req *dto.TmRechargeCreateOrderReq, p *ac
data.Type = 2
data.Status = 2
data.UserId = member.UserId
data.MemberId = member.Id
data.ExpireAt = time.Now().AddDate(0, 0, req.ExpireDays)
data.PayTime = &now
@ -96,10 +135,10 @@ func (e *TmRechargeLog) ManagerRecharge(req *dto.TmRechargeCreateOrderReq, p *ac
return err1
}
//更新用户翻译可用字符
if err1 := tx.Model(&models.TmMemberPlatform{}).Where("id =?", memberPlatform.Id).Update("remaining_character", data.TotalChars).Error; err1 != nil {
return err1
}
// //更新用户翻译可用字符
// if err1 := tx.Model(&models.TmMemberPlatform{}).Where("id =?", memberPlatform.Id).Update("remaining_character", data.TotalChars).Error; err1 != nil {
// return err1
// }
//写入可用字符
if err1 := qmgr.AddQuota(ctx, member.ApiKey, platform.Code, rechargeData); err1 != nil {
@ -112,11 +151,50 @@ func (e *TmRechargeLog) ManagerRecharge(req *dto.TmRechargeCreateOrderReq, p *ac
return err
}
// 后台扣除字符
func (e *TmRechargeLog) ManagerDeduct(req *dto.TmRechargeManageDeductReq) error {
ctx := context.Background()
var data models.TmRechargeLog
member, platform, _, code := e.CreateOrderJudge(req.MemberId, req.PlatformId)
if code != statuscode.Success {
return errors.New(statuscode.ErrorMessage[code])
}
req.Generate(&data)
now := time.Now()
data.OrderNo = utility.GenerateTraceID()
data.Status = 2
data.UserId = member.UserId
data.ExpireAt = time.Now().AddDate(0, 0, 30)
data.PayTime = &now
data.TotalChars = int(req.TotalChars.Mul(decimal.NewFromInt(10000)).IntPart())
qmgr := quota_manager.NewQuotaManager(redishelper.DefaultRedis.GetClient())
// 事务处理
err := e.Orm.Transaction(func(tx *gorm.DB) error {
//写入充值记录
if err1 := e.Orm.Create(&data).Error; err1 != nil {
return err1
}
//写入可用字符
if _, err1 := qmgr.Deduct(ctx, member.ApiKey, platform.Code, int64(data.TotalChars)); err1 != nil {
return err1
}
return nil
})
return err
}
// 新增充值校验
func (e *TmRechargeLog) CreateOrderJudge(req *dto.TmRechargeCreateOrderReq) (models.TmMember, *models.TmPlatform, models.TmMemberPlatform, int) {
func (e *TmRechargeLog) CreateOrderJudge(memberId int, platformId int) (models.TmMember, *models.TmPlatform, models.TmMemberPlatform, int) {
memberService := TmMember{Service: e.Service}
member := models.TmMember{}
if err := memberService.GetById(req.MemberId, &member); err != nil {
if err := memberService.GetById(memberId, &member); err != nil {
return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.NotFindMember
}
@ -125,7 +203,7 @@ func (e *TmRechargeLog) CreateOrderJudge(req *dto.TmRechargeCreateOrderReq) (mod
}
platformService := TmPlatform{Service: e.Service}
platform, err := platformService.GetById(req.PlatformId)
platform, err := platformService.GetById(platformId)
if err != nil {
e.Log.Errorf("获取平台信息失败:%s \r\n", err.Error())
@ -136,10 +214,15 @@ func (e *TmRechargeLog) CreateOrderJudge(req *dto.TmRechargeCreateOrderReq) (mod
return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.PlatformNotSupport
}
if platform.BlockChain == "" || platform.ReceiveAddress == "" {
e.Log.Error("翻译平台未配置收款信息")
return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.PlatformNotSupport
}
memberPlatformService := TmMemberPlatform{Service: e.Service}
memberPlatform, err := memberPlatformService.GetOrInsert(&dto.TmRechargeLogInsertOrUpdateReq{
MemberId: req.MemberId,
PlatformId: req.PlatformId,
MemberId: memberId,
PlatformId: platformId,
PlatformKey: platform.Code,
})
@ -155,7 +238,7 @@ func (e *TmRechargeLog) CreateOrderJudge(req *dto.TmRechargeCreateOrderReq) (mod
// 用户自己发起充值
// return code
func (e TmRechargeLog) CreateOrder(req *dto.CustomCreateOrderReq, apiKey string) int {
func (e TmRechargeLog) CreateOrder(req *dto.CustomCreateOrderReq, resp *dto.CustomCreateOrderResp, apiKey string) int {
ctx := context.Background()
var amountStr string
// var errReturn bool
@ -202,16 +285,22 @@ func (e TmRechargeLog) CreateOrder(req *dto.CustomCreateOrderReq, apiKey string)
return statuscode.ServerError
}
_, platform, _, code := e.CreateOrderJudge(member.Id, req.PlatformId)
if code != statuscode.Success {
return code
}
createReq := dto.TmRechargeCreateOrderReq{}
createReq.MemberId = member.Id
createReq.Amount = req.Amount
createReq.Amount = platform.Price.Mul(decimal.NewFromInt(int64(req.Count)))
createReq.PlatformId = req.PlatformId
createReq.ReceiveAddress = config.ConfigValue
createReq.ReceiveChannel = "TRX"
createReq.ReceiveAddress = platform.ReceiveAddress
createReq.ReceiveChannel = platform.BlockChain
createReq.Type = 1
createReq.UserId = req.UserId
amountStr, err = receiveaddressmanager.AllocatePaymentAmount(ctx, redishelper.DefaultRedis.GetClient(), req.Amount.InexactFloat64(), timeInterval)
amountStr, err = receiveaddressmanager.AllocatePaymentAmount(ctx, redishelper.DefaultRedis.GetClient(), createReq.Amount.InexactFloat64(), timeInterval)
if err != nil {
e.Log.Errorf("分配可用金额失败:%v \r\n", err)
@ -225,12 +314,6 @@ func (e TmRechargeLog) CreateOrder(req *dto.CustomCreateOrderReq, apiKey string)
return statuscode.ServerError
}
_, platform, _, code := e.CreateOrderJudge(&createReq)
if code != statuscode.Success {
return code
}
data := models.TmRechargeLog{}
data.Amount = createReq.Amount
platformCharacter := int64(platform.Character * 1000000)
@ -240,6 +323,7 @@ func (e TmRechargeLog) CreateOrder(req *dto.CustomCreateOrderReq, apiKey string)
data.Type = createReq.Type
data.Status = 1
data.UserId = member.UserId
data.MemberId = member.Id
data.PlatformId = platform.Id
data.ReceiveAddress = createReq.ReceiveAddress
data.ReceiveChannel = createReq.ReceiveChannel
@ -266,13 +350,30 @@ func (e TmRechargeLog) CreateOrder(req *dto.CustomCreateOrderReq, apiKey string)
return nil
})
if err != nil {
return statuscode.ServerError
}
resp.Amount = data.Amount.String()
resp.ReceiveAddress = data.ReceiveAddress
resp.OrderNo = data.OrderNo
resp.BlockChain = strings.ToUpper(data.ReceiveChannel)
resp.ExpireUnix = data.OrderExpireTime.Unix()
return statuscode.Success
}
// 充值回调
func (e *TmRechargeLog) PayCallBack(logs *[]dto.TmRechargeCallbackReq) error {
now := time.Now()
qmgr := quota_manager.NewQuotaManager(redishelper.DefaultRedis.GetClient())
ctx := context.Background()
memberService := TmMember{Service: e.Service}
platformService := TmPlatform{Service: e.Service}
for _, log := range *logs {
key := fmt.Sprintf(rediskey.TM_RECHARGE_PRE_ORDER, log.PayableAmount.String())
amountStr := log.PayableAmount.StringFixed(4)
key := fmt.Sprintf(rediskey.TM_RECHARGE_PRE_ORDER, amountStr)
orderNo, err := redishelper.DefaultRedis.GetString(key)
if err != nil {
@ -282,6 +383,14 @@ func (e *TmRechargeLog) PayCallBack(logs *[]dto.TmRechargeCallbackReq) error {
continue
}
var count int64
e.Orm.Model(&models.TmRechargeLog{}).Where("tx_hash = ?", log.TxHash).Count(&count)
if count > 0 {
e.Log.Errorf("订单已存在:%s \r\n", log.TxHash)
continue
}
var data models.TmRechargeLog
if err := e.Orm.Model(&models.TmRechargeLog{}).Where("order_no = ?", orderNo).First(&data).Error; err != nil {
e.Log.Errorf("获取充值记录失败 error:%s \r\n", err)
@ -289,7 +398,47 @@ func (e *TmRechargeLog) PayCallBack(logs *[]dto.TmRechargeCallbackReq) error {
}
if data.Status == 1 {
data.TxHash = log.TxHash
data.Status = 2
data.PayTime = &now
data.ExpireAt = time.Now().AddDate(0, 0, 30)
member := models.TmMember{}
err := memberService.GetById(data.MemberId, &member)
if err != nil {
e.Log.Errorf("获取用户信息失败 error:%s \r\n", err)
continue
}
platform, err := platformService.GetById(data.PlatformId)
if err != nil {
e.Log.Errorf("获取平台信息失败 error:%s \r\n", err)
continue
}
rechargeData := quota_manager.QuotaRecharge{
QuotaID: data.OrderNo,
Amount: int64(data.TotalChars),
ExpireAt: data.ExpireAt.Unix(),
}
err = e.Orm.Transaction(func(tx *gorm.DB) error {
if err1 := tx.Model(&models.TmRechargeLog{}).Where("id =?", data.Id).Updates(map[string]interface{}{"tx_hash": log.TxHash, "status": 2, "pay_time": now}).Error; err1 != nil {
e.Log.Errorf("发起充值记录失败 error:%s \r\n", err1)
return err1
}
//写入可用字符
if err1 := qmgr.AddQuota(ctx, member.ApiKey, platform.Code, rechargeData); err1 != nil {
return err1
}
return nil
})
if err != nil {
e.Log.Errorf("写入可用字符失败 error:%s \r\n", err)
continue
}
} else {
e.Log.Errorf("订单状态异常:%s 状态:%d\r\n", orderNo, data.Status)
}
@ -334,3 +483,28 @@ func (e *TmRechargeLog) GetRemainByOrderNo(orderNo string) (int, error) {
return strconv.Atoi(val)
}
func (e *TmRechargeLog) GetPlatforms() ([]models.TmPlatform, error) {
key1 := fmt.Sprintf(rediskey.TM_PLATFORM_KEY, "*")
scankeys, err := redishelper.DefaultRedis.ScanKeys(key1)
if err != nil {
return nil, err
}
platforms := make([]models.TmPlatform, 0)
for _, key := range scankeys {
val, err := redishelper.DefaultRedis.GetString(key)
if err != nil {
continue
}
var platform models.TmPlatform
sonic.Unmarshal([]byte(val), &platform)
if platform.Id > 0 {
platforms = append(platforms, platform)
}
}
return platforms, nil
}

View File

@ -31,3 +31,32 @@ func TestDeepSeekTranslator(t *testing.T) {
}
fmt.Println(result)
}
// 测试DeepSeekTranslator方法
func TestDenosiTranslator(t *testing.T) {
config := TranslatorServiceConfig{
DefaultProvider: "denosi",
ProviderConfigs: map[string]interface{}{
"denosi": map[string]interface{}{
"apiKey": "sk-PRXfCgLAefqo6jeYsJhKCU6hw5gmxkfNAOjISSQfOeHxqDw4",
"endpoint": "https://api.denosi.com/v1/chat/completions",
"model": "gpt-4o-mini",
},
},
}
service, err := NewTranslatorService(&config)
if err != nil {
fmt.Sprintln("报错:", err)
return
}
result, err := service.providers["denosi"].Translate("成吉思汗", "zh", "fr")
if err != nil {
fmt.Sprintln("报错:", err)
return
}
fmt.Println(result)
}

View File

@ -0,0 +1,112 @@
package service
import (
"bytes"
"encoding/json"
"fmt"
"go-admin/app/admin/service/dto"
"go-admin/utils/httphelper"
"io/ioutil"
"net/http"
"time"
"github.com/bytedance/sonic"
)
type DenosiTranslatorConfig struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
Endpoint string `json:"endpoint"`
Model string `json:"model"`
}
type DenosiTranslator struct {
config *DenosiTranslatorConfig
client *httphelper.HTTPClient
}
// 初始化适配器实例
func NewDenosiTranslator(config *DenosiTranslatorConfig) *DenosiTranslator {
defaultHeaders := map[string]string{
"Content-Type": "application/json",
}
httpClient := httphelper.NewHTTPClient(
15*time.Second,
config.Endpoint,
defaultHeaders,
)
return &DenosiTranslator{
config: config,
client: httpClient,
}
}
// 翻译
func (e *DenosiTranslator) Translate(text string, source string, target string) (*dto.TranslateResult, error) {
// TODO: 实现Deepseek API调用
result := dto.TranslateResult{}
reqBody := dto.DenosiTranslateRequest{
Model: e.config.Model, // v3 fast模型
Stream: false,
Messages: []dto.Message{
{Role: "system", Content: fmt.Sprintf("你是翻译专家,将内容从%s翻译为%s仅返回翻译结果", source, target)},
{Role: "user", Content: fmt.Sprintf("翻译:%s", text)},
},
Temperature: 0,
}
data, err := sonic.Marshal(reqBody)
if err != nil {
return &result, err
}
req, err := http.NewRequest("POST", e.config.Endpoint, bytes.NewBuffer(data))
if err != nil {
return &result, err
}
req.Header.Set("Authorization", "Bearer "+e.config.ApiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return &result, err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
if resp.StatusCode != 200 {
return &result, fmt.Errorf("Denosi error: %s", string(body))
}
var dsResp dto.DeepSeekResponse
err = json.Unmarshal(body, &dsResp)
if err != nil {
return &result, err
}
if len(dsResp.Choices) == 0 {
return &result, fmt.Errorf("no translation result from Denosi")
}
result.TranslatedText = dsResp.Choices[0].Message.Content
result.SourceLanguage = source
result.TargetLanguage = target
return &result, nil
}
func (e *DenosiTranslator) GetRemainCount() (int, error) {
// TODO: 实现Deepseek API调用
return 0, nil
}
// 返回服务商
func (e *DenosiTranslator) GetPlatform() string {
return "denosi"
}

View File

@ -6,15 +6,12 @@ import (
"fmt"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/mq"
rediskey "go-admin/common/redis_key"
"go-admin/common/statuscode"
"go-admin/utils/redishelper"
"time"
"unicode/utf8"
commonDto "go-admin/common/dto"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/service"
@ -37,6 +34,9 @@ type TranslatorToolService struct {
service.Service
}
var UnSportPlatformErr = errors.New("平台不支持")
var TransUnMarshalConfigErr = errors.New("翻译服务商配置解析失败")
// NewTranslatorService 创建翻译服务实例
func NewTranslatorService(cfg *TranslatorServiceConfig) (*TranslatorService, error) {
svc := &TranslatorService{
@ -50,30 +50,51 @@ func NewTranslatorService(cfg *TranslatorServiceConfig) (*TranslatorService, err
var newAdapter Translator
switch providerName {
case "google":
googleCfgBytes, _ := json.Marshal(providerCfg) // 假设配置是map[string]interface{},需要转换
googleCfgBytes, _ := json.Marshal(providerCfg)
var googleCfg GoogleTranslatorConfig
if err := json.Unmarshal(googleCfgBytes, &googleCfg); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal config for GoogleTranslate")
logger.Error("failed to unmarshal config for GoogleTranslate")
return nil, TransUnMarshalConfigErr
}
newAdapter = NewGoogleTranslator(&googleCfg)
// case "BaiduTranslate":
// // newAdapter = adapter.NewBaiduTranslator(...)
case "deepseek":
deepseekCfgBytes, _ := json.Marshal(providerCfg) // 假设配置是map[string]interface{},需要转换
deepseekCfgBytes, _ := json.Marshal(providerCfg)
var deepseekCfg DeepseekTranslatorConfig
if err := json.Unmarshal(deepseekCfgBytes, &deepseekCfg); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal config for DeepseekTranslator")
logger.Error(err, "failed to unmarshal config for DeepseekTranslator")
return nil, TransUnMarshalConfigErr
}
newAdapter = NewDeepseekTranslator(&deepseekCfg)
case "deepl", "deepl_free":
deeplCfgBytes, _ := json.Marshal(providerCfg) // 假设配置是map[string]interface{},需要转换
deeplCfgBytes, _ := json.Marshal(providerCfg)
var deeplCfg DeeplTranslatorConfig
if err := json.Unmarshal(deeplCfgBytes, &deeplCfg); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal config for DeepLTranslator")
logger.Error("failed to unmarshal config for DeepLTranslator")
return nil, TransUnMarshalConfigErr
}
newAdapter = NewDeeplTranslator(&deeplCfg)
//翻译之家
case "deepl_trans", "google_trans", "baidu_trans", "youdao_trans", "bing_trans", "huoshan_trans", "chatgpt-4o_trans", "yandex_trans":
transCfgBytes, _ := json.Marshal(providerCfg)
var transCfg TransTranslatorConfig
if err := json.Unmarshal(transCfgBytes, &transCfg); err != nil {
logger.Error("failed to unmarshal config for Translator")
return nil, TransUnMarshalConfigErr
}
newAdapter = NewTransTranslator(&transCfg)
case "denosi":
transCfgBytes, _ := json.Marshal(providerCfg)
var transCfg DenosiTranslatorConfig
if err := json.Unmarshal(transCfgBytes, &transCfg); err != nil {
logger.Error("failed to unmarshal config for Translator")
return nil, TransUnMarshalConfigErr
}
newAdapter = NewDenosiTranslator(&transCfg)
default:
return nil, errors.Errorf("unsupported translation provider: %s", providerName)
return nil, UnSportPlatformErr
}
svc.RegisterProvider(providerName, newAdapter)
}
@ -149,7 +170,7 @@ func (s *TranslatorService) TranslateJudge(req *dto.TranslateReq, apiKey string)
platformConfigInterface := Translator.config.ProviderConfigs[req.Platform]
// 尝试将 interface{} 断言为 map[string]interface{}
if mapData, ok := platformConfigInterface.(map[string]interface{}); !ok {
if mapData, ok := platformConfigInterface.(map[string]interface{}); ok {
tmPlatformAccount.DecrRemainBy(req.Platform, mapData["apiKey"].(string), count)
}
@ -157,6 +178,7 @@ func (s *TranslatorService) TranslateJudge(req *dto.TranslateReq, apiKey string)
redishelper.DefaultRedis.IncrBy(fmt.Sprintf(rediskey.TM_MEMBER_DAILY_COUNT, date, apiKey, req.Platform), int64(count))
//每日统计保留三天
redishelper.DefaultRedis.Expire(fmt.Sprintf(rediskey.TM_MEMBER_DAILY_COUNT, date, apiKey, req.Platform), 3*24*time.Hour)
} else {
tmMemberService.RefundQuote(ctx, apiKey, req.Platform, &decyDatas)
@ -200,42 +222,38 @@ func (s *TranslatorService) GetTranslator(platform string) (translator *Translat
ProviderConfigs: configs,
}
switch platform {
case "deepl", "deepl_free", "deepseek":
translator, err = NewTranslatorService(&cfg)
if err != nil {
s.Log.Errorf("failed to create translator service: %s", err)
switch err {
case UnSportPlatformErr:
code = statuscode.PlatformNotSupport
default:
code = statuscode.ServerError
return
}
} else {
code = statuscode.Success
}
code = statuscode.Success
return
default:
code = statuscode.PlatformNotSupport
return
}
}
// 从 Redis List 顺序取出一个账号,使用后放回队尾,模拟轮询
func (s *TranslatorService) GetNextAccount(platformCode string) (*models.TmPlatformAccount, error) {
var val string
var err error
var remain int
// var remain int
key := fmt.Sprintf(rediskey.TM_PLATFORM_ACCOUNT_LIST_KEY, platformCode)
retries := []time.Duration{100 * time.Millisecond, 200 * time.Millisecond, 300 * time.Millisecond}
defer func() {
if val != "" {
if remain > 0 {
// 放回队尾,继续轮询机制
if err := redishelper.DefaultRedis.RPushList(key, val); err != nil {
s.Log.Errorf("failed to push account back to queue: %v", err)
}
}
}
}()
// 尝试从 Redis 队列中弹出账号 ID带重试机制
@ -254,35 +272,36 @@ func (s *TranslatorService) GetNextAccount(platformCode string) (*models.TmPlatf
return nil, errors.New("failed to get account from queue after multiple retries: " + err.Error())
}
accountService := TmPlatformAccount{Service: s.Service}
// accountService := TmPlatformAccount{Service: s.Service}
account := models.TmPlatformAccount{}
sonic.UnmarshalString(val, &account)
switch platformCode {
case "deepseek":
remain = 999999
default:
remain, _ = accountService.GetRemainCount(platformCode, account.ApiKey)
}
// switch platformCode {
// case "deepseek":
// remain = 999999
// default:
// remain, _ = accountService.GetRemainCount(platformCode, account.ApiKey)
// }
//取消限制
// 如果剩余字符数 <= 0跳过该账号不放回队列并更新数据库状态
if remain <= 0 {
event := commonDto.ExhaustedAccountMessage{
Id: account.Id,
Platform: platformCode,
}
payload, _ := sonic.Marshal(event)
err = mq.MQ.Publish( // default exchange
"account_exhausted_queue",
payload,
)
// if remain <= 0 {
// event := commonDto.ExhaustedAccountMessage{
// Id: account.Id,
// Platform: platformCode,
// }
// payload, _ := sonic.Marshal(event)
// err = mq.MQ.Publish( // default exchange
// "account_exhausted_queue",
// payload,
// )
if err != nil {
s.Log.Errorf("发送账号耗尽通知失败:%v", err)
}
// if err != nil {
// s.Log.Errorf("发送账号耗尽通知失败:%v", err)
// }
return nil, errors.New("account exhausted and removed from queue")
}
// return nil, errors.New("account exhausted and removed from queue")
// }
return &account, nil
}

View File

@ -0,0 +1,108 @@
package service
import (
"fmt"
"go-admin/app/admin/service/dto"
"go-admin/utils/httphelper"
"time"
)
// 翻译之家
type TransTranslator struct {
config *TransTranslatorConfig
client *httphelper.HTTPClient
}
type TransTranslatorConfig struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
Endpoint string `json:"endpoint"`
}
type TransTranslatorResponse[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
Data T `json:"data"`
}
// 剩余字符数
type TransTranslatorResponseNumData struct {
UseNum int `json:"use_num" comment:"总字符"`
IsUsed int `json:"is_used" comment:"已用字符"`
}
// 翻译内容
type TransTranslatorResponseData struct {
Text string `json:"text"`
}
// 翻译之家
func NewTransTranslator(config *TransTranslatorConfig) *TransTranslator {
defaultHeaders := map[string]string{
"Content-Type": "application/json",
}
httpClient := httphelper.NewHTTPClient(
15*time.Second,
config.Endpoint,
defaultHeaders,
)
return &TransTranslator{
config: config,
client: httpClient,
}
}
// 翻译
func (e *TransTranslator) Translate(text string, sourceLang, targetLang string) (*dto.TranslateResult, error) {
result := dto.TranslateResult{}
responseData := TransTranslatorResponse[TransTranslatorResponseData]{}
route := fmt.Sprintf("/api/index/translate?token=%s", e.config.ApiKey)
params := map[string]string{
"keywords": text,
"sourceLanguage": sourceLang,
"targetLanguage": targetLang,
}
// Deepl API 翻译通常是 POST 请求到 /v2/translate
err := e.client.Post(route, params, nil, &responseData)
if err != nil {
return &result, fmt.Errorf("翻译请求出错: %w", err)
}
if responseData.Code != 1 {
return &result, fmt.Errorf("翻译失败,错误代码: %d msg: %s", responseData.Code, responseData.Message)
}
if responseData.Data.Text != "" {
result.TranslatedText = responseData.Data.Text
} else {
return &result, fmt.Errorf("翻译失败,返回数据为空")
}
return &result, nil
}
func (e *TransTranslator) GetRemainCount() (int, error) {
responseData := TransTranslatorResponse[TransTranslatorResponseNumData]{}
route := fmt.Sprintf("/api/index/getUserNums?token=%s", e.config.ApiKey)
// Deepl API 翻译通常是 POST 请求到 /v2/translate
err := e.client.Get(route, nil, &responseData)
if err != nil {
return 0, fmt.Errorf("翻译请求出错: %w", err)
}
if responseData.Code != 1 {
return 0, fmt.Errorf("翻译失败,错误代码: %d msg: %s", responseData.Code, responseData.Message)
}
result := responseData.Data.UseNum - responseData.Data.IsUsed
return result, nil
}
// 获取服务商
func (t *TransTranslator) GetPlatform() string {
return "trans"
}

View File

@ -13,6 +13,9 @@ func InitJob() {
"ExamplesOne": ExamplesOne{},
"DailyJob": DailyJob{},
"RemainCharJob": RemainCharJob{},
"CleanExpiredOrderJob": CleanExpiredOrderJob{},
"TrxPaymentJob": TrxPaymentJob{},
"SyncRemainCharJob": SyncRemainCharJob{},
// ...
}
}

View File

@ -2,11 +2,12 @@ package jobs
import (
"fmt"
models2 "go-admin/app/jobs/models"
"time"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk"
models2 "go-admin/app/jobs/models"
"gorm.io/gorm"
"time"
"github.com/robfig/cron/v3"
@ -59,7 +60,7 @@ func (e *ExecJob) Run() {
//TODO: 待完善部分
//str := time.Now().Format(timeFormat) + " [INFO] JobCore " + string(e.EntryId) + "exec success , spend :" + latencyTime.String()
//ws.SendAll(str)
log.Info("[Job] JobCore %s exec success , spend :%v", e.Name, latencyTime)
log.Infof("[Job] JobCore %s exec success , spend :%v", e.Name, latencyTime)
return
}

View File

@ -12,6 +12,30 @@ type DailyJob struct{}
type RemainCharJob struct{}
type CleanExpiredOrderJob struct{}
// 同步剩余字符
type SyncRemainCharJob struct{}
// 定时同步第三方用量
func (t SyncRemainCharJob) Exec(arg interface{}) error {
platformAccountService := service.TmPlatformAccount{}
platformAccountService.Orm = GetDb()
platformAccountService.Log = logger.NewHelper(logger.DefaultLogger)
return platformAccountService.SyncAll()
}
// 清理过期订单
func (t CleanExpiredOrderJob) Exec(arg interface{}) error {
// expireTime := time.Now().Add(5 * time.Minute)
rechargeLogService := service.TmRechargeLog{}
rechargeLogService.Orm = GetDb()
rechargeLogService.Log = logger.NewHelper(logger.DefaultLogger)
return rechargeLogService.CleanExpiredOrder()
}
// 剩余字符统计
func (t RemainCharJob) Exec(arg interface{}) error {
memberService := service.TmMember{}

View File

@ -36,3 +36,37 @@ func TestRemainTranslate(t *testing.T) {
t.Error(err)
}
}
func TestClean(t *testing.T) {
dsn := "root:123456@tcp(127.0.0.1:3306)/aggregate_translate?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
sdk.Runtime.SetDb("default", db)
redishelper.InitDefaultRedis("127.0.0.1:6379", "", 1)
redishelper.InitLockRedisConn("127.0.0.1:6379", "", "1")
job := CleanExpiredOrderJob{}
if err := job.Exec(nil); err != nil {
t.Error(err)
}
}
func TestSyncPlatformAccountChar(t *testing.T) {
initSetting()
job := SyncRemainCharJob{}
if err := job.Exec(nil); err != nil {
t.Error(err)
}
}
func initSetting() {
dsn := "root:123456@tcp(127.0.0.1:3306)/aggregate_translate?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
sdk.Runtime.SetDb("default", db)
redishelper.InitDefaultRedis("127.0.0.1:6379", "", 1)
redishelper.InitLockRedisConn("127.0.0.1:6379", "", "1")
}

View File

@ -5,10 +5,13 @@ import (
"fmt"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
rediskey "go-admin/common/redis_key"
"go-admin/config"
"go-admin/utils/redishelper"
"go-admin/utils/utility"
"io/ioutil"
"io"
"net/http"
"strings"
"time"
"github.com/go-admin-team/go-admin-core/logger"
@ -23,7 +26,7 @@ const (
)
// trx 链上支付定时查询
func (j *TrxPaymentJob) Exec(args interface{}) error {
func (j TrxPaymentJob) Exec(arg interface{}) error {
configService := service.SysConfig{}
configService.Orm = GetDb()
req := dto.SysConfigByKeyReq{}
@ -35,11 +38,41 @@ func (j *TrxPaymentJob) Exec(args interface{}) error {
return nil
}
key := fmt.Sprintf(rediskey.TM_RECHARGE_PRE_ORDER, "*")
keys, err := redishelper.DefaultRedis.ScanKeys(key)
if err != nil {
logger.Error("查询redis key失败", err)
return nil
}
if len(keys) == 0 {
logger.Info("没有待处理订单")
return nil
}
rechargeService := service.TmRechargeLog{}
rechargeService.Orm = GetDb()
rechargeService.Log = logger.NewHelper(logger.DefaultLogger)
platforms, err := rechargeService.GetPlatforms()
toAddresss := []string{}
if err != nil {
logger.Error("查询平台失败", err)
return err
}
for _, platform := range platforms {
if strings.ToLower(platform.BlockChain) == "trx" && !utility.ContainsString(toAddresss, platform.ReceiveAddress) {
toAddresss = append(toAddresss, platform.ReceiveAddress)
}
}
startTime := time.Now().UnixMilli()
endTime := time.Now().Add(-1 * time.Hour).UnixMilli()
transfers, err := GetTRC20Transfers(UsdtContractAddress, configData.ConfigValue, endTime, startTime)
for _, toAddress := range toAddresss {
transfers, err := GetTRC20Transfers(UsdtContractAddress, toAddress, endTime, startTime)
if err != nil {
logger.Error("查询失败", err)
return nil
@ -49,7 +82,8 @@ func (j *TrxPaymentJob) Exec(args interface{}) error {
item := dto.TmRechargeCallbackReq{}
for _, transfer := range transfers {
if transfer.TransactionID == "" || transfer.ToAddress != configData.ConfigValue {
if transfer.TransactionID == "" || transfer.ToAddress != toAddress {
logger.Infof("跳出插入 ", transfer)
continue
}
@ -58,37 +92,57 @@ func (j *TrxPaymentJob) Exec(args interface{}) error {
item.TxHash = transfer.TransactionID
item.PayableAmount = payableAmount
item.FromAddress = transfer.FromAddress
item.ToAddress = transfer.ToAddress
if utility.ContainsString(toAddresss, item.ToAddress) {
logs = append(logs, item)
} else {
logger.Infof("没有写入logs ", item)
}
}
if len(logs) > 0 {
err := rechargeService.PayCallBack(&logs)
if err != nil {
logger.Error("执行完毕,err:")
logger.Error("执行完毕,err:", err.Error())
}
} else {
// logger.Infof("接收地址:%s 合约地址:%s 无数据", toAddress, UsdtContractAddress)
}
}
return nil
}
// GetTRC20Transfers 获取指定 TRC20 代币的交易记录
func GetTRC20Transfers(contractAddress, accountAddress string, minTimestamp, maxTimestamp int64) ([]dto.TRC20Transfer, error) {
url := fmt.Sprintf("%s/v1/accounts/%s/transactions/trc20?contract_address=%s", config.ExtConfig.TrxGridUrl, accountAddress, contractAddress)
if minTimestamp > 0 {
url += fmt.Sprintf("&min_timestamp=%d", minTimestamp)
}
if maxTimestamp > 0 {
url += fmt.Sprintf("&max_timestamp=%d", maxTimestamp)
}
resp, err := http.Get(url)
// logger.Info("查询地址:", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
// 设置请求头(包含 TronGrid API Key
req.Header.Set("Accept", "*/*")
req.Header.Set("TRON-PRO-API-KEY", config.ExtConfig.TronApiKey) // 从配置读取 API Key
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %v", err)
}
@ -96,6 +150,7 @@ func GetTRC20Transfers(contractAddress, accountAddress string, minTimestamp, max
var result struct {
Data []dto.TRC20Transfer `json:"data"`
}
// logger.Info("查询结果:", string(body))
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
}

View File

@ -0,0 +1,28 @@
package jobs
import (
"go-admin/config"
"go-admin/utils/redishelper"
"testing"
"github.com/go-admin-team/go-admin-core/sdk"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func TestTrxPaymentJob(t *testing.T) {
dsn := "root:123456@tcp(127.0.0.1:3306)/aggregate_translate?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
sdk.Runtime.SetDb("default", db)
config.ExtConfig.TrxGridUrl = "https://api.trongrid.io"
redishelper.InitDefaultRedis("127.0.0.1:6379", "", 1)
redishelper.InitLockRedisConn("127.0.0.1:6379", "", "1")
trxPaymentJob := TrxPaymentJob{}
err := trxPaymentJob.Exec(nil)
if err != nil {
t.Error(err)
}
}

View File

@ -227,8 +227,8 @@ func initRouter() {
r.Use(handler.TlsHandler())
}
//r.Use(middleware.Metrics())
r.Use(common.Sentinel()).
Use(common.RequestId(pkg.TrafficKey)).
// r.Use(common.Sentinel()).
r.Use(common.RequestId(pkg.TrafficKey)).
Use(api.SetRequestLogger)
common.InitMiddleware(r)

View File

@ -23,7 +23,7 @@ var ErrorMessage = map[int]string{
NotFindMember: "not find member",
NotFindApiKey: "not find api key",
MemberPlatformNotSupport: "member platform not support",
RechargeAmountMustBeGreaterThanZero: "recharge amount must be greater than zero",
RechargeNumberMustBeGreaterThanZero: "The recharge quantity must be greater than 0",
}
const (
@ -47,6 +47,6 @@ const (
MemberPlatformNotSupport = 30003 //用户平台不支持
//================ 充值相关 ===============
RechargeAmountMustBeGreaterThanZero = 40001 //充值金额必须大于0
RechargeNumberMustBeGreaterThanZero = 40001 //充值数量必须大于0
)

View File

@ -12,7 +12,8 @@ var ExtConfig Extend
type Extend struct {
AMap AMap // 这里配置对应配置文件的结构即可
Mq MqConfig
TrxGridUrl string `yaml:"trx_grid_url"`
TrxGridUrl string `yaml:"trxGridUrl"`
TronApiKey string `yaml:"tronApiKey"`
}
type AMap struct {

View File

@ -54,7 +54,8 @@ settings:
password: '123456'
pass: "123456"
#trx api
trx_grid_url: "https://api.trongrid.io"
trxGridUrl: "https://api.trongrid.io"
tronApiKey: "223c129e-73f5-470f-9464-f9969846c134"
cache:
redis:
addr: 127.0.0.1:6379

View File

@ -0,0 +1,35 @@
package chainhelper
import (
"errors"
"regexp"
)
// 比较钱包地址格式
func JudgeChainAddress(chain, walletAddress string) error {
switch chain {
case "trx":
return validateTronAddress(walletAddress)
default:
return errors.New("invalid chain")
}
}
func validateTronAddress(address string) error {
// TRON 地址通常以 'T' 开头,并且是 34 个字符的 Base58Check 编码
// 这是一个简化的检查,最佳实践是使用专门的 Tron 地址验证库来包含校验和验证
if len(address) != 34 {
return errors.New("Tron 钱包地址长度不正确")
}
if address[0] != 'T' {
return errors.New("Tron 钱包地址长度不正确,必须以 'T'开头")
}
// 检查字符集
matched, _ := regexp.MatchString("^[T][1-9a-zA-Z]{33}$", address)
if !matched {
return errors.New("TRON 钱包地址格式不正确")
}
return nil
}

44
utils/utility/safego.go Normal file
View File

@ -0,0 +1,44 @@
package utility
import (
"fmt"
"runtime"
"runtime/debug"
"strings"
"github.com/go-admin-team/go-admin-core/logger"
)
// SafeGo 安全地启动一个 goroutine捕获 panic
func SafeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
// 记录 Goroutine ID、panic 信息和堆栈
logger.Error(fmt.Sprintf("Recovered from panic in Goroutine %s: %v\nStack Trace:\n%s", GetGoroutineID(), r, string(debug.Stack())))
}
}()
fn()
}()
}
// 获取 Goroutine ID
func GetGoroutineID() string {
buf := make([]byte, 64)
n := runtime.Stack(buf, false)
stack := string(buf[:n])
// 提取 Goroutine ID
id := strings.Split(stack, " ")[1]
return id
}
func SafeGoParam[T any](fn func(T), param T) {
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error(fmt.Sprintf(" SafeGoParam Recovered from panic in Goroutine %s: %v\nStack Trace:\n%s", GetGoroutineID(), r, string(debug.Stack())))
}
}()
fn(param) // 执行传入的函数
}()
}

View File

@ -27,3 +27,13 @@ func ContainsInt(arr []int, v int) bool {
}
return false
}
func ContainsString(arr []string, v string) bool {
for _, a := range arr {
if a == v {
return true
}
}
return false
}