You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

111 lines
3.6 KiB

package user
import (
"os"
"strings"
"sync"
"time"
"yunyan/comm"
"yunyan/lego/core"
"yunyan/lego/core/cbase"
"yunyan/lego/sys/postgres"
alitranslate "yunyan/sys/aliyun/translate"
)
// 实时机器翻译(同声传译 / 面对面翻译)的服务解析。
//
// 与实名认证同一套路子:翻译服务配在后台「第三方服务配置」(类别 comm.SvcCatMT=3),
// 存 console 共享库 postgres,凭据在服务端解密使用,**不下发客户端**。
//
// 为什么放服务端而不是像以前那样客户端直连:
// 以前客户端用火山翻译,AK/SK 是通过 AppConfig.env **明文下发给每一个客户端**的,
// 任何人抓个包或反编译就能拿到云账号凭据。改成服务端代调后凭据不再出网关。
// mtCacheTTL 翻译客户端的缓存时长。
//
// 同传是流式高频调用,每句都去查一次库、重建一次 SDK 客户端太浪费;
// 但也不能永久缓存——后台改了配置得能生效,所以给个短 TTL。
const mtCacheTTL = 60 * time.Second
type modelTranslateComp struct {
cbase.ModuleCompBase
module *User
mu sync.RWMutex
cached alitranslate.ISys
cachedId string
cachedAt time.Time
}
func (this *modelTranslateComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, opt core.IModuleOptions) (err error) {
this.ModuleCompBase.Init(service, module, comp, opt)
this.module = module.(*User)
return
}
// resolveTranslator 取本应用可用的机器翻译服务。
//
// 作用域口径与 resolveThirdSvcs 一致:应用行(app_name=<app>)优先于全局行(app_name=''),
// 只取启用的、类别含 comm.SvcCatMT 的服务。
// 返回 svcId/provider 供落日志与响应回带,便于排查"到底用的哪家"。
func (this *modelTranslateComp) resolveTranslator() (svcId, provider string, t alitranslate.ISys, err error) {
this.mu.RLock()
if this.cached != nil && time.Since(this.cachedAt) < mtCacheTTL {
svcId, t = this.cachedId, this.cached
this.mu.RUnlock()
return svcId, "alibaba", t, nil
}
this.mu.RUnlock()
app := comm.AppName()
svcs := make([]*comm.ThirdSvcConfig, 0)
if err = postgres.Find(comm.TableSvcConfig, &svcs, "(app_name=? OR app_name='')", app); err != nil {
if err == postgres.ErrNoDocuments {
err = nil
} else {
return
}
}
var picked *comm.ThirdSvcConfig
for _, s := range svcs {
if !s.Enable || !comm.CategoriesHas(s.Categories, comm.SvcCatMT) {
continue
}
if picked == nil || (picked.AppName == "" && s.AppName == app && app != "") {
picked = s
}
}
if picked == nil {
return "", "", nil, nil // 未配置:调用方回 TranslateNotConfigured
}
fields, ferr := comm.ResolveSvcPlainFields(picked, nil, os.Getenv("FIELD_ENCRYPT_KEY"))
if ferr != nil {
return picked.Id, picked.Provider, nil, ferr
}
// 目前只实现了阿里云。换别家时在这里按 provider 分支即可
// (sys/ 下已有 bytedance/google/microsoft 的翻译实现可接)。
if strings.TrimSpace(strings.ToLower(picked.Provider)) != "alibaba" {
return picked.Id, picked.Provider, nil, nil
}
opts := []alitranslate.Option{
alitranslate.SetAccessKeyId(strings.TrimSpace(fields["access_key_id"])),
alitranslate.SetAccessKeySecret(strings.TrimSpace(fields["access_key_secret"])),
}
if r := strings.TrimSpace(fields["region"]); r != "" {
opts = append(opts, alitranslate.SetRegion(r))
}
sys, serr := alitranslate.NewSys(opts...)
if serr != nil {
return picked.Id, picked.Provider, nil, serr
}
this.mu.Lock()
this.cached, this.cachedId, this.cachedAt = sys, picked.Id, time.Now()
this.mu.Unlock()
return picked.Id, picked.Provider, sys, nil
}