feat(word): show/play UK-US audio and add example sentence

This commit is contained in:
2026-02-26 13:22:03 +08:00
parent e2a9ebc7b7
commit e5a245155a
5 changed files with 132 additions and 36 deletions

View File

@@ -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:使用有道 dictvoicetype=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 {