feat(layout): 更新应用布局和UI组件样式

- 重构App.vue中的侧边栏布局,更新Logo设计为带有标识和副标题的新样式
- 调整顶部导航栏,增加标题区域显示当前路由标题和日期
- 修改菜单项配置,更新导航标签为更直观的中文描述
- 在Home.vue中替换原有的仪表板为新的Hero卡片和项目进展展示
- 更新Memory.vue中的学习界面,添加学习计划设置和多阶段学习模式
- 集成新的API端点路径,将baseURL从/api调整为/api/v1
- 调整整体视觉风格,包括颜色主题、字体家族和响应式布局
- 更新数据库模型以支持词库功能,添加相关的数据迁移和种子数据
- 调整认证系统的用户ID类型从整型到字符串的变更
- 更改前端构建工具从npm到pnpm,并更新相应的Dockerfile配置
This commit is contained in:
2026-02-27 16:16:57 +08:00
parent a62c2a3aa1
commit 613ce02e9a
47 changed files with 3164 additions and 2579 deletions

View File

@@ -13,5 +13,37 @@ func InitDB(cfg *config.Config) (*gorm.DB, error) {
}
func AutoMigrate(db *gorm.DB) error {
return db.AutoMigrate(&model.User{}, &model.Word{}, &model.MemoryRecord{})
if err := db.AutoMigrate(
&model.User{},
&model.WordBook{},
&model.Word{},
&model.WordSentence{},
&model.WordTranslation{},
&model.WordSynonym{},
&model.WordSynonymItem{},
&model.WordPhrase{},
&model.WordRel{},
&model.WordRelItem{},
&model.MemoryRecord{},
); err != nil {
return err
}
return seedWordBooks(db)
}
func seedWordBooks(db *gorm.DB) error {
books := []model.WordBook{
{ID: model.DefaultWordBookID, Code: "default", Name: "默认词库", SourceBookID: "DEFAULT"},
{ID: "00000000000000000000000000000002", Code: "cet4", Name: "四级词库", SourceBookID: "CET4"},
{ID: "00000000000000000000000000000003", Code: "ielts", Name: "雅思词库", SourceBookID: "IELTS"},
{ID: "00000000000000000000000000000004", Code: "toefl", Name: "托福词库", SourceBookID: "TOEFL"},
}
for i := range books {
book := books[i]
if err := db.Where("code = ?", book.Code).FirstOrCreate(&book).Error; err != nil {
return err
}
}
return nil
}

View File

@@ -16,14 +16,14 @@ type WordHandler struct {
wordService *service.WordService
}
func userIDFromContext(c *gin.Context) int64 {
func userIDFromContext(c *gin.Context) string {
v, ok := c.Get("user_id")
if !ok {
return 1
return ""
}
uid, ok := v.(int64)
if !ok || uid <= 0 {
return 1
uid, ok := v.(string)
if !ok {
return ""
}
return uid
}
@@ -40,7 +40,6 @@ func (h *WordHandler) AddWord(c *gin.Context) {
return
}
// 调用有道API查询
youdaoResp, err := h.wordService.QueryWord(req.Word)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败: " + err.Error()})
@@ -52,7 +51,6 @@ func (h *WordHandler) AddWord(c *gin.Context) {
return
}
// 保存到数据库
word, err := h.wordService.SaveWord(userIDFromContext(c), req.Word, youdaoResp)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存失败: " + err.Error()})
@@ -114,6 +112,22 @@ func (h *WordHandler) GetWords(c *gin.Context) {
})
}
func (h *WordHandler) GetWordByID(c *gin.Context) {
id := strings.TrimSpace(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "非法单词ID"})
return
}
word, err := h.wordService.GetWordByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "单词不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": word})
}
// 获取统计信息
func (h *WordHandler) GetStatistics(c *gin.Context) {
stats, err := h.wordService.GetStatistics(userIDFromContext(c))
@@ -135,7 +149,6 @@ func (h *WordHandler) GetAudio(c *gin.Context) {
return
}
// very small sanitization
word = strings.TrimSpace(word)
if word == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "word为空"})

View File

@@ -0,0 +1,36 @@
package model
import (
"log"
"os"
"strconv"
"sync"
"github.com/bwmarrin/snowflake"
)
var (
snowflakeNode *snowflake.Node
snowflakeOnce sync.Once
)
func getSnowflakeNode() *snowflake.Node {
snowflakeOnce.Do(func() {
nodeID := int64(1)
if raw := os.Getenv("MEMORA_SNOWFLAKE_NODE"); raw != "" {
if parsed, err := strconv.ParseInt(raw, 10, 64); err == nil {
nodeID = parsed
}
}
node, err := snowflake.NewNode(nodeID)
if err != nil {
log.Panicf("init snowflake node failed: %v", err)
}
snowflakeNode = node
})
return snowflakeNode
}
func NewID() string {
return getSnowflakeNode().Generate().String()
}

View File

@@ -1,11 +1,15 @@
package model
import "time"
import (
"time"
"gorm.io/gorm"
)
type MemoryRecord struct {
ID int64 `json:"id" gorm:"primaryKey"`
WordID int64 `json:"word_id" gorm:"index;not null"`
UserID int64 `json:"user_id" gorm:"index;default:1"`
ID string `json:"id" gorm:"primaryKey;size:32"`
WordID string `json:"word_id" gorm:"size:32;index;not null"`
UserID string `json:"user_id" gorm:"size:32;index;not null"`
CorrectCount int `json:"correct_count" gorm:"default:0"`
TotalCount int `json:"total_count" gorm:"default:0"`
MasteryLevel int `json:"mastery_level" gorm:"default:0"`
@@ -13,9 +17,16 @@ type MemoryRecord struct {
NextReviewAt *time.Time `json:"next_review_at" gorm:"index"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Word *Word `json:"word,omitempty" gorm:"foreignKey:WordID"`
Word *Word `json:"word,omitempty" gorm:"foreignKey:WordID;references:ID"`
}
func (MemoryRecord) TableName() string {
return "memory_records"
}
func (m *MemoryRecord) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}

View File

@@ -1,9 +1,13 @@
package model
import "time"
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID int64 `json:"id" gorm:"primaryKey"`
ID string `json:"id" gorm:"primaryKey;size:32"`
Email string `json:"email" gorm:"size:120;uniqueIndex;not null"`
Name string `json:"name" gorm:"size:80;not null"`
PasswordHash string `json:"-" gorm:"size:255;not null"`
@@ -14,3 +18,10 @@ type User struct {
func (User) TableName() string {
return "users"
}
func (u *User) BeforeCreate(tx *gorm.DB) error {
if u.ID == "" {
u.ID = NewID()
}
return nil
}

View File

@@ -1,21 +1,195 @@
package model
import "time"
import (
"time"
"gorm.io/gorm"
)
type Word struct {
ID int64 `json:"id" gorm:"primaryKey"`
Word string `json:"word" gorm:"size:100;uniqueIndex;not null"`
ID string `json:"id" gorm:"primaryKey;size:32"`
BookID string `json:"book_id" gorm:"size:32;index;not null"`
WordRank int `json:"word_rank" gorm:"index;default:0"`
HeadWord string `json:"head_word" gorm:"size:120;index"`
Word string `json:"word" gorm:"size:120;uniqueIndex;not null"`
SourceWordID string `json:"source_word_id" gorm:"size:64;index"`
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"`
UKSpeech string `json:"uk_speech" gorm:"size:255"`
USSpeech string `json:"us_speech" gorm:"size:255"`
PartOfSpeech string `json:"part_of_speech" gorm:"size:50"`
Definition string `json:"definition" gorm:"type:text"`
ExampleSentence string `json:"example_sentence" gorm:"type:text"`
RawPayload string `json:"raw_payload,omitempty" gorm:"type:longtext"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Book *WordBook `json:"book.sql,omitempty" gorm:"foreignKey:BookID;references:ID"`
}
func (Word) TableName() string {
return "words"
}
func (w *Word) BeforeCreate(tx *gorm.DB) error {
if w.ID == "" {
w.ID = NewID()
}
return nil
}
// WordSentence 对应 content.word.content.sentence.sentences
// sContent/sCn 以原字段命名映射。
type WordSentence struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
WordID string `json:"word_id" gorm:"size:32;index;not null"`
SortOrder int `json:"sort_order" gorm:"index;default:0"`
SContent string `json:"s_content" gorm:"type:text"`
SCn string `json:"s_cn" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordSentence) TableName() string {
return "word_sentences"
}
func (m *WordSentence) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}
// WordTranslation 对应 content.word.content.trans
// 保存中英释义及词性信息。
type WordTranslation struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
WordID string `json:"word_id" gorm:"size:32;index;not null"`
SortOrder int `json:"sort_order" gorm:"index;default:0"`
Pos string `json:"pos" gorm:"size:32"`
TranCn string `json:"tran_cn" gorm:"type:text"`
TranOther string `json:"tran_other" gorm:"type:text"`
DescCn string `json:"desc_cn" gorm:"size:64"`
DescOther string `json:"desc_other" gorm:"size:64"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordTranslation) TableName() string {
return "word_translations"
}
func (m *WordTranslation) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}
// WordSynonym + WordSynonymItem 对应 content.word.content.syno.synos[*].hwds[*]
type WordSynonym struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
WordID string `json:"word_id" gorm:"size:32;index;not null"`
SortOrder int `json:"sort_order" gorm:"index;default:0"`
Pos string `json:"pos" gorm:"size:32"`
Tran string `json:"tran" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordSynonym) TableName() string {
return "word_synonyms"
}
func (m *WordSynonym) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}
type WordSynonymItem struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
SynonymID string `json:"synonym_id" gorm:"size:32;index;not null"`
SortOrder int `json:"sort_order" gorm:"index;default:0"`
Word string `json:"word" gorm:"size:120;not null"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordSynonymItem) TableName() string {
return "word_synonym_items"
}
func (m *WordSynonymItem) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}
// WordPhrase 对应 content.word.content.phrase.phrases
type WordPhrase struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
WordID string `json:"word_id" gorm:"size:32;index;not null"`
SortOrder int `json:"sort_order" gorm:"index;default:0"`
PContent string `json:"p_content" gorm:"type:text"`
PCn string `json:"p_cn" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordPhrase) TableName() string {
return "word_phrases"
}
func (m *WordPhrase) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}
// WordRel + WordRelItem 对应 content.word.content.relWord.rels[*].words[*]
type WordRel struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
WordID string `json:"word_id" gorm:"size:32;index;not null"`
SortOrder int `json:"sort_order" gorm:"index;default:0"`
Pos string `json:"pos" gorm:"size:32"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordRel) TableName() string {
return "word_rels"
}
func (m *WordRel) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}
type WordRelItem struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
RelID string `json:"rel_id" gorm:"size:32;index;not null"`
SortOrder int `json:"sort_order" gorm:"index;default:0"`
Hwd string `json:"hwd" gorm:"size:120"`
Tran string `json:"tran" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordRelItem) TableName() string {
return "word_rel_items"
}
func (m *WordRelItem) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = NewID()
}
return nil
}

View File

@@ -0,0 +1,32 @@
package model
import (
"time"
"gorm.io/gorm"
)
const (
DefaultWordBookID = "00000000000000000000000000000001"
DefaultWordBookCode = "default"
)
type WordBook struct {
ID string `json:"id" gorm:"primaryKey;size:32"`
Code string `json:"code" gorm:"size:64;uniqueIndex;not null"`
Name string `json:"name" gorm:"size:120;not null"`
SourceBookID string `json:"source_book_id" gorm:"size:64;uniqueIndex"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (WordBook) TableName() string {
return "word_books"
}
func (b *WordBook) BeforeCreate(tx *gorm.DB) error {
if b.ID == "" {
b.ID = NewID()
}
return nil
}

View File

@@ -20,7 +20,7 @@ func (r *MemoryRepository) Create(record *model.MemoryRecord) error {
return r.db.Create(record).Error
}
func (r *MemoryRepository) FindByWord(userID, wordID int64) (*model.MemoryRecord, error) {
func (r *MemoryRepository) FindByWord(userID, wordID string) (*model.MemoryRecord, error) {
var record model.MemoryRecord
if err := r.db.Where("word_id = ? AND user_id = ?", wordID, userID).First(&record).Error; err != nil {
return nil, err
@@ -28,7 +28,7 @@ func (r *MemoryRepository) FindByWord(userID, wordID int64) (*model.MemoryRecord
return &record, nil
}
func (r *MemoryRepository) FindByID(userID, recordID int64) (*model.MemoryRecord, error) {
func (r *MemoryRepository) FindByID(userID, recordID string) (*model.MemoryRecord, error) {
var record model.MemoryRecord
if err := r.db.Preload("Word").Where("id = ? AND user_id = ?", recordID, userID).First(&record).Error; err != nil {
return nil, err
@@ -40,7 +40,7 @@ func (r *MemoryRepository) Save(record *model.MemoryRecord) error {
return r.db.Save(record).Error
}
func (r *MemoryRepository) Due(userID int64, limit int) ([]model.MemoryRecord, error) {
func (r *MemoryRepository) Due(userID string, limit int) ([]model.MemoryRecord, error) {
var records []model.MemoryRecord
if err := r.db.Preload("Word").Where("user_id = ? AND (next_review_at <= ? OR next_review_at IS NULL)", userID, time.Now()).Limit(limit).Find(&records).Error; err != nil {
return nil, err
@@ -48,7 +48,7 @@ func (r *MemoryRepository) Due(userID int64, limit int) ([]model.MemoryRecord, e
return records, nil
}
func (r *MemoryRepository) ListByUser(userID int64, limit int) ([]model.MemoryRecord, error) {
func (r *MemoryRepository) ListByUser(userID string, limit int) ([]model.MemoryRecord, error) {
var records []model.MemoryRecord
if err := r.db.Preload("Word").Where("user_id = ?", userID).Limit(limit).Find(&records).Error; err != nil {
return nil, err
@@ -56,7 +56,7 @@ func (r *MemoryRepository) ListByUser(userID int64, limit int) ([]model.MemoryRe
return records, nil
}
func (r *MemoryRepository) CountOverview(userID int64) (total, mastered, needReview, todayReviewed int64) {
func (r *MemoryRepository) CountOverview(userID string) (total, mastered, needReview, todayReviewed int64) {
r.db.Model(&model.MemoryRecord{}).Where("user_id = ?", userID).Count(&total)
r.db.Model(&model.MemoryRecord{}).Where("user_id = ? AND mastery_level >= 4", userID).Count(&mastered)
r.db.Model(&model.MemoryRecord{}).Where("user_id = ? AND next_review_at <= ?", userID, time.Now()).Count(&needReview)

View File

@@ -22,6 +22,14 @@ func (r *WordRepository) FindByWord(word string) (*model.Word, error) {
return &w, nil
}
func (r *WordRepository) FindByID(id string) (*model.Word, error) {
var w model.Word
if err := r.db.Where("id = ?", id).First(&w).Error; err != nil {
return nil, err
}
return &w, nil
}
func (r *WordRepository) Create(word *model.Word) error {
return r.db.Create(word).Error
}

View File

@@ -5,7 +5,7 @@ type AddWordRequest struct {
}
type ReviewAnswerRequest struct {
RecordID int64 `json:"record_id" binding:"required"`
RecordID string `json:"record_id" binding:"required"`
Answer string `json:"answer" binding:"required"`
Mode string `json:"mode" binding:"required"` // spelling, en2cn, cn2en
}
@@ -15,7 +15,7 @@ type CreateStudySessionRequest struct {
}
type SubmitStudyAnswerRequest struct {
WordID int64 `json:"word_id" binding:"required"`
WordID string `json:"word_id" binding:"required"`
Answer string `json:"answer" binding:"required"`
Mode string `json:"mode" binding:"required"` // spelling, en2cn, cn2en
}

View File

@@ -12,24 +12,31 @@ func New(wordHandler *handler.WordHandler, authHandler *handler.AuthHandler) *gi
r.Use(middleware.CORS())
api := r.Group("/api")
{
api.POST("/auth/register", authHandler.Register)
api.POST("/auth/login", authHandler.Login)
api.GET("/auth/me", authHandler.Me)
protected := api.Group("")
protected.Use(middleware.AuthRequired())
{
protected.POST("/words", wordHandler.AddWord)
protected.GET("/words", wordHandler.GetWords)
protected.POST("/study/sessions", wordHandler.CreateStudySession)
protected.POST("/study/answers", wordHandler.SubmitStudyAnswer)
protected.GET("/review", wordHandler.GetReviewWords)
protected.POST("/review", wordHandler.SubmitReview)
protected.GET("/stats", wordHandler.GetStatistics)
protected.GET("/audio", wordHandler.GetAudio)
}
}
registerRoutes(api, wordHandler, authHandler)
registerRoutes(api.Group("/v1"), wordHandler, authHandler)
return r
}
func registerRoutes(group *gin.RouterGroup, wordHandler *handler.WordHandler, authHandler *handler.AuthHandler) {
group.POST("/auth/register", authHandler.Register)
group.POST("/auth/login", authHandler.Login)
group.GET("/auth/me", authHandler.Me)
protected := group.Group("")
protected.Use(middleware.AuthRequired())
{
protected.POST("/words", wordHandler.AddWord)
protected.GET("/words", wordHandler.GetWords)
protected.GET("/words/:id", wordHandler.GetWordByID)
protected.POST("/study/sessions", wordHandler.CreateStudySession)
protected.POST("/study/answers", wordHandler.SubmitStudyAnswer)
protected.GET("/review", wordHandler.GetReviewWords)
protected.POST("/review", wordHandler.SubmitReview)
protected.GET("/review/today", wordHandler.GetReviewWords)
protected.POST("/review/submit", wordHandler.SubmitReview)
protected.GET("/stats", wordHandler.GetStatistics)
protected.GET("/stats/overview", wordHandler.GetStatistics)
protected.GET("/audio", wordHandler.GetAudio)
}
}

View File

@@ -63,9 +63,9 @@ func sign(data string) string {
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}
func makeToken(uid int64) string {
func makeToken(uid string) string {
exp := time.Now().Add(7 * 24 * time.Hour).Unix()
payload := fmt.Sprintf("%d:%d", uid, exp)
payload := fmt.Sprintf("%s:%d", uid, exp)
encPayload := base64.RawURLEncoding.EncodeToString([]byte(payload))
sig := sign(encPayload)
return encPayload + "." + sig
@@ -84,35 +84,35 @@ func (s *AuthService) Login(req request.LoginRequest) (string, *model.User, erro
return makeToken(user.ID), &user, nil
}
func ParseToken(tokenString string) (int64, error) {
func ParseToken(tokenString string) (string, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 2 {
return 0, errors.New("token无效")
return "", errors.New("token无效")
}
encPayload, gotSig := parts[0], parts[1]
if sign(encPayload) != gotSig {
return 0, errors.New("token无效")
return "", errors.New("token无效")
}
payloadBytes, err := base64.RawURLEncoding.DecodeString(encPayload)
if err != nil {
return 0, errors.New("token无效")
return "", errors.New("token无效")
}
payloadParts := strings.Split(string(payloadBytes), ":")
if len(payloadParts) != 2 {
return 0, errors.New("token无效")
return "", errors.New("token无效")
}
uid, err := strconv.ParseInt(payloadParts[0], 10, 64)
if err != nil {
return 0, errors.New("token无效")
uid := payloadParts[0]
if uid == "" {
return "", errors.New("token无效")
}
exp, err := strconv.ParseInt(payloadParts[1], 10, 64)
if err != nil {
return 0, errors.New("token无效")
return "", errors.New("token无效")
}
if time.Now().Unix() > exp {
return 0, errors.New("token已过期")
return "", errors.New("token已过期")
}
return uid, nil

View File

@@ -176,7 +176,7 @@ func (s *WordService) DownloadAudio(url, filePath string) error {
}
// 保存单词到数据库
func (s *WordService) SaveWord(userID int64, word string, youdaoResp *model.YoudaoResponse) (*model.Word, error) {
func (s *WordService) SaveWord(userID string, word string, youdaoResp *model.YoudaoResponse) (*model.Word, error) {
// 检查单词是否已存在
if w, err := s.wordRepo.FindByWord(word); err == nil {
// 单词已存在,更新记忆记录
@@ -262,6 +262,8 @@ func (s *WordService) SaveWord(userID int64, word string, youdaoResp *model.Youd
// 创建新单词
newWord := model.Word{
BookID: model.DefaultWordBookID,
HeadWord: word,
Word: word,
PhoneticUK: phoneticUK,
PhoneticUS: phoneticUS,
@@ -284,7 +286,7 @@ func (s *WordService) SaveWord(userID int64, word string, youdaoResp *model.Youd
}
// 创建记忆记录
func (s *WordService) createMemoryRecord(userID, wordID int64) error {
func (s *WordService) createMemoryRecord(userID, wordID string) error {
record := model.MemoryRecord{
WordID: wordID,
UserID: userID,
@@ -296,7 +298,7 @@ func (s *WordService) createMemoryRecord(userID, wordID int64) error {
}
// 更新记忆记录
func (s *WordService) updateMemoryRecord(userID, wordID int64, correct bool) error {
func (s *WordService) updateMemoryRecord(userID, wordID string, correct bool) error {
record, err := s.memoryRepo.FindByWord(userID, wordID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -333,7 +335,7 @@ func (s *WordService) updateMemoryRecord(userID, wordID int64, correct bool) err
}
// 获取待复习单词
func (s *WordService) GetReviewWords(userID int64, mode string, limit int) ([]model.MemoryRecord, error) {
func (s *WordService) GetReviewWords(userID string, mode string, limit int) ([]model.MemoryRecord, error) {
records, err := s.memoryRepo.Due(userID, limit)
if err != nil {
return nil, err
@@ -375,7 +377,7 @@ func containsAny(def string, ans string) bool {
}
// 提交复习答案
func (s *WordService) SubmitReviewAnswer(userID int64, req request.ReviewAnswerRequest) (*response.ReviewResult, error) {
func (s *WordService) SubmitReviewAnswer(userID string, req request.ReviewAnswerRequest) (*response.ReviewResult, error) {
record, err := s.memoryRepo.FindByID(userID, req.RecordID)
if err != nil {
return nil, err
@@ -416,8 +418,12 @@ func (s *WordService) GetAllWords(limit, offset int, query string) ([]model.Word
return s.wordRepo.List(limit, offset, strings.TrimSpace(query))
}
func (s *WordService) GetWordByID(id string) (*model.Word, error) {
return s.wordRepo.FindByID(id)
}
// 获取记忆统计
func (s *WordService) GetStatistics(userID int64) (map[string]interface{}, error) {
func (s *WordService) GetStatistics(userID string) (map[string]interface{}, error) {
totalWords, masteredWords, needReview, todayReviewed := s.memoryRepo.CountOverview(userID)
return map[string]interface{}{
@@ -428,7 +434,7 @@ func (s *WordService) GetStatistics(userID int64) (map[string]interface{}, error
}, nil
}
func (s *WordService) CreateStudySession(userID int64, limit int) ([]model.Word, error) {
func (s *WordService) CreateStudySession(userID string, limit int) ([]model.Word, error) {
if limit <= 0 || limit > 50 {
limit = 10
}
@@ -442,9 +448,9 @@ func (s *WordService) CreateStudySession(userID int64, limit int) ([]model.Word,
return words, nil
}
func (s *WordService) SubmitStudyAnswer(userID int64, req request.SubmitStudyAnswerRequest) (*response.ReviewResult, error) {
func (s *WordService) SubmitStudyAnswer(userID string, req request.SubmitStudyAnswerRequest) (*response.ReviewResult, error) {
reviewReq := request.ReviewAnswerRequest{
RecordID: 0,
RecordID: "",
Answer: req.Answer,
Mode: req.Mode,
}