跳到主要内容
版本:2.8.x(Latest)

根据RESTful风格,新增应该使用POST方式,祭出我们的三板斧,无情的搬砖式开发。

添加 Api


api/words/v1/words.go

type ProficiencyLevel uint  

const (
ProficiencyLevel1 ProficiencyLevel = iota + 1
ProficiencyLevel2
ProficiencyLevel3
ProficiencyLevel4
ProficiencyLevel5
)

type CreateReq struct {
g.Meta `path:"words" method:"post" sm:"创建" tags:"单词"`
Word string `json:"word" v:"required|length:1,100" dc:"单词"`
Definition string `json:"definition" v:"required|length:1,300" dc:"单词定义"`
ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"例句"`
ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"中文翻译"`
Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"发音"`
ProficiencyLevel ProficiencyLevel `json:"proficiency_level" v:"required|between:1,5" dc:"熟练度,1最低,5最高"`
}

type CreateRes struct {
}

在这里我们自定义了一个数据类型ProficiencyLevel,表示单词的掌握程度,并定义了五个枚举值:ProficiencyLevel1-5从低到高表示级别。

这种自定义类型加上固定枚举值的方式是一种高级的程序设计技巧,可以广泛用在各类状态上,比如订单状态,项目阶段等。新手在编程总喜欢使用int一把梭,最后造成代码里全是1,2,3...这种数字状态,导致代码的可读性和可维护性较差。

编写Logic


同样的,定义Words对象,新建New函数用作实例化。

internal/logic/words/words.go

package words

type Words struct {
}

func New() *Words {
return &Words{}
}

internal/logic/words/words.go

...

type CreateInput struct {
Uid uint
Word string
Definition string
ExampleSentence string
ChineseTranslation string
Pronunciation string
ProficiencyLevel v1.ProficiencyLevel
}

func (w *Words) Create(ctx context.Context, in CreateInput) error {
var cls = dao.Words.Columns()

count, err := dao.Words.Ctx(ctx).
Where(cls.Uid, in.Uid).
Where(cls.Word, in.Word).Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New("单词已存在")
}

_, err = dao.Words.Ctx(ctx).Data(do.Words{
Uid: in.Uid,
Word: in.Word,
Definition: in.Definition,
ExampleSentence: in.ExampleSentence,
ChineseTranslation: in.ChineseTranslation,
Pronunciation: in.Pronunciation,
ProficiencyLevel: in.ProficiencyLevel,
}).Insert()
if err != nil {
return err
}
return nil
}

Logic中我们也需要确保同一用户单词不能重复,和数据库保持一致。

account logic

单词表中保存有uid字段,我们需要在logic/users包中封装一个GetUid函数提供uid

internal/logic/users/users_account.go

func (u *Users) GetUid(ctx context.Context) (uint, error) {  
user, err := u.Info(ctx)
if err != nil {
return 0, err
}
return user.Id, nil
}

Controller调用Logic


在创建单词的控制器中,我们需要调用accountwords两个logic,我们将他封装到控制器中。

internal/controller/words/words_new.go

...


package words

import (
"star/api/words"
usersLogic "star/internal/logic/users"
wordsLogic "star/internal/logic/words"
)

type ControllerV1 struct {
users *usersLogic.Users
words *wordsLogic.Words
}

func NewV1() words.IWordsV1 {
return &ControllerV1{
users: usersLogic.New(),
words: wordsLogic.New(),
}
}

internal/controller/words/words_v1_create.go

package words  

import (
"context"

"star/internal/model"
"star/api/words/v1"
)

func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
uid, err := c.users.GetUid(ctx)
if err != nil {
return nil, err
}
err = c.words.Create(ctx, &model.WordInput{
Uid: uid,
Word: req.Word,
Definition: req.Definition,
ExampleSentence: req.ExampleSentence,
ChineseTranslation: req.ChineseTranslation,
Pronunciation: req.Pronunciation,
ProficiencyLevel: model.ProficiencyLevel(req.ProficiencyLevel),
})
return nil, err
}

Controller中调用两个Logic层级的方法:users.GetUidwords.Create来实现功能。注意,不要在words.Create中直接调用users.GetUid,这样会加重words包的耦合。

最佳实验是,尽量保证Logic函数的功能单一化,在Controller中多次调用Logic完成功能。

注册控制器


internal/cmd/cmd.go

package cmd  

...

var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Group("/v1", func(group *ghttp.RouterGroup) {
group.Bind(
users.NewV1(),
)
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(middleware.Auth)
group.Bind(
account.NewV1(),
words.NewV1(),
)
})
})
})
s.Run()
return nil
},
}
)

控制器注册到与account.NewV1()同一个路由组下,确保能经过Auth中间件。

接口测试


$ curl -X POST http://127.0.0.1:8000/v1/words \
-H "Authorization: eyJhbGci...5U" \
-H "Content-Type: application/json" \
-d '{
"word": "example",
"definition": "A representative form or pattern.",
"example_sentence": "This is an example sentence.",
"chinese_translation": "例子",
"pronunciation": "ɪɡˈzɑːmp(ə)l",
"proficiency_level": 3
}'

{
    "code": 0,
    "message": "",
    "data": null
}

执行命令,查询数据是否正常添加:

$ SELECT * FROM words;
iduidworddefinitionexample_sentencechinese_translationpronunciationproficiency_levelcreated_atupdated_at
11exampleA representative form or pattern.This is an example sentence.例子ɪɡˈzɑːmp(ə)l32024/11/12 15:38:502024/11/12 15:38:50