From f3ca87fb54059c24efd7524139531e4b516cc9c3 Mon Sep 17 00:00:00 2001 From: hucan <951870319@qq.com> Date: Mon, 7 Jul 2025 19:01:54 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=9C=A8=E7=BA=BF=E6=94=AF=E4=BB=98?= =?UTF-8?q?=202=E3=80=81=E6=9F=A5=E8=AF=A2=E5=B9=B3=E5=8F=B0=E5=89=A9?= =?UTF-8?q?=E4=BD=99=E5=AD=97=E7=AC=A6=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/apis/tm_platform.go | 12 ++ app/admin/apis/tm_platform_account.go | 25 +++ app/admin/apis/tm_recharge_log.go | 32 ++- app/admin/apis/translate.go | 2 +- app/admin/models/tm_platform.go | 16 +- app/admin/router/tm_member.go | 7 +- app/admin/router/tm_platform_account.go | 6 +- app/admin/service/dto/tm_platform.go | 90 +++++++-- app/admin/service/dto/tm_platform_account.go | 4 + app/admin/service/dto/tm_recharge_log.go | 47 ++++- app/admin/service/tm_platform_account.go | 112 +++++++++++ app/admin/service/tm_recharge_log.go | 194 ++++++++++++++++--- app/admin/service/translator_service.go | 57 ++++-- app/admin/service/translator_trans.go | 108 +++++++++++ app/jobs/examples.go | 8 +- app/jobs/translate.go | 12 ++ app/jobs/translate_test.go | 14 ++ app/jobs/trxpayment_job.go | 76 +++++--- app/jobs/trxpayment_job_test.go | 28 +++ common/statuscode/status_1.go | 4 +- config/extend.go | 2 +- config/settings.yml | 2 +- utils/chainhelper/chainhelper.go | 35 ++++ utils/utility/slice.go | 10 + 24 files changed, 789 insertions(+), 114 deletions(-) create mode 100644 app/admin/service/translator_trans.go create mode 100644 app/jobs/trxpayment_job_test.go create mode 100644 utils/chainhelper/chainhelper.go diff --git a/app/admin/apis/tm_platform.go b/app/admin/apis/tm_platform.go index 9d5f295..0db9535 100644 --- a/app/admin/apis/tm_platform.go +++ b/app/admin/apis/tm_platform.go @@ -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) diff --git a/app/admin/apis/tm_platform_account.go b/app/admin/apis/tm_platform_account.go index 1444344..e004027 100644 --- a/app/admin/apis/tm_platform_account.go +++ b/app/admin/apis/tm_platform_account.go @@ -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, "查询成功") +} diff --git a/app/admin/apis/tm_recharge_log.go b/app/admin/apis/tm_recharge_log.go index 88f9e99..1d598a8 100644 --- a/app/admin/apis/tm_recharge_log.go +++ b/app/admin/apis/tm_recharge_log.go @@ -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{} diff --git a/app/admin/apis/translate.go b/app/admin/apis/translate.go index 460e9d6..a906ed7 100644 --- a/app/admin/apis/translate.go +++ b/app/admin/apis/translate.go @@ -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 } diff --git a/app/admin/models/tm_platform.go b/app/admin/models/tm_platform.go index b2e749b..73c8f6a 100644 --- a/app/admin/models/tm_platform.go +++ b/app/admin/models/tm_platform.go @@ -9,13 +9,15 @@ import ( type TmPlatform struct { models.Model - Name string `json:"name" gorm:"type:varchar(20);comment:平台名称"` - ShowName string `json:"showName" gorm:"type:varchar(20);comment:展示名称"` - ApiBaseUrl string `json:"apiBaseUrl" gorm:"type:varchar(500);comment:平台接口地址"` - Description string `json:"description" gorm:"type:varchar(255);comment:描述"` - 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:单价"` + Name string `json:"name" gorm:"type:varchar(20);comment:平台名称"` + ShowName string `json:"showName" gorm:"type:varchar(20);comment:展示名称"` + ApiBaseUrl string `json:"apiBaseUrl" gorm:"type:varchar(500);comment:平台接口地址"` + Description string `json:"description" gorm:"type:varchar(255);comment:描述"` + 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 } diff --git a/app/admin/router/tm_member.go b/app/admin/router/tm_member.go index 0168ae2..1a7558f 100644 --- a/app/admin/router/tm_member.go +++ b/app/admin/router/tm_member.go @@ -26,8 +26,9 @@ 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.PUT("status", actions.PermissionAction(), api.ChangeStatus) //状态变更 + r.POST("manager-recharge", actions.PermissionAction(), rechargeApi.ManagerRecharge) //手动充值 + r.PUT("manager-deduct", actions.PermissionAction(), rechargeApi.ManagerDeduct) //手动扣除 + r.PUT("status", actions.PermissionAction(), api.ChangeStatus) //状态变更 } r2 := v1.Group("/tm-member").Use(authMiddleware.MiddlewareFunc()) @@ -35,5 +36,7 @@ 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) //用户发起充值 } } diff --git a/app/admin/router/tm_platform_account.go b/app/admin/router/tm_platform_account.go index 99489b9..5ae6810 100644 --- a/app/admin/router/tm_platform_account.go +++ b/app/admin/router/tm_platform_account.go @@ -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) //查询平台剩余字符 } -} \ No newline at end of file +} diff --git a/app/admin/service/dto/tm_platform.go b/app/admin/service/dto/tm_platform.go index 52fc988..ad8f8b4 100644 --- a/app/admin/service/dto/tm_platform.go +++ b/app/admin/service/dto/tm_platform.go @@ -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" ) @@ -40,17 +42,43 @@ func (m *TmPlatformGetPageReq) GetNeedSearch() interface{} { } type TmPlatformInsertReq struct { - Id int `json:"-" comment:"平台id"` // 平台id - Name string `json:"name" comment:"平台名称"` - ShowName string `json:"showName" comment:"展示名称"` - ApiBaseUrl string `json:"apiBaseUrl" comment:"平台接口地址"` - Description string `json:"description" comment:"描述"` - Code string `json:"code" comment:"平台编码(字典 tm_platform)"` - Character int `json:"character" comment:"字符数"` - Price decimal.Decimal `json:"price" comment:"单价"` + Id int `json:"-" comment:"平台id"` // 平台id + Name string `json:"name" comment:"平台名称"` + ShowName string `json:"showName" comment:"展示名称"` + ApiBaseUrl string `json:"apiBaseUrl" comment:"平台接口地址"` + Description string `json:"description" comment:"描述"` + 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{} { @@ -77,17 +107,43 @@ type TmPlatformListResp struct { } type TmPlatformUpdateReq struct { - Id int `uri:"id" comment:"平台id"` // 平台id - Name string `json:"name" comment:"平台名称"` - ShowName string `json:"showName" comment:"展示名称"` - ApiBaseUrl string `json:"apiBaseUrl" comment:"平台接口地址"` - Description string `json:"description" comment:"描述"` - Code string `json:"code" comment:"平台编码(字典 tm_platform)"` - Character int `json:"character" comment:"字符数"` - Price decimal.Decimal `json:"price" comment:"单价"` + Id int `uri:"id" comment:"平台id"` // 平台id + Name string `json:"name" comment:"平台名称"` + ShowName string `json:"showName" comment:"展示名称"` + ApiBaseUrl string `json:"apiBaseUrl" comment:"平台接口地址"` + Description string `json:"description" comment:"描述"` + 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{} { diff --git a/app/admin/service/dto/tm_platform_account.go b/app/admin/service/dto/tm_platform_account.go index a131860..80e07ac 100644 --- a/app/admin/service/dto/tm_platform_account.go +++ b/app/admin/service/dto/tm_platform_account.go @@ -95,3 +95,7 @@ type TmPlatformAccountDeleteReq struct { func (s *TmPlatformAccountDeleteReq) GetId() interface{} { return s.Ids } + +type TmPlatformAccountQueryRemainReq struct { + Id int `uri:"id"` +} diff --git a/app/admin/service/dto/tm_recharge_log.go b/app/admin/service/dto/tm_recharge_log.go index ba9d0e2..14a1d59 100644 --- a/app/admin/service/dto/tm_recharge_log.go +++ b/app/admin/service/dto/tm_recharge_log.go @@ -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"` + UserId int `json:"userId" comment:"用户id"` + 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") diff --git a/app/admin/service/tm_platform_account.go b/app/admin/service/tm_platform_account.go index 45b0567..1d74d0b 100644 --- a/app/admin/service/tm_platform_account.go +++ b/app/admin/service/tm_platform_account.go @@ -21,6 +21,106 @@ 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("查看对象不存在或无权查看") + } + + 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: "deepseek", + 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 +} + // GetPage 获取TmPlatformAccount列表 func (e *TmPlatformAccount) GetPage(c *dto.TmPlatformAccountGetPageReq, p *actions.DataPermission, list *[]models.TmPlatformAccount, count *int64) error { var err error @@ -38,6 +138,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 } diff --git a/app/admin/service/tm_recharge_log.go b/app/admin/service/tm_recharge_log.go index 73697d9..807d37c 100644 --- a/app/admin/service/tm_recharge_log.go +++ b/app/admin/service/tm_recharge_log.go @@ -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,26 @@ type TmRechargeLog struct { service.Service } +// 清理过期订单 +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 0 { + platforms = append(platforms, platform) + } + } + + return platforms, nil +} diff --git a/app/admin/service/translator_service.go b/app/admin/service/translator_service.go index 324e5b4..1296e13 100644 --- a/app/admin/service/translator_service.go +++ b/app/admin/service/translator_service.go @@ -37,6 +37,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 +53,43 @@ 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) default: - return nil, errors.Errorf("unsupported translation provider: %s", providerName) + return nil, UnSportPlatformErr } svc.RegisterProvider(providerName, newAdapter) } @@ -149,7 +165,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 +173,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,22 +217,20 @@ func (s *TranslatorService) GetTranslator(platform string) (translator *Translat ProviderConfigs: configs, } - switch platform { - case "deepl", "deepl_free", "deepseek": - translator, err = NewTranslatorService(&cfg) + translator, err = NewTranslatorService(&cfg) - if err != nil { - s.Log.Errorf("failed to create translator service: %s", err) + if err != nil { + switch err { + case UnSportPlatformErr: + code = statuscode.PlatformNotSupport + default: code = statuscode.ServerError - return } - + } else { code = statuscode.Success - return - default: - code = statuscode.PlatformNotSupport - return } + + return } // 从 Redis List 顺序取出一个账号,使用后放回队尾,模拟轮询 diff --git a/app/admin/service/translator_trans.go b/app/admin/service/translator_trans.go new file mode 100644 index 0000000..40a2de5 --- /dev/null +++ b/app/admin/service/translator_trans.go @@ -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" +} diff --git a/app/jobs/examples.go b/app/jobs/examples.go index d42e1db..6f2ec29 100644 --- a/app/jobs/examples.go +++ b/app/jobs/examples.go @@ -10,9 +10,11 @@ import ( // 字典 key 可以配置到 自动任务 调用目标 中; func InitJob() { jobList = map[string]JobExec{ - "ExamplesOne": ExamplesOne{}, - "DailyJob": DailyJob{}, - "RemainCharJob": RemainCharJob{}, + "ExamplesOne": ExamplesOne{}, + "DailyJob": DailyJob{}, + "RemainCharJob": RemainCharJob{}, + "CleanExpiredOrderJob": CleanExpiredOrderJob{}, + "TrxPaymentJob": TrxPaymentJob{}, // ... } } diff --git a/app/jobs/translate.go b/app/jobs/translate.go index 2b861ca..c9e72f4 100644 --- a/app/jobs/translate.go +++ b/app/jobs/translate.go @@ -12,6 +12,18 @@ type DailyJob struct{} type RemainCharJob struct{} +type CleanExpiredOrderJob struct{} + +// 清理过期订单 +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{} diff --git a/app/jobs/translate_test.go b/app/jobs/translate_test.go index 831d173..61c32d4 100644 --- a/app/jobs/translate_test.go +++ b/app/jobs/translate_test.go @@ -36,3 +36,17 @@ 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) + } +} diff --git a/app/jobs/trxpayment_job.go b/app/jobs/trxpayment_job.go index 8571fd0..4800098 100644 --- a/app/jobs/trxpayment_job.go +++ b/app/jobs/trxpayment_job.go @@ -9,6 +9,7 @@ import ( "go-admin/utils/utility" "io/ioutil" "net/http" + "strings" "time" "github.com/go-admin-team/go-admin-core/logger" @@ -23,7 +24,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{} @@ -37,39 +38,58 @@ func (j *TrxPaymentJob) Exec(args interface{}) error { rechargeService := service.TmRechargeLog{} rechargeService.Orm = GetDb() + 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) - if err != nil { - logger.Error("查询失败", err) - return nil - } - - logs := make([]dto.TmRechargeCallbackReq, 0) - item := dto.TmRechargeCallbackReq{} - - for _, transfer := range transfers { - if transfer.TransactionID == "" || transfer.ToAddress != configData.ConfigValue { - continue - } - - //实际金额 - payableAmount := utility.StringToDecimal(transfer.Value).Div(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(transfer.TokenInfo.Decimals)))).Truncate(6) - item.TxHash = transfer.TransactionID - item.PayableAmount = payableAmount - item.FromAddress = transfer.FromAddress - - logs = append(logs, item) - } - - if len(logs) > 0 { - err := rechargeService.PayCallBack(&logs) + for _, toAddress := range toAddresss { + transfers, err := GetTRC20Transfers(UsdtContractAddress, toAddress, endTime, startTime) if err != nil { - logger.Error("执行完毕,err:") + logger.Error("查询失败", err) + return nil + } + + logs := make([]dto.TmRechargeCallbackReq, 0) + item := dto.TmRechargeCallbackReq{} + + for _, transfer := range transfers { + if transfer.TransactionID == "" || transfer.ToAddress != toAddress { + continue + } + + //实际金额 + payableAmount := utility.StringToDecimal(transfer.Value).Div(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(transfer.TokenInfo.Decimals)))).Truncate(6) + 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) + } + } + + if len(logs) > 0 { + err := rechargeService.PayCallBack(&logs) + + if err != nil { + logger.Error("执行完毕,err:", err.Error()) + } } } - return nil } diff --git a/app/jobs/trxpayment_job_test.go b/app/jobs/trxpayment_job_test.go new file mode 100644 index 0000000..f5564df --- /dev/null +++ b/app/jobs/trxpayment_job_test.go @@ -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) + } +} diff --git a/common/statuscode/status_1.go b/common/statuscode/status_1.go index f044f01..e1d0bbb 100644 --- a/common/statuscode/status_1.go +++ b/common/statuscode/status_1.go @@ -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 ) diff --git a/config/extend.go b/config/extend.go index 70c8451..0117474 100644 --- a/config/extend.go +++ b/config/extend.go @@ -12,7 +12,7 @@ var ExtConfig Extend type Extend struct { AMap AMap // 这里配置对应配置文件的结构即可 Mq MqConfig - TrxGridUrl string `yaml:"trx_grid_url"` + TrxGridUrl string `yaml:"trxGridUrl"` } type AMap struct { diff --git a/config/settings.yml b/config/settings.yml index 10f82e2..36fb5e7 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -54,7 +54,7 @@ settings: password: '123456' pass: "123456" #trx api - trx_grid_url: "https://api.trongrid.io" + trxGridUrl: "https://api.trongrid.io" cache: redis: addr: 127.0.0.1:6379 diff --git a/utils/chainhelper/chainhelper.go b/utils/chainhelper/chainhelper.go new file mode 100644 index 0000000..d36c28a --- /dev/null +++ b/utils/chainhelper/chainhelper.go @@ -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 +} diff --git a/utils/utility/slice.go b/utils/utility/slice.go index 5bee83b..450a0e1 100644 --- a/utils/utility/slice.go +++ b/utils/utility/slice.go @@ -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 +}