From e5a245155a6de33de65cde1a7c1d1c7839d2b8d2 Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Thu, 26 Feb 2026 13:22:03 +0800 Subject: [PATCH] feat(word): show/play UK-US audio and add example sentence --- memora-api/internal/model/model.go | 21 ++--- memora-api/internal/service/word.go | 122 ++++++++++++++++++++++----- memora-api/sql/init.sql | 1 + memora-web/src/services/api/types.ts | 1 + memora-web/src/views/Memory.vue | 23 ++++- 5 files changed, 132 insertions(+), 36 deletions(-) diff --git a/memora-api/internal/model/model.go b/memora-api/internal/model/model.go index 790c71f..fe4695e 100644 --- a/memora-api/internal/model/model.go +++ b/memora-api/internal/model/model.go @@ -5,16 +5,17 @@ import ( ) type Word struct { - ID int64 `json:"id" gorm:"primaryKey"` - Word string `json:"word" gorm:"size:100;uniqueIndex;not null"` - PhoneticUK string `json:"phonetic_uk" gorm:"size:255"` - PhoneticUS string `json:"phonetic_us" gorm:"size:255"` - AudioUK string `json:"audio_uk" gorm:"size:500"` - AudioUS string `json:"audio_us" gorm:"size:500"` - PartOfSpeech string `json:"part_of_speech" gorm:"size:50"` - Definition string `json:"definition" gorm:"type:text"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID int64 `json:"id" gorm:"primaryKey"` + Word string `json:"word" gorm:"size:100;uniqueIndex;not null"` + PhoneticUK string `json:"phonetic_uk" gorm:"size:255"` + PhoneticUS string `json:"phonetic_us" gorm:"size:255"` + AudioUK string `json:"audio_uk" gorm:"size:500"` + AudioUS string `json:"audio_us" gorm:"size:500"` + PartOfSpeech string `json:"part_of_speech" gorm:"size:50"` + Definition string `json:"definition" gorm:"type:text"` + ExampleSentence string `json:"example_sentence" gorm:"type:text"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } func (Word) TableName() string { diff --git a/memora-api/internal/service/word.go b/memora-api/internal/service/word.go index 9a1925a..18a1129 100644 --- a/memora-api/internal/service/word.go +++ b/memora-api/internal/service/word.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "regexp" "strings" @@ -26,6 +27,65 @@ func NewWordService(db *gorm.DB) *WordService { return &WordService{db: db} } +type dictAPIEntry struct { + PhoneticUK string + PhoneticUS string + Part string + Example string +} + +// fallback: dictionaryapi.dev(补充音标/词性/例句) +func (s *WordService) fetchDictMeta(word string) *dictAPIEntry { + endpoint := "https://api.dictionaryapi.dev/api/v2/entries/en/" + url.PathEscape(strings.ToLower(word)) + resp, err := http.Get(endpoint) + if err != nil || resp == nil { + return nil + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil + } + var arr []map[string]any + if err := json.Unmarshal(body, &arr); err != nil || len(arr) == 0 { + return nil + } + entry := &dictAPIEntry{} + first := arr[0] + if p, ok := first["phonetics"].([]any); ok { + for _, x := range p { + if m, ok := x.(map[string]any); ok { + if t, ok := m["text"].(string); ok && t != "" { + if entry.PhoneticUK == "" { + entry.PhoneticUK = t + } + if entry.PhoneticUS == "" { + entry.PhoneticUS = t + } + } + } + } + } + if ms, ok := first["meanings"].([]any); ok && len(ms) > 0 { + if m0, ok := ms[0].(map[string]any); ok { + if p, ok := m0["partOfSpeech"].(string); ok { + entry.Part = p + } + if defs, ok := m0["definitions"].([]any); ok && len(defs) > 0 { + if d0, ok := defs[0].(map[string]any); ok { + if ex, ok := d0["example"].(string); ok { + entry.Example = ex + } + } + } + } + } + return entry +} + // 查询单词(调用有道API) func (s *WordService) QueryWord(word string) (*model.YoudaoResponse, error) { // 优先用 app_id,兼容 app_key @@ -117,7 +177,7 @@ func (s *WordService) SaveWord(word string, youdaoResp *model.YoudaoResponse) (* } // 解析有道API响应 - var phoneticUK, phoneticUS, partOfSpeech, definition string + var phoneticUK, phoneticUS, partOfSpeech, definition, exampleSentence string // 解析音标 if youdaoResp.Basic.UkPhonetic != "" { @@ -156,36 +216,52 @@ func (s *WordService) SaveWord(word string, youdaoResp *model.YoudaoResponse) (* partOfSpeech = "n./v./adj." } - // 音频 URL - 有道返回的 speakUrl 示例: https://dict.youdao.com/dictvoice?audio=hello + // 音频 URL:使用有道 dictvoice,type=1(英音) type=2(美音) audioPath := config.AppConfig.Audio.Path var audioUK, audioUS string - if youdaoResp.SpeakUrl != "" { - // 默认是美音,加 _uk 是英音 - audioUK = strings.Replace(youdaoResp.SpeakUrl, "audio=", "audio=word_uk_", 1) - audioUS = youdaoResp.SpeakUrl + ukURL := fmt.Sprintf("https://dict.youdao.com/dictvoice?audio=%s&type=1", url.QueryEscape(word)) + usURL := fmt.Sprintf("https://dict.youdao.com/dictvoice?audio=%s&type=2", url.QueryEscape(word)) + if audioPath != "" { + ukPath := fmt.Sprintf("%s/%s_uk.mp3", audioPath, word) + usPath := fmt.Sprintf("%s/%s_us.mp3", audioPath, word) + go s.DownloadAudio(ukURL, ukPath) + go s.DownloadAudio(usURL, usPath) + audioUK = ukPath + audioUS = usPath + } - // 异步下载音频文件 - go func() { - if audioPath != "" { - ukPath := fmt.Sprintf("%s/%s_uk.mp3", audioPath, word) - usPath := fmt.Sprintf("%s/%s_us.mp3", audioPath, word) - s.DownloadAudio(audioUK, ukPath) - s.DownloadAudio(audioUS, usPath) + // 补充元信息(音标/词性/例句) + meta := s.fetchDictMeta(word) + if meta != nil { + if phoneticUK == "" && meta.PhoneticUK != "" { + phoneticUK = meta.PhoneticUK + } + if phoneticUS == "" && meta.PhoneticUS != "" { + phoneticUS = meta.PhoneticUS + } + if partOfSpeech == "" || partOfSpeech == "n./v./adj." { + if meta.Part != "" { + partOfSpeech = meta.Part } - }() - audioUK = fmt.Sprintf("%s/%s_uk.mp3", audioPath, word) - audioUS = fmt.Sprintf("%s/%s_us.mp3", audioPath, word) + } + if meta.Example != "" { + exampleSentence = meta.Example + } + } + if exampleSentence == "" { + exampleSentence = fmt.Sprintf("Example: %s", word) } // 创建新单词 newWord := model.Word{ - Word: word, - PhoneticUK: phoneticUK, - PhoneticUS: phoneticUS, - AudioUK: audioUK, - AudioUS: audioUS, - PartOfSpeech: partOfSpeech, - Definition: definition, + Word: word, + PhoneticUK: phoneticUK, + PhoneticUS: phoneticUS, + AudioUK: audioUK, + AudioUS: audioUS, + PartOfSpeech: partOfSpeech, + Definition: definition, + ExampleSentence: exampleSentence, } if err := s.db.Create(&newWord).Error; err != nil { diff --git a/memora-api/sql/init.sql b/memora-api/sql/init.sql index 414ca94..f91bb59 100644 --- a/memora-api/sql/init.sql +++ b/memora-api/sql/init.sql @@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS words ( audio_us VARCHAR(500) COMMENT '美式音频文件路径', part_of_speech VARCHAR(50) COMMENT '词性', definition TEXT COMMENT '标准释义', + example_sentence TEXT COMMENT '例句', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_word (word) diff --git a/memora-web/src/services/api/types.ts b/memora-web/src/services/api/types.ts index 37af4f7..f3610b5 100644 --- a/memora-web/src/services/api/types.ts +++ b/memora-web/src/services/api/types.ts @@ -7,6 +7,7 @@ export interface Word { audio_us?: string part_of_speech?: string definition?: string + example_sentence?: string } export interface MemoryRecord { diff --git a/memora-web/src/views/Memory.vue b/memora-web/src/views/Memory.vue index b4befc0..e1f1b05 100644 --- a/memora-web/src/views/Memory.vue +++ b/memora-web/src/views/Memory.vue @@ -32,8 +32,13 @@ {{ saved.word }} {{ saved.part_of_speech }} {{ saved.definition }} - {{ saved.phonetic_uk }} - {{ saved.phonetic_us }} + {{ saved.phonetic_uk || '暂无' }} + {{ saved.phonetic_us || '暂无' }} + {{ saved.example_sentence || '暂无' }} + + 播放英音 + 播放美音 + @@ -41,7 +46,7 @@