Browse Source

feat: add dict and word apis

RegMs If 3 years ago
parent
commit
5c793c677f
7 changed files with 468 additions and 55 deletions
  1. 100 1
      controller/dicts.go
  2. 36 25
      controller/users.go
  3. 131 0
      controller/words.go
  4. 7 0
      main.go
  5. 56 2
      model/dicts.go
  6. 46 24
      model/users.go
  7. 92 3
      model/words.go

+ 100 - 1
controller/dicts.go

@@ -1,11 +1,110 @@
 package controller
 
-import "github.com/gin-gonic/gin"
+import (
+	"errors"
+	"net/http"
+	"woord-core-service/model"
+
+	"github.com/gin-gonic/gin"
+	"gorm.io/gorm"
+)
+
+type ListDictsResponse struct {
+	Total uint               `json:"total"`
+	List  []model.DictResult `json:"list"`
+}
 
 // 列出所有词库
 func ListDicts(c *gin.Context) {
+	dicts, err := model.ListDicts(&model.Dict{
+		UserID: c.MustGet(AuthUserKey).(uint),
+	})
+	if err != nil {
+		respondUnknownError(c, err)
+		return
+	}
+
+	respondOK(c, &ListDictsResponse{
+		Total: uint(len(dicts)),
+		List:  dicts,
+	})
+}
+
+type GetDictRequest struct {
+	DictID uint `form:"id" binding:"required"`
+}
+
+type GetDictResponse struct {
+	*model.DictResult `json:"dict"`
+	Words             ListWordsResponse `json:"words"`
 }
 
 // 获取词库
 func GetDict(c *gin.Context) {
+	var request GetDictRequest
+	if err := c.ShouldBind(&request); err != nil {
+		respondError(c, http.StatusBadRequest, err)
+		return
+	}
+
+	dict, err := model.GetDict(&model.Dict{
+		Model: &gorm.Model{
+			ID: request.DictID,
+		},
+		UserID: c.MustGet(AuthUserKey).(uint),
+	})
+	if err != nil {
+		if errors.Is(err, model.ErrDictNotFound) {
+			respondError(c, http.StatusNotFound, err)
+		} else {
+			respondUnknownError(c, err)
+		}
+		return
+	}
+
+	words, err := model.ListWords(&model.Word{
+		DictID: request.DictID,
+	})
+	if err != nil {
+		respondUnknownError(c, err)
+		return
+	}
+
+	respondOK(c, &GetDictResponse{
+		DictResult: dict,
+		Words: ListWordsResponse{
+			Total: uint(len(words)),
+			List:  words,
+		},
+	})
+}
+
+type CreateDictRequest struct {
+	DictName     string `form:"name" binding:"required"`
+	ValueTitle   string `form:"value" binding:"required"`
+	MeaningTitle string `form:"meaning" binding:"required"`
+	ExtraTitle   string `form:"extra"`
+}
+
+// 创建词库
+func CreateDict(c *gin.Context) {
+	var request CreateDictRequest
+	if err := c.ShouldBind(&request); err != nil {
+		respondError(c, http.StatusBadRequest, err)
+		return
+	}
+
+	dict, err := model.CreateDict(&model.Dict{
+		Name:         request.DictName,
+		ValueTitle:   request.ValueTitle,
+		MeaningTitle: request.MeaningTitle,
+		ExtraTitle:   request.ExtraTitle,
+		UserID:       c.MustGet(AuthUserKey).(uint),
+	})
+	if err != nil {
+		respondUnknownError(c, err)
+		return
+	}
+
+	respondOK(c, dict)
 }

+ 36 - 25
controller/users.go

@@ -10,14 +10,10 @@ import (
 )
 
 type RegisterRequest struct {
-	Name     string `form:"name" binding:"required"`
+	UserName string `form:"name" binding:"required"`
 	Password string `form:"password" binding:"required"`
 }
 
-type RegisterResponse struct {
-	UserID uint `json:"id"`
-}
-
 // 注册
 func Register(c *gin.Context) {
 	var request RegisterRequest
@@ -26,26 +22,30 @@ func Register(c *gin.Context) {
 		return
 	}
 
-	user := &model.User{Name: request.Name, Password: request.Password}
-	if err := model.CreateUser(user); err != nil {
+	user, err := model.CreateUser(&model.User{
+		Name:     request.UserName,
+		Password: request.Password,
+	})
+	if err != nil {
 		if errors.Is(err, model.ErrUserNameAlreadyExists) {
 			respondError(c, http.StatusConflict, err)
 		} else {
 			respondUnknownError(c, err)
 		}
-	} else {
-		respondOK(c, &RegisterResponse{UserID: user.ID})
+		return
 	}
+
+	respondOK(c, user)
 }
 
 type LoginRequest struct {
-	Name     string `form:"name" binding:"required"`
+	UserName string `form:"name" binding:"required"`
 	Password string `form:"password" binding:"required"`
 }
 
 type LoginResponse struct {
-	UserID uint   `json:"id"`
-	Token  string `json:"token"`
+	*model.UserResult `json:"user"`
+	Token             string `json:"token"`
 }
 
 // 登录
@@ -56,35 +56,46 @@ func Login(c *gin.Context) {
 		return
 	}
 
-	user := &model.User{Name: request.Name, Password: request.Password}
-	if err := model.AuthenticateUser(user); err != nil {
+	user, err := model.AuthenticateUser(&model.User{
+		Name:     request.UserName,
+		Password: request.Password,
+	})
+	if err != nil {
 		if errors.Is(err, model.ErrWrongUserNameOrPassword) {
 			respondError(c, http.StatusUnauthorized, err)
 		} else {
 			respondUnknownError(c, err)
 		}
-	} else if token, err := newToken(user.ID); err != nil {
+		return
+	}
+
+	token, err := newToken(user.ID)
+	if err != nil {
 		respondUnknownError(c, err)
-	} else {
-		respondOK(c, &LoginResponse{UserID: user.ID, Token: token})
+		return
 	}
-}
 
-type GetCurrentUserResponse struct {
-	UserID   uint   `json:"id"`
-	UserName string `json:"name"`
+	respondOK(c, &LoginResponse{
+		UserResult: user,
+		Token:      token,
+	})
 }
 
 // 获取当前用户
 func GetCurrentUser(c *gin.Context) {
-	user := &model.User{Model: gorm.Model{ID: c.MustGet(AuthUserKey).(uint)}}
-	if err := model.GetUser(user); err != nil {
+	user, err := model.GetUser(&model.User{
+		Model: &gorm.Model{
+			ID: c.MustGet(AuthUserKey).(uint),
+		},
+	})
+	if err != nil {
 		if errors.Is(err, model.ErrUserNotFound) {
 			respondError(c, http.StatusNotFound, err)
 		} else {
 			respondUnknownError(c, err)
 		}
-	} else {
-		respondOK(c, &GetCurrentUserResponse{UserID: user.ID, UserName: user.Name})
+		return
 	}
+
+	respondOK(c, user)
 }

+ 131 - 0
controller/words.go

@@ -0,0 +1,131 @@
+package controller
+
+import (
+	"errors"
+	"net/http"
+	"woord-core-service/model"
+
+	"github.com/gin-gonic/gin"
+	"gorm.io/gorm"
+)
+
+type ListWordsResponse struct {
+	Total uint               `json:"total"`
+	List  []model.WordResult `json:"list"`
+}
+
+type CreateWordRequest struct {
+	Value   string `form:"value" binding:"required"`
+	Meaning string `form:"meaning" binding:"required"`
+	Extra   string `form:"extra"`
+	DictID  uint   `form:"dictID" binding:"required"`
+}
+
+// 创建单词
+func CreateWord(c *gin.Context) {
+	var request CreateWordRequest
+	if err := c.ShouldBind(&request); err != nil {
+		respondError(c, http.StatusBadRequest, err)
+		return
+	}
+
+	_, err := model.GetDict(&model.Dict{
+		Model: &gorm.Model{
+			ID: request.DictID,
+		},
+		UserID: c.MustGet(AuthUserKey).(uint),
+	})
+	if err != nil {
+		if errors.Is(err, model.ErrDictNotFound) {
+			respondError(c, http.StatusNotFound, err)
+		} else {
+			respondUnknownError(c, err)
+		}
+		return
+	}
+
+	word, err := model.CreateWord(&model.Word{
+		Value:   request.Value,
+		Meaning: request.Meaning,
+		Extra:   request.Extra,
+		DictID:  request.DictID,
+	})
+	if err != nil {
+		if errors.Is(err, model.ErrWordAlreadyExists) {
+			respondError(c, http.StatusConflict, err)
+		} else {
+			respondUnknownError(c, err)
+		}
+		return
+	}
+
+	respondOK(c, word)
+}
+
+type UpdateWordRequest struct {
+	WordID  uint   `form:"id" binding:"required"`
+	Value   string `form:"value" binding:"required"`
+	Meaning string `form:"meaning" binding:"required"`
+	Extra   string `form:"extra"`
+	Star    bool   `form:"star"`
+}
+
+// 更新单词
+func UpdateWord(c *gin.Context) {
+	var request UpdateWordRequest
+	if err := c.ShouldBind(&request); err != nil {
+		respondError(c, http.StatusBadRequest, err)
+		return
+	}
+
+	word, err := model.UpdateWord(&model.Word{
+		Model: &gorm.Model{
+			ID: request.WordID,
+		},
+		Value:   request.Value,
+		Meaning: request.Meaning,
+		Extra:   request.Extra,
+		Star:    request.Star,
+	})
+	if err != nil {
+		if errors.Is(err, model.ErrWordNotFound) {
+			respondError(c, http.StatusNotFound, err)
+		} else if errors.Is(err, model.ErrWordAlreadyExists) {
+			respondError(c, http.StatusConflict, err)
+		} else {
+			respondUnknownError(c, err)
+		}
+		return
+	}
+
+	respondOK(c, word)
+}
+
+type DeleteWordRequest struct {
+	WordID uint `form:"id" binding:"required"`
+}
+
+// 删除单词
+func DeleteWord(c *gin.Context) {
+	var request DeleteWordRequest
+	if err := c.ShouldBind(&request); err != nil {
+		respondError(c, http.StatusBadRequest, err)
+		return
+	}
+
+	err := model.DeleteWord(&model.Word{
+		Model: &gorm.Model{
+			ID: request.WordID,
+		},
+	})
+	if err != nil {
+		if errors.Is(err, model.ErrWordNotFound) {
+			respondError(c, http.StatusNotFound, err)
+		} else {
+			respondUnknownError(c, err)
+		}
+		return
+	}
+
+	respondOK(c, nil)
+}

+ 7 - 0
main.go

@@ -41,6 +41,13 @@ func main() {
 
 		auth.GET("/dict/list", controller.ListDicts)
 		auth.GET("/dict/get", controller.GetDict)
+		auth.POST("/dict/create", controller.CreateDict)
+		auth.PUT("/dict/update")
+		auth.DELETE("/dict/delete")
+
+		auth.POST("/word/create", controller.CreateWord)
+		auth.PUT("/word/update", controller.UpdateWord)
+		auth.DELETE("/word/delete", controller.DeleteWord)
 	}
 
 	r.Run(":8080")

+ 56 - 2
model/dicts.go

@@ -1,10 +1,20 @@
 package model
 
-import "gorm.io/gorm"
+import (
+	"errors"
+	"fmt"
+	"woord-core-service/global"
+
+	"gorm.io/gorm"
+)
+
+var (
+	ErrDictNotFound = fmt.Errorf("词库不存在")
+)
 
 // 词库
 type Dict struct {
-	gorm.Model
+	*gorm.Model
 
 	// 词库名
 	Name string
@@ -24,3 +34,47 @@ type Dict struct {
 	// 所属用户 ID
 	UserID uint
 }
+
+type DictResult struct {
+	ID           uint   `json:"id"`
+	Name         string `json:"name"`
+	ValueTitle   string `json:"value"`
+	MeaningTitle string `json:"meaning"`
+	ExtraTitle   string `json:"extra"`
+	WordCount    uint   `json:"wordCount"`
+}
+
+// 列出所有词库
+func ListDicts(dict *Dict) ([]DictResult, error) {
+	result := []DictResult{}
+	if err := global.DB.Model(&Dict{}).Select("*, (?) AS word_count", global.DB.Model(&Word{}).Select("COUNT()").Where("dict_id = dicts.id")).Find(&result, dict, "user_id").Error; err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+// 获取词库
+func GetDict(dict *Dict) (*DictResult, error) {
+	result := &DictResult{}
+	if err := global.DB.Model(&Dict{}).Select("*, (?) AS word_count", global.DB.Model(&Word{}).Select("COUNT()").Where("dict_id = dicts.id")).Take(result, dict, "id", "user_id").Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, ErrDictNotFound
+		}
+		return nil, err
+	}
+	return result, nil
+}
+
+// 创建词库
+func CreateDict(dict *Dict) (*DictResult, error) {
+	if err := global.DB.Select("name", "value_title", "meaning_title", "extra_title", "user_id").Create(dict).Error; err != nil {
+		return nil, err
+	}
+	return &DictResult{
+		ID:           dict.ID,
+		Name:         dict.Name,
+		ValueTitle:   dict.ValueTitle,
+		MeaningTitle: dict.MeaningTitle,
+		ExtraTitle:   dict.ExtraTitle,
+	}, nil
+}

+ 46 - 24
model/users.go

@@ -10,12 +10,18 @@ import (
 	"gorm.io/gorm"
 )
 
+var (
+	ErrUserNotFound            = fmt.Errorf("用户不存在")
+	ErrUserNameAlreadyExists   = fmt.Errorf("用户名已存在")
+	ErrWrongUserNameOrPassword = fmt.Errorf("用户名或密码错误")
+)
+
 // 用户
 type User struct {
-	gorm.Model
+	*gorm.Model
 
 	// 用户名
-	Name string `gorm:"unique"`
+	Name string `gorm:"unique;index"`
 
 	// 用户密码,保存为 HS256 散列值
 	Password string
@@ -24,11 +30,10 @@ type User struct {
 	Dicts []Dict
 }
 
-var (
-	ErrUserNameAlreadyExists   = fmt.Errorf("用户名已存在")
-	ErrWrongUserNameOrPassword = fmt.Errorf("用户名或密码错误")
-	ErrUserNotFound            = fmt.Errorf("用户不存在")
-)
+type UserResult struct {
+	ID   uint   `json:"id"`
+	Name string `json:"name"`
+}
 
 // 计算密码的 HS256 散列值
 func hashPassword(password string) string {
@@ -37,31 +42,48 @@ func hashPassword(password string) string {
 	return fmt.Sprintf("%x", mac.Sum(nil))
 }
 
+// 获取用户
+func GetUser(user *User) (*UserResult, error) {
+	result := &UserResult{}
+	if err := global.DB.Model(&User{}).Take(result, user, "id").Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, ErrUserNotFound
+		}
+		return nil, err
+	}
+	return result, nil
+}
+
 // 创建用户
-func CreateUser(user *User) error {
-	if global.DB.Take(user, user, "name").Error == nil {
-		return ErrUserNameAlreadyExists
+func CreateUser(user *User) (*UserResult, error) {
+	if err := global.DB.Model(&User{}).Take(&struct{ ID uint }{}, user, "name").Error; !errors.Is(err, gorm.ErrRecordNotFound) {
+		if err != nil {
+			return nil, err
+		}
+		return nil, ErrUserNameAlreadyExists
 	}
 
 	user.Password = hashPassword(user.Password)
-	return global.DB.Select("name", "password").Create(user).Error
+
+	if err := global.DB.Select("name", "password").Create(user).Error; err != nil {
+		return nil, err
+	}
+	return &UserResult{
+		ID:   user.ID,
+		Name: user.Name,
+	}, nil
 }
 
 // 认证用户
-func AuthenticateUser(user *User) error {
+func AuthenticateUser(user *User) (*UserResult, error) {
 	user.Password = hashPassword(user.Password)
-	if err := global.DB.Take(user, user, "name", "password").Error; errors.Is(err, gorm.ErrRecordNotFound) {
-		return ErrWrongUserNameOrPassword
-	} else {
-		return err
-	}
-}
 
-// 获取用户
-func GetUser(user *User) error {
-	if err := global.DB.Take(user, user, "id").Error; errors.Is(err, gorm.ErrRecordNotFound) {
-		return ErrUserNotFound
-	} else {
-		return err
+	result := &UserResult{}
+	if err := global.DB.Model(&User{}).Take(result, user, "name", "password").Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, ErrWrongUserNameOrPassword
+		}
+		return nil, err
 	}
+	return result, nil
 }

+ 92 - 3
model/words.go

@@ -1,13 +1,24 @@
 package model
 
-import "gorm.io/gorm"
+import (
+	"errors"
+	"fmt"
+	"woord-core-service/global"
+
+	"gorm.io/gorm"
+)
+
+var (
+	ErrWordNotFound      = fmt.Errorf("单词不存在")
+	ErrWordAlreadyExists = fmt.Errorf("单词已存在")
+)
 
 // 单词
 type Word struct {
-	gorm.Model
+	*gorm.Model
 
 	// 单词
-	Value string
+	Value string `gorm:"index"`
 
 	// 词义
 	Meaning string
@@ -21,3 +32,81 @@ type Word struct {
 	// 所属词库 ID
 	DictID uint
 }
+
+type WordResult struct {
+	ID      uint   `json:"id"`
+	Value   string `json:"value"`
+	Meaning string `json:"meaning"`
+	Extra   string `json:"extra"`
+	Star    bool   `json:"star"`
+}
+
+// 列出所有单词
+func ListWords(word *Word) ([]WordResult, error) {
+	result := []WordResult{}
+	if err := global.DB.Model(&Word{}).Find(&result, word, "dict_id").Error; err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+// 创建单词
+func CreateWord(word *Word) (*WordResult, error) {
+	if err := global.DB.Model(&Word{}).Take(&struct{ ID uint }{}, word, "value", "dict_id").Error; !errors.Is(err, gorm.ErrRecordNotFound) {
+		if err != nil {
+			return nil, err
+		}
+		return nil, ErrWordAlreadyExists
+	}
+
+	if err := global.DB.Select("value", "meaning", "extra", "dict_id").Create(word).Error; err != nil {
+		return nil, err
+	}
+	return &WordResult{
+		ID:      word.ID,
+		Value:   word.Value,
+		Meaning: word.Meaning,
+		Extra:   word.Extra,
+	}, nil
+}
+
+// 更新单词
+func UpdateWord(word *Word) (*WordResult, error) {
+	dictID := &struct{ DictID uint }{}
+	if err := global.DB.Model(&Word{}).Take(dictID, word, "id").Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, ErrWordNotFound
+		}
+		return nil, err
+	}
+
+	if err := global.DB.Model(&Word{}).Where("id <> ? AND value = ?", word.ID, word.Value).Take(&struct{ ID uint }{}, dictID, "dict_id").Error; !errors.Is(err, gorm.ErrRecordNotFound) {
+		if err != nil {
+			return nil, err
+		}
+		return nil, ErrWordAlreadyExists
+	}
+
+	if err := global.DB.Model(word).Select("value", "meaning", "extra", "star").Updates(word).Error; err != nil {
+		return nil, err
+	}
+	return &WordResult{
+		ID:      word.ID,
+		Value:   word.Value,
+		Meaning: word.Meaning,
+		Extra:   word.Extra,
+		Star:    word.Star,
+	}, nil
+}
+
+// 删除单词
+func DeleteWord(word *Word) error {
+	if err := global.DB.Model(&Word{}).Take(&struct{ ID uint }{}, word, "id").Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return ErrWordNotFound
+		}
+		return err
+	}
+
+	return global.DB.Delete(word).Error
+}