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
278 lines
8.2 KiB
Go
278 lines
8.2 KiB
Go
package service
|
||
|
||
import (
|
||
"encoding/json"
|
||
"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"
|
||
"github.com/pkg/errors"
|
||
)
|
||
|
||
type TranslatorServiceConfig struct {
|
||
DefaultProvider string // 默认翻译服务商
|
||
ProviderConfigs map[string]interface{} // 各个服务商的具体配置
|
||
}
|
||
|
||
type TranslatorService struct {
|
||
service.Service
|
||
config *TranslatorServiceConfig
|
||
providers map[string]Translator // 注册的翻译服务商适配器
|
||
// cache cache.Cache // 缓存服务
|
||
}
|
||
|
||
type TranslatorToolService struct {
|
||
service.Service
|
||
}
|
||
|
||
// NewTranslatorService 创建翻译服务实例
|
||
func NewTranslatorService(cfg *TranslatorServiceConfig) (*TranslatorService, error) {
|
||
svc := &TranslatorService{
|
||
config: cfg,
|
||
// cache: c,
|
||
providers: make(map[string]Translator),
|
||
}
|
||
|
||
// 根据配置初始化并注册翻译服务商适配器
|
||
for providerName, providerCfg := range cfg.ProviderConfigs {
|
||
var newAdapter Translator
|
||
switch providerName {
|
||
case "google":
|
||
googleCfgBytes, _ := json.Marshal(providerCfg) // 假设配置是map[string]interface{},需要转换
|
||
var googleCfg GoogleTranslatorConfig
|
||
if err := json.Unmarshal(googleCfgBytes, &googleCfg); err != nil {
|
||
return nil, errors.Wrapf(err, "failed to unmarshal config for GoogleTranslate")
|
||
}
|
||
newAdapter = NewGoogleTranslator(&googleCfg)
|
||
// case "BaiduTranslate":
|
||
// // newAdapter = adapter.NewBaiduTranslator(...)
|
||
case "deepseek":
|
||
deepseekCfgBytes, _ := json.Marshal(providerCfg) // 假设配置是map[string]interface{},需要转换
|
||
var deepseekCfg DeepseekTranslatorConfig
|
||
if err := json.Unmarshal(deepseekCfgBytes, &deepseekCfg); err != nil {
|
||
return nil, errors.Wrapf(err, "failed to unmarshal config for DeepseekTranslator")
|
||
}
|
||
newAdapter = NewDeepseekTranslator(&deepseekCfg)
|
||
case "deepl", "deepl_free":
|
||
deeplCfgBytes, _ := json.Marshal(providerCfg) // 假设配置是map[string]interface{},需要转换
|
||
var deeplCfg DeeplTranslatorConfig
|
||
if err := json.Unmarshal(deeplCfgBytes, &deeplCfg); err != nil {
|
||
return nil, errors.Wrapf(err, "failed to unmarshal config for DeepLTranslator")
|
||
}
|
||
newAdapter = NewDeeplTranslator(&deeplCfg)
|
||
default:
|
||
return nil, errors.Errorf("unsupported translation provider: %s", providerName)
|
||
}
|
||
svc.RegisterProvider(providerName, newAdapter)
|
||
}
|
||
|
||
if len(svc.providers) == 0 {
|
||
return nil, errors.New("no translation providers registered")
|
||
}
|
||
|
||
return svc, nil
|
||
}
|
||
|
||
// RegisterProvider 注册翻译服务商适配器
|
||
func (s *TranslatorService) RegisterProvider(name string, provider Translator) {
|
||
// s.providerMutex.Lock()
|
||
// defer s.providerMutex.Unlock()
|
||
s.providers[name] = provider
|
||
logger.Infof("Registered translation provider: %s", name)
|
||
}
|
||
|
||
// 翻译校验
|
||
// return statusCode
|
||
func (s *TranslatorService) TranslateJudge(req *dto.TranslateReq, apiKey string) (result *dto.TranslateResult, respCode int) {
|
||
tmMemberService := TmMember{Service: s.Service}
|
||
tmPlatformAccount := TmPlatformAccount{Service: s.Service}
|
||
memberInfo, err1 := tmMemberService.GetByKey(apiKey)
|
||
|
||
if err1 != nil {
|
||
s.Log.Errorf("获取用户信息失败:%v", err1)
|
||
respCode = statuscode.ServerError
|
||
return
|
||
}
|
||
|
||
if memberInfo.Status == 2 {
|
||
respCode = statuscode.ApiUnauthorized
|
||
return
|
||
}
|
||
|
||
remainCount, _ := tmMemberService.GetRemainCount(req.Platform, apiKey)
|
||
count := utf8.RuneCountInString(req.Text)
|
||
respCode = statuscode.Success
|
||
|
||
if remainCount < count {
|
||
respCode = statuscode.InSufficRemainChar
|
||
return
|
||
}
|
||
|
||
Translator, code := s.GetTranslator(req.Platform)
|
||
|
||
if code != statuscode.Success {
|
||
respCode = code
|
||
return
|
||
}
|
||
|
||
result, err := Translator.providers[req.Platform].Translate(req.Text, req.SourceLang, req.TargetLang)
|
||
|
||
if err == nil {
|
||
err2 := tmMemberService.DecrBy(req.Platform, apiKey, count)
|
||
|
||
if err2 != nil {
|
||
s.Log.Errorf("翻译计数失败:%v", err2)
|
||
respCode = statuscode.ServerError
|
||
return
|
||
}
|
||
|
||
platformConfigInterface := Translator.config.ProviderConfigs[req.Platform]
|
||
|
||
// 尝试将 interface{} 断言为 map[string]interface{}
|
||
if mapData, ok := platformConfigInterface.(map[string]interface{}); !ok {
|
||
tmPlatformAccount.DecrRemainBy(req.Platform, mapData["apiKey"].(string), count)
|
||
}
|
||
|
||
date := time.Now().Format("20060102")
|
||
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 {
|
||
code = statuscode.ServerError
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func (s *TranslatorService) GetTranslator(platform string) (translator *TranslatorService, code int) {
|
||
platformKey := fmt.Sprintf(rediskey.TM_PLATFORM_KEY, platform)
|
||
platformVal, _ := redishelper.DefaultRedis.GetString(platformKey)
|
||
|
||
if platformVal == "" {
|
||
code = statuscode.PlatformNotSupport
|
||
return
|
||
}
|
||
|
||
var platformEntity models.TmPlatform
|
||
account, err := s.GetNextAccount(platform)
|
||
|
||
if err != nil {
|
||
s.Log.Errorf("获取翻译api失败:%v", err)
|
||
}
|
||
|
||
sonic.UnmarshalString(platformVal, &platformEntity)
|
||
|
||
if account.Id == 0 || account.Status == 2 {
|
||
code = statuscode.TransactionNotAvailable
|
||
return
|
||
}
|
||
|
||
configs := map[string]interface{}{}
|
||
configs[platform] = map[string]interface{}{
|
||
"apiKey": account.ApiKey,
|
||
"apiSecret": account.ApiSecret,
|
||
"endpoint": platformEntity.ApiBaseUrl,
|
||
}
|
||
cfg := TranslatorServiceConfig{
|
||
DefaultProvider: platform,
|
||
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)
|
||
code = statuscode.ServerError
|
||
return
|
||
}
|
||
|
||
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
|
||
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,带重试机制
|
||
for _, delay := range retries {
|
||
val, err = redishelper.DefaultRedis.LPopList(key)
|
||
if err == nil {
|
||
// 成功获取到值,跳出重试循环
|
||
break
|
||
}
|
||
|
||
time.Sleep(delay)
|
||
}
|
||
|
||
// 如果所有重试都失败了,并且不是因为队列为空
|
||
if err != nil {
|
||
return nil, errors.New("failed to get account from queue after multiple retries: " + err.Error())
|
||
}
|
||
|
||
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)
|
||
}
|
||
|
||
// 如果剩余字符数 <= 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 err != nil {
|
||
s.Log.Errorf("发送账号耗尽通知失败:%v", err)
|
||
}
|
||
|
||
return nil, errors.New("account exhausted and removed from queue")
|
||
}
|
||
|
||
return &account, nil
|
||
}
|