根据RESTful
风格,新增应该使用POST
方式,祭出我们的三板斧,无情的搬砖式开发。
添加 Api
api/words/v1/words.go
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 uint `json:"proficiency_level" v:"required|between:1,5" dc:"熟练度,1最低,5最高"`
}
type CreateRes struct {
}
你是否注意到,CreateReq
结构体中与之前定义的model.WordInput
高度相似,我们能不能复用一下,使api
和logic
保持一致,精简代码呢?像这样:
api/words/v1/words.go
type CreateReq struct {
g.Meta `path:"words" method:"post" sm:"创建" tags:"单词"`
model.WordInput
}
...
internal/model/words.go
package model
...
type WordInput struct {
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 uint `json:"proficiency_level" v:"required|between:1,5" dc:"熟练度,1最低,5最高"`
}
答案是,程序正常运行,但是这种方式极不可取。这是因为Api
层是数据接收层,Logic
层是逻辑操作层。这种层层透传的方式会带来如下问题:
- 方法参数定义不明确,不明确的定义意味着会增加额外的协作成本,额外的不明确风险;
- 同一数据结构与多数方法形成耦合,数据结构的任一变动将会影响所有相关方法;
- 相关方法无法充分复用。
最佳实验是,宁可多写几行代码,也不要透传数据模型。
编写Logic
internal/logic/words/words.go
package words
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"star/internal/dao"
"star/internal/model"
"star/internal/model/do"
)
func Create(ctx context.Context, in *model.WordInput) error {
if err := checkWord(ctx, in); err != nil {
return err
}
_, 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
}
func checkWord(ctx context.Context, in *model.WordInput) error {
count, err := dao.Words.Ctx(ctx).Where("uid", in.Uid).Where("word", in.Word).Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New("单词已存在")
}
return nil
}
在Logic
中我们也需要确保同一用户单词不能重复,和数据库保持一致。
Controller调用Logic
单词表中保存有uid
字段,我们需要在logic/users
包中封装一个GetUid
函数提供uid
。
internal/logic/users/account.go
func GetUid(ctx context.Context) (uint, error) {
user, err := Info(ctx)
if err != nil {
return 0, err
}
return user.Id, nil
}
internal/controller/words/words_v1_create.go
package words
import (
"context"
"star/internal/logic/users"
"star/internal/logic/words"
"star/internal/model"
"star/api/words/v1"
)
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
uid, err := users.GetUid(ctx)
if err != nil {
return nil, err
}
err = 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.GetUid
和words.Create
来实现功能。注意,不要在words.Create
中直接调用users.GetUid
,删除Controller
中的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;
id | uid | word | definition | example_sentence | chinese_translation | pronunciation | proficiency_level | created_at | updated_at |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | example | A representative form or pattern. | This is an example sentence. | 例子 | ɪɡˈzɑːmp(ə)l | 3 | 2024/11/12 15:38:50 | 2024/11/12 15:38:50 |