refactor(internal): 优化 OSS 预签名 URL 缓存刷新任务和英雄数据缓存逻辑
- 注释掉 OSS预签名 URL 缓存刷新任务的定时执行代码 - 在 hero/hero.go 中增加对 Redis缓存和英雄数据集的非空校验 - 修改 OSS预签名 URL 生成逻辑,自动替换为 CDN 域名
This commit is contained in:
@@ -2,11 +2,14 @@ package i18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
"epic/internal/consts"
|
||||
"epic/internal/dao"
|
||||
"epic/internal/model/entity"
|
||||
"epic/internal/util"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -603,14 +606,22 @@ var I18nZhToEn = func() map[string]string {
|
||||
return m
|
||||
}()
|
||||
|
||||
// Logic i18n主逻辑结构体
|
||||
// cache结构调整:lang -> { code -> value, key -> value }
|
||||
type Logic struct {
|
||||
cache map[string]map[string]string // lang -> key -> value
|
||||
cache map[string]struct {
|
||||
ByCode map[string]string // code->value
|
||||
ByKey map[string]string // key->value
|
||||
}
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func New() *Logic {
|
||||
return &Logic{
|
||||
cache: make(map[string]map[string]string),
|
||||
cache: make(map[string]struct {
|
||||
ByCode map[string]string
|
||||
ByKey map[string]string
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,16 +656,24 @@ func (l *Logic) LoadFromDB(ctx context.Context) error {
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
// 重新构建缓存
|
||||
l.cache = make(map[string]map[string]string)
|
||||
// 新增:同步数据库内容到I18nEnToZh和I18nZhToEn备份
|
||||
l.cache = make(map[string]struct {
|
||||
ByCode map[string]string
|
||||
ByKey map[string]string
|
||||
})
|
||||
for _, m := range mappings {
|
||||
if l.cache[m.Language] == nil {
|
||||
l.cache[m.Language] = make(map[string]string)
|
||||
if l.cache[m.Language].ByCode == nil {
|
||||
l.cache[m.Language] = struct {
|
||||
ByCode map[string]string
|
||||
ByKey map[string]string
|
||||
}{ByCode: make(map[string]string), ByKey: make(map[string]string)}
|
||||
}
|
||||
l.cache[m.Language][m.KeyName] = m.Value
|
||||
if m.Code != "" {
|
||||
l.cache[m.Language].ByCode[m.Code] = m.Value
|
||||
}
|
||||
l.cache[m.Language].ByKey[m.KeyName] = m.Value
|
||||
if m.Language == "zh" {
|
||||
I18nEnToZh[m.KeyName] = m.Value
|
||||
I18nZhToEn[m.Value] = m.KeyName // 新增:同步反向映射
|
||||
I18nZhToEn[m.Value] = m.KeyName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,13 +681,19 @@ func (l *Logic) LoadFromDB(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取指定key的指定语言翻译
|
||||
func (l *Logic) Get(ctx context.Context, lang, key string) string {
|
||||
// Get 获取指定key/code的指定语言翻译
|
||||
func (l *Logic) Get(ctx context.Context, lang, key string, code ...string) string {
|
||||
l.mutex.RLock()
|
||||
defer l.mutex.RUnlock()
|
||||
|
||||
if len(code) > 0 && code[0] != "" {
|
||||
if m, ok := l.cache[lang]; ok {
|
||||
if v, ok := m.ByCode[code[0]]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
if m, ok := l.cache[lang]; ok {
|
||||
if v, ok := m[key]; ok {
|
||||
if v, ok := m.ByKey[key]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
@@ -683,7 +708,7 @@ func (l *Logic) GetBatch(ctx context.Context, lang string, keys []string) map[st
|
||||
result := make(map[string]string)
|
||||
if m, ok := l.cache[lang]; ok {
|
||||
for _, key := range keys {
|
||||
if v, ok := m[key]; ok {
|
||||
if v, ok := m.ByKey[key]; ok {
|
||||
result[key] = v
|
||||
} else {
|
||||
result[key] = key // 找不到返回原文
|
||||
@@ -724,10 +749,16 @@ func (l *Logic) Add(ctx context.Context, key, lang, value, category string) erro
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.cache[lang] == nil {
|
||||
l.cache[lang] = make(map[string]string)
|
||||
if _, exists := l.cache[lang]; !exists {
|
||||
l.cache[lang] = struct {
|
||||
ByCode map[string]string
|
||||
ByKey map[string]string
|
||||
}{
|
||||
ByCode: make(map[string]string),
|
||||
ByKey: make(map[string]string),
|
||||
}
|
||||
}
|
||||
l.cache[lang][key] = value
|
||||
l.cache[lang].ByKey[key] = value
|
||||
|
||||
util.Info(ctx, "添加翻译:", lang, key, "->", value)
|
||||
return nil
|
||||
@@ -752,10 +783,16 @@ func (l *Logic) Update(ctx context.Context, key, lang, value string) error {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.cache[lang] == nil {
|
||||
l.cache[lang] = make(map[string]string)
|
||||
if _, exists := l.cache[lang]; !exists {
|
||||
l.cache[lang] = struct {
|
||||
ByCode map[string]string
|
||||
ByKey map[string]string
|
||||
}{
|
||||
ByCode: make(map[string]string),
|
||||
ByKey: make(map[string]string),
|
||||
}
|
||||
}
|
||||
l.cache[lang][key] = value
|
||||
l.cache[lang].ByKey[key] = value
|
||||
|
||||
util.Info(ctx, "更新翻译:", lang, key, "->", value)
|
||||
return nil
|
||||
@@ -781,7 +818,7 @@ func (l *Logic) Delete(ctx context.Context, key, lang string) error {
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if m, ok := l.cache[lang]; ok {
|
||||
delete(m, key)
|
||||
delete(m.ByKey, key)
|
||||
}
|
||||
|
||||
util.Info(ctx, "删除翻译:", lang, key)
|
||||
@@ -811,7 +848,7 @@ func (l *Logic) GetByCategory(ctx context.Context, lang, category string) (map[s
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ImportFromMap 从map批量导入翻译
|
||||
// 从map批量导入翻译
|
||||
func (l *Logic) ImportFromMap(ctx context.Context, lang string, mappings map[string]string, category string) error {
|
||||
if len(mappings) == 0 {
|
||||
return nil
|
||||
@@ -840,11 +877,17 @@ func (l *Logic) ImportFromMap(ctx context.Context, lang string, mappings map[str
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.cache[lang] == nil {
|
||||
l.cache[lang] = make(map[string]string)
|
||||
if _, exists := l.cache[lang]; !exists {
|
||||
l.cache[lang] = struct {
|
||||
ByCode map[string]string
|
||||
ByKey map[string]string
|
||||
}{
|
||||
ByCode: make(map[string]string),
|
||||
ByKey: make(map[string]string),
|
||||
}
|
||||
}
|
||||
for key, value := range mappings {
|
||||
l.cache[lang][key] = value
|
||||
l.cache[lang].ByKey[key] = value
|
||||
}
|
||||
|
||||
util.Info(ctx, "批量导入翻译完成:", lang, category, "共", len(mappings), "条")
|
||||
@@ -878,7 +921,7 @@ func (l *Logic) StartAutoRefresh(ctx context.Context) {
|
||||
|
||||
// En2Zh 英文转中文(静态映射,仅降级时使用)
|
||||
func En2Zh(s string) string {
|
||||
if v, ok := GetI18nLogic().cache["zh"][s]; ok {
|
||||
if v, ok := GetI18nLogic().cache["zh"].ByKey[s]; ok {
|
||||
return v
|
||||
}
|
||||
// 仅降级时才用备份
|
||||
@@ -890,7 +933,7 @@ func En2Zh(s string) string {
|
||||
|
||||
// Zh2En 中文转英文(静态映射,仅降级时使用)
|
||||
func Zh2En(s string) string {
|
||||
if v, ok := GetI18nLogic().cache["en"][s]; ok {
|
||||
if v, ok := GetI18nLogic().cache["en"].ByKey[s]; ok {
|
||||
return v
|
||||
}
|
||||
// 仅降级时才用备份
|
||||
@@ -975,3 +1018,158 @@ func ImportI18nFromMap(ctx context.Context, lang string, mappings map[string]str
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncI18nFromRemote 从远程接口拉取英雄和神器的中英文名称,写入数据库和内存缓存
|
||||
func (l *Logic) SyncI18nFromRemote(ctx context.Context) error {
|
||||
client := gclient.New()
|
||||
|
||||
types := []struct {
|
||||
url string
|
||||
category string
|
||||
}{
|
||||
{consts.SimileHeroName, "hero"},
|
||||
{consts.SimileArtifactName, "artifact"},
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
g.Log().Infof(ctx, "拉取i18n远程数据: %s", t.url)
|
||||
resp, err := client.Get(ctx, t.url)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "拉取远程i18n数据失败: %s, err: %v", t.url, err)
|
||||
continue
|
||||
}
|
||||
content := resp.ReadAll()
|
||||
j := gjson.New(content)
|
||||
if j == nil || j.IsNil() {
|
||||
g.Log().Errorf(ctx, "解析远程i18n数据失败: %s, 内容为空", t.url)
|
||||
continue
|
||||
}
|
||||
var data map[string][]map[string]interface{}
|
||||
if err := j.Var().Scan(&data); err != nil {
|
||||
g.Log().Errorf(ctx, "解析远程i18n数据失败: %s, err: %v", t.url, err)
|
||||
continue
|
||||
}
|
||||
enArr, enOk := data["en"]
|
||||
zhArr, zhOk := data["zh-CN"]
|
||||
if !enOk || !zhOk {
|
||||
g.Log().Warningf(ctx, "远程i18n数据缺少en或zh-CN: %s", t.url)
|
||||
continue
|
||||
}
|
||||
// code->en/zh
|
||||
enMap := make(map[string]string)
|
||||
zhMap := make(map[string]string)
|
||||
for _, item := range enArr {
|
||||
if code, ok := item["code"].(string); ok {
|
||||
if name, ok := item["name"].(string); ok {
|
||||
enMap[code] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range zhArr {
|
||||
if code, ok := item["code"].(string); ok {
|
||||
if name, ok := item["name"].(string); ok {
|
||||
zhMap[code] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
// code->en/zh三元组
|
||||
type row struct{ code, en, zh string }
|
||||
var rows []row
|
||||
for code, enName := range enMap {
|
||||
if zhName, ok := zhMap[code]; ok {
|
||||
rows = append(rows, row{code, enName, zhName})
|
||||
}
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
g.Log().Warningf(ctx, "未生成任何en->zh映射: %s", t.url)
|
||||
continue
|
||||
}
|
||||
|
||||
// 读取数据库现有映射,优先用code查找
|
||||
var dbMappings []*entity.EpicI18NMappings
|
||||
err = dao.EpicI18NMappings.Ctx(ctx).
|
||||
Where(dao.EpicI18NMappings.Columns().Language, "zh").
|
||||
Where(dao.EpicI18NMappings.Columns().Category, t.category).
|
||||
Where(dao.EpicI18NMappings.Columns().Status, 1).
|
||||
Where(dao.EpicI18NMappings.Columns().Deleted, 0).
|
||||
Scan(&dbMappings)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "查询i18n数据库失败: %v", err)
|
||||
continue
|
||||
}
|
||||
dbMapByCode := make(map[string]*entity.EpicI18NMappings)
|
||||
dbMapByKey := make(map[string]*entity.EpicI18NMappings)
|
||||
for _, m := range dbMappings {
|
||||
if m.Code != "" {
|
||||
dbMapByCode[m.Code] = m
|
||||
}
|
||||
dbMapByKey[m.KeyName] = m
|
||||
}
|
||||
|
||||
var toInsert []g.Map
|
||||
var toUpdate []struct{ code, en, zh string }
|
||||
for _, r := range rows {
|
||||
var exist *entity.EpicI18NMappings
|
||||
if r.code != "" {
|
||||
exist = dbMapByCode[r.code]
|
||||
}
|
||||
if exist == nil {
|
||||
exist = dbMapByKey[r.en]
|
||||
}
|
||||
if exist == nil {
|
||||
toInsert = append(toInsert, g.Map{
|
||||
dao.EpicI18NMappings.Columns().KeyName: r.en,
|
||||
dao.EpicI18NMappings.Columns().Language: "zh",
|
||||
dao.EpicI18NMappings.Columns().Value: r.zh,
|
||||
dao.EpicI18NMappings.Columns().Category: t.category,
|
||||
dao.EpicI18NMappings.Columns().Code: r.code,
|
||||
dao.EpicI18NMappings.Columns().Status: 1,
|
||||
dao.EpicI18NMappings.Columns().CreateTime: gtime.Now(),
|
||||
dao.EpicI18NMappings.Columns().UpdateTime: gtime.Now(),
|
||||
})
|
||||
} else if exist.Value != r.zh {
|
||||
toUpdate = append(toUpdate, struct{ code, en, zh string }{r.code, r.en, r.zh})
|
||||
}
|
||||
}
|
||||
|
||||
if len(toInsert) > 0 {
|
||||
_, err := dao.EpicI18NMappings.Ctx(ctx).Data(toInsert).Insert()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "批量导入i18n数据失败: %s, err: %v", t.url, err)
|
||||
} else {
|
||||
g.Log().Infof(ctx, "远程i18n新增导入成功: %s, 共%d条", t.url, len(toInsert))
|
||||
// 更新缓存
|
||||
l.mutex.Lock()
|
||||
if _, exists := l.cache["zh"]; !exists {
|
||||
l.cache["zh"] = struct {
|
||||
ByCode map[string]string
|
||||
ByKey map[string]string
|
||||
}{
|
||||
ByCode: make(map[string]string),
|
||||
ByKey: make(map[string]string),
|
||||
}
|
||||
}
|
||||
for _, item := range toInsert {
|
||||
if keyName, ok := item[dao.EpicI18NMappings.Columns().KeyName].(string); ok {
|
||||
if value, ok := item[dao.EpicI18NMappings.Columns().Value].(string); ok {
|
||||
l.cache["zh"].ByKey[keyName] = value
|
||||
if code, ok := item[dao.EpicI18NMappings.Columns().Code].(string); ok && code != "" {
|
||||
l.cache["zh"].ByCode[code] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
l.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
if len(toUpdate) > 0 {
|
||||
for _, r := range toUpdate {
|
||||
if err := l.Update(ctx, r.en, "zh", r.zh); err != nil {
|
||||
g.Log().Errorf(ctx, "更新i18n数据失败: %s %s -> %s, err: %v", t.category, r.en, r.zh, err)
|
||||
}
|
||||
}
|
||||
g.Log().Infof(ctx, "远程i18n更新成功: %s, 共%d条", t.url, len(toUpdate))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user