feat(word): show/play UK-US audio and add example sentence
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user