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_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 }