diff --git "a/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" "b/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" index 55937263849..2429e4d1970 100644 --- "a/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" +++ "b/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" @@ -29,18 +29,20 @@ type LoginRes struct { ## 编写Logic --- -登录逻辑的的难点在于生成`Token`。准备好一个随机字符串`JwtKey`用作签名,我们将其定义在`utility`目录下。 +登录逻辑的的难点在于生成`Token`。准备好一个随机字符串`JwtKey`用作签名。 -*utility/jwt.go* +*internal/consts/consts.go* ```go -package utility +package consts -var JwtKey = []byte("db03d23b03ec405793b38f10592a2f34") +const ( + JwtKey = "db03d23b03ec405793b38f10592a2f34" +) ``` 编写核心逻辑,先根据用户名进行`Where`查询,获取到数据后,将密码再次加密,如果和数据库中的密文一致则说明是合法用户,生成`Token`返回。 -*internal/logic/users/account.go* +*internal/logic/users/users_account.go* ```go package users @@ -56,7 +58,7 @@ import ( "star/utility" ) -type userClaims struct { +type jwtClaims struct { Id uint Username string jwt.RegisteredClaims @@ -66,20 +68,20 @@ func (u *Users) Login(ctx context.Context, username, password string) (tokenStri var user entity.Users err = dao.Users.Ctx(ctx).Where("username", username).Scan(&user) if err != nil { - return "", errors.New("用户名或密码错误") + return "", gerror.New("用户名或密码错误") } if user.Id == 0 { - return "", errors.New("用户不存在") + return "", gerror.New("用户不存在") } // 将密码加密后与数据库中的密码进行比对 - if user.Password != encryptPassword(password) { - return "", errors.New("用户名或密码错误") + if user.Password != u.encryptPassword(password) { + return "", gerror.New("用户名或密码错误") } // 生成token - uc := &userClaims{ + uc := &jwtClaims{ Id: user.Id, Username: user.Username, RegisteredClaims: jwt.RegisteredClaims{ @@ -87,7 +89,7 @@ func (u *Users) Login(ctx context.Context, username, password string) (tokenStri }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, uc) - return token.SignedString(utility.JwtKey) + return token.SignedString(consts.JwtKey) } ``` diff --git "a/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" "b/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" index dffbc6c5855..47465e1c52d 100644 --- "a/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" +++ "b/docs/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" @@ -74,7 +74,7 @@ type InfoRes struct { ## 编写Logic --- -*internal/logic/users/account.go* +*internal/logic/users/users_account.go* ```go package users @@ -93,14 +93,12 @@ import ( ... func (u *Users) Info(ctx context.Context) (user *entity.Users, err error) { - user = new(entity.Users) tokenString := g.RequestFromCtx(ctx).Request.Header.Get("Authorization") - - tokenClaims, _ := jwt.ParseWithClaims(tokenString, &userClaims{}, func(token *jwt.Token) (interface{}, error) { - return utility.JwtKey, nil + tokenClaims, _ := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) { + return consts.JwtKey, nil }) - if claims, ok := tokenClaims.Claims.(*userClaims); ok && tokenClaims.Valid { + if claims, ok := tokenClaims.Claims.(*jwtClaims); ok && tokenClaims.Valid { err = dao.Users.Ctx(ctx).Where("id", claims.Id).Scan(&user) } return @@ -115,7 +113,7 @@ func (u *Users) Info(ctx context.Context) (user *entity.Users, err error) { --- 同样将`logic`注册到控制器中。 -*internal/controller/users/users_new.go* +*internal/controller/account/account_new.go* ```go ... @@ -123,16 +121,16 @@ package account import ( "star/api/account" - usersL "star/internal/logic/users" + "star/internal/logic/users" ) type ControllerV1 struct { - users *usersL.Users + users *users.Users } func NewV1() account.IAccountV1 { return &ControllerV1{ - users: &usersL.Users{}, + users: users.New(), } } ``` @@ -143,23 +141,21 @@ package account import ( "context" - + "star/api/account/v1" - "star/internal/logic/users" ) func (c *ControllerV1) Info(ctx context.Context, req *v1.InfoReq) (res *v1.InfoRes, err error) { user, err := c.users.Info(ctx) if err != nil { - return nil, err + return nil, err } return &v1.InfoRes{ - Username: user.Username, - Email: user.Email, - CreatedAt: user.CreatedAt, - UpdatedAt: user.UpdatedAt, - }, nil - return + Username: user.Username, + Email: user.Email, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + }, nil } ``` diff --git "a/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" "b/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" index df2d774673f..e346ac73fc1 100644 --- "a/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" +++ "b/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" @@ -47,7 +47,7 @@ done! --- `Logic` 是业务逻辑层,存放在`internal/logic`下,供`Controller`调用从而实现具体的业务逻辑。 -定义一个`Users`对象: +定义一个`Users`对象,并新建一个`New`函数用作实例化它: *internal/logic/users/users.go* ```go @@ -55,11 +55,15 @@ package users type Users struct { } + +func New() *Users { + return &Users{} +} ``` 编写注册方法: -*internal/logic/users/register.go* +*internal/logic/users/users_register.go* ```go package users @@ -99,17 +103,17 @@ package users import ( "star/api/users" - usersL "star/internal/logic/users" + userLogic "star/internal/logic/users" ) type ControllerV1 struct { - users *usersL.Users + users *userLogic.Users } func NewV1() users.IUsersV1 { return &ControllerV1{ - users: &usersL.Users{}, - } + users: userLogic.New(), + } } ``` diff --git "a/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" "b/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" index 2e02b94ead9..fd9018443f1 100644 --- "a/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" +++ "b/docs/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" @@ -147,7 +147,7 @@ $ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: applica --- 用户名是登录的重要依据,如果碰巧系统中有两个同名用户,则会出现重大的逻辑混乱。所以我们需要在数据入库前查询该用户是否存在,如果存在,则返回错误信息,提示用户已经存在。 -*internal/logic/users/register.go* +*internal/logic/users/users_register.go* ```go package users @@ -201,22 +201,24 @@ ALTER TABLE users ADD UNIQUE (username); --- 密码明文保存是一种非常不安全的行为,通常的做法是对其`hash`计算后存入数据库,例如`md5`、`SHA-1`等。 -新增一个函数`encryptPassword`实现密码加密功能。 +新增一个方法`encryptPassword`实现密码加密功能。 -*internal/logic/users/utility.go* +*internal/logic/users/users.go* ```go package users import "github.com/gogf/gf/v2/crypto/gmd5" -func encryptPassword(password string) string { +... + +func (u *Users) encryptPassword(password string) string { return gmd5.MustEncryptString(password) } ``` `gmd5`组件帮助我们快速实现`md5`加密功能。编写注册逻辑代码,引入密码加密。 -*internal/logic/users/register.go* +*internal/logic/users/users_register.go* ```go package users @@ -227,7 +229,7 @@ func (u *Users) Register(ctx context.Context, username, password, email string) _, err := dao.Users.Ctx(ctx).Data(do.Users{ Username: username, - Password: encryptPassword(password), + Password: u.encryptPassword(password), Email: email, }).Insert() if err != nil { @@ -257,20 +259,24 @@ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: applicati ## Register 函数优化 --- -在`model`层自定义一个数据模型,用作`Logic`层的入参。 +自定义一个数据模型,用作`Logic`层的入参。 -*internal/model/users.go* +*internal/logic/users/users_register.go* ```go package model - -type UserInput struct { + +... + +type RegisterInput struct { Username string Password string Email string } + +... ``` -*internal/logic/users/register.go* +*internal/logic/users/users_register.go* ```go package users @@ -279,7 +285,7 @@ import ( ... ) -func (u *Users) Register(ctx context.Context, in *model.UserInput) error { +func (u *Users) Register(ctx context.Context, in RegisterInput) error { if err := u.checkUser(ctx, in.Username); err != nil { return err } @@ -298,22 +304,24 @@ func (u *Users) Register(ctx context.Context, in *model.UserInput) error { ... ``` -更改`Controller`层,将`UserInput`传入。 +更改`Controller`层,将`RegisterInput`传入。 *internal/controller/users/users_v1_register.go* ```go package users - -import ( - "star/internal/model" - ... -) + +import ( + "context" + + "star/api/users/v1" + "star/internal/logic/users" +) func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) { - err = c.users.Register(ctx, &model.UserInput{ - Username: req.Username, - Password: req.Password, - Email: req.Email, + err = c.users.Register(ctx, users.RegisterInput{ + Username: req.Username, + Password: req.Password, + Email: req.Email, }) return nil, err } diff --git "a/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" "b/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" index 6c33ba515c4..f88aa030d9d 100644 --- "a/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" +++ "b/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" @@ -11,13 +11,12 @@ description: '使用GoFrame框架设计随机获取单词接口。提供路径wo - 返回的数据基本一致。 ## 添加Api --- -*api/words/v1/learn_words.go* +*api/words/v1/words_learn.go* ```go package v1 import ( "github.com/gogf/gf/v2/frame/g" - "star/internal/model" ) type RandListReq struct { @@ -36,7 +35,7 @@ type RandListRes struct { ## 编写Logic --- -*internal/logic/words/learn_words.go* +*internal/logic/words/words_learn.go* ```go package words @@ -49,21 +48,20 @@ import ( "star/internal/model/entity" ) -// Rand 随机若干获取单词 func (w *Words) Rand(ctx context.Context, uid, limit uint) ([]entity.Words, error) { if limit <= 0 { - limit = 50 + limit = 50 } var ( - list = make([]entity.Words, limit) - err error + err error + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + list = make([]entity.Words, limit) ) - db := dao.Words.Ctx(ctx) if uid > 0 { - db = db.Where("uid", uid) + orm = orm.Where(cls.Uid, uid) } - - err = db.Limit(int(limit)).OrderRandom().Scan(&list) + err = orm.Limit(int(limit)).OrderRandom().Scan(&list) return list, err } ``` @@ -80,32 +78,31 @@ import ( "context" "star/api/words/v1" - "star/internal/model" ) func (c *ControllerV1) RandList(ctx context.Context, req *v1.RandListReq) (res *v1.RandListRes, err error) { uid, err := c.users.GetUid(ctx) if err != nil { - return nil, err + return nil, err } wordList, err := c.words.Rand(ctx, uid, req.Limit) if err != nil { - return nil, err + return nil, err } var list []v1.List for _, v := range wordList { - list = append(list, v1.List{ - Id: v.Id, - Word: v.Word, - Definition: v.Definition, - ProficiencyLevel: model.ProficiencyLevel(v.ProficiencyLevel), - }) + list = append(list, v1.List{ + Id: v.Id, + Word: v.Word, + Definition: v.Definition, + ProficiencyLevel: v1.ProficiencyLevel(v.ProficiencyLevel), + }) } return &v1.RandListRes{ - List: list, + List: list, }, nil } ``` diff --git "a/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" "b/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" index 5906b4d3394..babaf892f8c 100644 --- "a/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" +++ "b/docs/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" @@ -8,14 +8,14 @@ description: '通过GoFrame框架实现设置单词掌握程度功能。使用PA 设置掌握程度的功能本质上是一种编辑,只改变`ProficiencyLevel`一个字段,所以这里使用`PATCH`方式比`PUT`更为合适。 ## 添加Api --- -*api/words/v1/learn_words.go* +*api/words/v1/words_learn.go* ```go ... type SetLevelReq struct { g.Meta `path:"words/{id}/level" method:"patch"` - Id uint `json:"id" v:"required"` - Level model.ProficiencyLevel `json:"level" v:"required|between:1,5"` + Id uint `json:"id" v:"required"` + Level ProficiencyLevel `json:"level" v:"required|between:1,5"` } type SetLevelRes struct { @@ -26,24 +26,27 @@ type SetLevelRes struct { ## 编写Logic --- -*internal/logic/words/learn_words.go* +*internal/logic/words/words_learn.go* ```go ... // SetLevel 设置单词熟练度 -func (w *Words) SetLevel(ctx context.Context, uid, id uint, level model.ProficiencyLevel) error { +func (w *Words) SetLevel(ctx context.Context, uid, id uint, level v1.ProficiencyLevel) error { if level < 0 || level > 5 { return gerror.New("熟练度值不合法") } - db := dao.Words.Ctx(ctx) + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) if uid > 0 { - db = db.Where("uid", uid) + orm = orm.Where(cls.Uid, uid) } - _, err := db.Data("proficiency_level", level).Where("id", id).Update() + _, err := orm.Data(cls.ProficiencyLevel, level).Where(cls.Id, id).Update() return err -} +}**** ``` 为了防止数据异常,我们要在入库前检测等级是否在`1-5`之间。 diff --git "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" index 8277f2649e7..31c9e5b922f 100644 --- "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" +++ "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -51,36 +51,3 @@ internal/model/entity/words.go internal/dao/internal/words.go internal/dao/words.go ``` - -## 自定义数据模型 ---- -像用户模型一样,如法炮制的在`model`自定义一个数据模型,用作`Logic`入参。 - -*internal/model/words.go* -```go -package model - -type ProficiencyLevel uint - -const ( - ProficiencyLevel1 ProficiencyLevel = iota + 1 - ProficiencyLevel2 - ProficiencyLevel3 - ProficiencyLevel4 - ProficiencyLevel5 -) - -type WordInput struct { - Uid uint - Word string - Definition string - ExampleSentence string - ChineseTranslation string - Pronunciation string - ProficiencyLevel ProficiencyLevel -} -``` - -在这里我们自定义了一个数据类型`ProficiencyLevel`,表示单词的掌握程度,并定义了五个枚举值:`ProficiencyLevel1-5`从低到高表示级别。 - -这种自定义类型加上固定枚举值的方式是一种高级的程序设计技巧,可以广泛用在各类状态上,比如订单状态,项目阶段等。新手在编程总喜欢使用`int`一把梭,最后造成代码里全是`1,2,3...`这种数字状态,导致代码的可读性和可维护性较差。 \ No newline at end of file diff --git "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" index 3c9693ce080..208e13e675f 100644 --- "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" +++ "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" @@ -10,79 +10,78 @@ description: '使用GoFrame框架实现RESTful风格的单词创建API,包括 --- *api/words/v1/words.go* ```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 uint `json:"proficiency_level" v:"required|between:1,5" dc:"熟练度,1最低,5最高"` + 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 { } ``` -您是否注意到,`CreateReq`结构体中与之前定义的`model.WordInput`高度相似,我们能不能复用一下,使`api`和`logic`保持一致,精简代码呢?像这样: +在这里我们自定义了一个数据类型`ProficiencyLevel`,表示单词的掌握程度,并定义了五个枚举值:`ProficiencyLevel1-5`从低到高表示级别。 -*api/words/v1/words.go* -```go -type CreateReq struct { - g.Meta `path:"words" method:"post" sm:"创建" tags:"单词"` - model.WordInput -} +这种自定义类型加上固定枚举值的方式是一种高级的程序设计技巧,可以广泛用在各类状态上,比如订单状态,项目阶段等。新手在编程总喜欢使用`int`一把梭,最后造成代码里全是`1,2,3...`这种数字状态,导致代码的可读性和可维护性较差。 -... -``` +## 编写Logic +--- +同样的,定义`Words`对象,新建`New`函数用作实例化。 -*internal/model/words.go* +*internal/logic/words/words.go* ```go -package model +package words -... - -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最高"` +type Words struct { +} + +func New() *Words { + return &Words{} } ``` -答案是,程序正常运行,但是**这种方式极不可取**。这是因为`Api`层是数据接收层,`Logic`层是逻辑操作层。这种层层透传的方式会带来如下问题: -- 方法参数定义不明确,不明确的定义意味着会增加额外的协作成本,额外的不明确风险; -- 同一数据结构与多数方法形成耦合,数据结构的任一变动将会影响所有相关方法; -- 相关方法无法充分复用。 - -最佳实验是,**宁可多写几行代码,也不要透传数据模型。** - -## 编写Logic ---- *internal/logic/words/words.go* ```go -package words - -import ( - "context" - - "github.com/gogf/gf/v2/errors/gerror" - "star/internal/dao" - "star/internal/model" - "star/internal/model/do" -) +... -type Words struct { -} +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() -func (w *Words) Create(ctx context.Context, in *model.WordInput) error { - if err := w.checkWord(ctx, in); err != nil { + 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{ + _, err = dao.Words.Ctx(ctx).Data(do.Words{ Uid: in.Uid, Word: in.Word, Definition: in.Definition, @@ -95,17 +94,6 @@ func (w *Words) Create(ctx context.Context, in *model.WordInput) error { return err } return nil -} - -func (w *Words) 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 } ``` @@ -114,7 +102,7 @@ func (w *Words) checkWord(ctx context.Context, in *model.WordInput) error { ### account logic 单词表中保存有`uid`字段,我们需要在`logic/users`包中封装一个`GetUid`函数提供`uid`。 -*internal/logic/users/account.go* +*internal/logic/users/users_account.go* ```go func (u *Users) GetUid(ctx context.Context) (uint, error) { user, err := u.Info(ctx) @@ -138,19 +126,19 @@ package words import ( "star/api/words" - usersL "star/internal/logic/users" - wordsL "star/internal/logic/words" + usersLogic "star/internal/logic/users" + wordsLogic "star/internal/logic/words" ) type ControllerV1 struct { - users *usersL.Users - words *wordsL.Words + users *usersLogic.Users + words *wordsLogic.Words } func NewV1() words.IWordsV1 { return &ControllerV1{ - users: &usersL.Users{}, - words: &wordsL.Words{}, + users: usersLogic.New(), + words: wordsLogic.New(), } } ``` diff --git "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" index 526bd22b077..d1c350e9126 100644 --- "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" +++ "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" @@ -12,13 +12,13 @@ description: '利用GoFrame框架中的REST API来更新单词信息,包括单 ```go type UpdateReq struct { g.Meta `path:"words/{id}" method:"put" sm:"更新" tags:"单词"` - Id uint `json:"id" v:"required"` - 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最高"` + Id uint `json:"id" v:"required"` + 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 UpdateRes struct { @@ -27,66 +27,45 @@ type UpdateRes struct { ## 编写Logic --- -在编辑时,我们也需要检查`word`是否唯一。现有的`checkWord`函数不能满足需要,编辑时会连同自己一起判断,所以要改进一下: -- 加上`id`字段,不为`0`时使用`id`和`word`联合判断; -- `id`为`0`时代表新增,仅使用`word`判断。 - *internal/logic/words/words.go* ```go -package words +package words ... -func (w *Words) Create(ctx context.Context, in *model.WordInput) error { - if err := w.checkWord(ctx, 0, in); err != nil { - return err - } - ... -} - -// checkWord 在更新时不检查自身 -func (w *Words) checkWord(ctx context.Context, id uint, in *model.WordInput) error { - db := dao.Words.Ctx(ctx).Where("uid", in.Uid).Where("word", in.Word) - if id > 0 { - db = db.WhereNot("id", id) - } - count, err := db.Count() +type UpdateInput struct { + Uid uint + Word string + Definition string + ExampleSentence string + ChineseTranslation string + Pronunciation string + ProficiencyLevel v1.ProficiencyLevel +} + +func (w *Words) Update(ctx context.Context, id uint, in UpdateInput) error { + var cls = dao.Words.Columns() + + count, err := dao.Words.Ctx(ctx). + Where(cls.Uid, in.Uid). + Where(cls.Word, in.Word). + WhereNot(cls.Id, id). + Count() if err != nil { return err } if count > 0 { return gerror.New("单词已存在") } - return nil -} -``` - -加上更新逻辑: - -*internal/logic/words/words.go* -```go -package words - -... - -func (w *Words) Update(ctx context.Context, id uint, in *model.WordInput) error { - if err := w.checkWord(ctx, id, in); err != nil { - return err - } - - db := dao.Words.Ctx(ctx).Where("uid", in.Uid).Data(do.Words{ + + _, err = dao.Words.Ctx(ctx).Data(do.Words{ Word: in.Word, Definition: in.Definition, ExampleSentence: in.ExampleSentence, ChineseTranslation: in.ChineseTranslation, Pronunciation: in.Pronunciation, ProficiencyLevel: in.ProficiencyLevel, - }).Where("id", id) - if in.Uid > 0 { - db = db.Where("uid", in.Uid) - } - - _, err := db.Update() + }).Where(cls.Id, id).Where(cls.Uid, in.Uid).Update() if err != nil { return err } @@ -96,7 +75,7 @@ func (w *Words) Update(ctx context.Context, id uint, in *model.WordInput) error ... ``` -在`Uid`大于零的情况下,则必须在`ORM`链式中加上`Uid`判断条件,以防止越权,后续的查询,删除动作同样如此。 +必须在`ORM`链式中加上`Uid`判断条件,以防止越权,后续的查询,删除动作同样如此。另外加上`WhereNot`,以忽略自身的单词重复检测。 ## Controller调用Logic --- @@ -107,23 +86,18 @@ package words import ( "context" - "star/internal/model" "star/api/words/v1" + "star/internal/logic/words" ) func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { - uid, err := c.users.GetUid(ctx) - if err != nil { - return nil, err - } - err = c.words.Update(ctx, req.Id, &model.WordInput{ - Uid: uid, - Word: req.Word, - Definition: req.Definition, - ExampleSentence: req.ExampleSentence, - ChineseTranslation: req.ChineseTranslation, - Pronunciation: req.Pronunciation, - ProficiencyLevel: model.ProficiencyLevel(req.ProficiencyLevel), + err = c.words.Update(ctx, req.Id, words.UpdateInput{ + Word: req.Word, + Definition: req.Definition, + ExampleSentence: req.ExampleSentence, + ChineseTranslation: req.ChineseTranslation, + Pronunciation: req.Pronunciation, + ProficiencyLevel: req.ProficiencyLevel, }) return nil, err } diff --git "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" index f8e642e5956..68c6bbb76a7 100644 --- "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" +++ "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" @@ -14,13 +14,11 @@ description: '使用GET方式查询单词的分页列表,包含模糊查询功 | Size | `int` | `json:"size"` | `between:1,100` | 每页数量,默认10 | ## 添加Api --- -首先,我们定义一个结构体保存一个单词的字段。 +定义一个结构体`List`保存一个单词的字段。在`ListRes`中返回一个`List`切片,表示单词列表,`Total`表示所有单词数量,返回给前端方便做分页操作。 -*api/words/v1/words_struct.go* +*api/words/v1/words.go* ```go -package v1 - -import "star/internal/model" +... type List struct { Id uint `json:"id"` @@ -28,13 +26,6 @@ type List struct { Definition string `json:"definition"` ProficiencyLevel model.ProficiencyLevel `json:"proficiencyLevel"` } -``` - -在`ListRes`中返回一个`List`切片,表示单词列表,`Total`表示所有单词数量,返回给前端方便做分页操作。 - -*api/words/v1/words.go* -```go -... type ListReq struct { g.Meta `path:"words" method:"get" sm:"列表" tags:"单词"` @@ -51,62 +42,48 @@ type ListRes struct { ## 编写Logic --- -先定义一个`Query`结构体,用作查询列表入参,严格遵循每一层重新定义数据结构。 - -*internal/model/words.go* +*internal/logic/words/words.go* ```go ... -type WordQuery struct { +type ListInput struct { Uid uint Word string Page int Size int -} -``` - -*internal/logic/words/words.go* -```go -... - -func (w *Words) List(ctx context.Context, query *model.WordQuery) (list []entity.Words, total uint, err error) { - if query == nil { - query = &model.WordQuery{} - } +} + +func (w *Words) List(ctx context.Context, in ListInput) (list []entity.Words, total int, err error) { // 对于查询初始值的处理 - if query.Page == 0 { - query.Page = 1 + if in.Page == 0 { + in.Page = 1 } - if query.Size == 0 { - query.Size = 15 + if in.Size == 0 { + in.Size = 15 } + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) // 组成查询链 - db := dao.Words.Ctx(ctx) - if query.Uid > 0 { - db = db.Where("uid", query.Uid) + if in.Uid > 0 { + orm = orm.Where(cls.Uid, in.Uid) } // 模糊查询 - if len(query.Word) != 0 { - db = db.WhereLike("word", fmt.Sprintf("%%%s%%", query.Word)) + if len(in.Word) != 0 { + orm = orm.WhereLike(cls.Word, "%"+in.Word+"%") } - db = db.Order("created_at desc, id desc").Page(query.Page, query.Size) - - data, totalInt, err := db.AllAndCount(true) - if err != nil { + orm = orm.OrderDesc(cls.CreatedAt).OrderDesc(cls.Id).Page(in.Page, in.Size) + if err = orm.ScanAndCount(&list, &total, true); err != nil { return } - - list = []entity.Words{} - _ = data.Structs(&list) - total = uint(totalInt) - return } ``` -上述代码用到了`db.WhereLike("word", fmt.Sprintf("%%%s%%", query.Word))`,它是模糊查询的意思,最终会生成`word LIKE '%{word}%'` 子句。 +上述代码用到了`orm.WhereLike(cls.Word, "%"+in.Word+"%")`,它是模糊查询的意思,最终会生成`word LIKE '%{word}%'` 子句。 `AllAndCount`用于同时查询数据记录列表及总数量,一般用于分页查询场景中,简化分页查询逻辑。 @@ -120,39 +97,37 @@ import ( "context" "star/api/words/v1" - "star/internal/model" + "star/internal/logic/words" ) func (c *ControllerV1) List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) { uid, err := c.users.GetUid(ctx) if err != nil { - return nil, err - } - - query := &model.WordQuery{ - Uid: uid, - Word: req.Word, - Page: req.Page, - Size: req.Size, + return nil, err } - wordList, total, err := c.words.List(ctx, query) + wordList, total, err := c.words.List(ctx, words.ListInput{ + Uid: uid, + Word: req.Word, + Page: req.Page, + Size: req.Size, + }) if err != nil { - return nil, err + return nil, err } var list []v1.List for _, v := range wordList { - list = append(list, v1.List{ - Id: v.Id, - Word: v.Word, - Definition: v.Definition, - ProficiencyLevel: model.ProficiencyLevel(v.ProficiencyLevel), - }) + list = append(list, v1.List{ + Id: v.Id, + Word: v.Word, + Definition: v.Definition, + ProficiencyLevel: v1.ProficiencyLevel(v.ProficiencyLevel), + }) } return &v1.ListRes{ - List: list, - Total: total, + List: list, + Total: uint(total), }, nil } ``` diff --git "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" index e02a61310fb..01669a56e28 100644 --- "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" +++ "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" @@ -8,7 +8,7 @@ description: '单词详情接口通过GET请求获取不在列表中的单词详 单词详情同样使用`GET`方式,用作获取单词的详细信息,会包含例句,中文翻译等列表中不存在的字段。 ## 添加Api --- -*api/words/v1/words.go*d +*api/words/v1/words.go* ```go ... @@ -18,15 +18,15 @@ type DetailReq struct { } type DetailRes struct { - Id uint `json:"id"` - Word string `json:"word"` - Definition string `json:"definition"` - ExampleSentence string `json:"exampleSentence"` - ChineseTranslation string `json:"chineseTranslation"` - Pronunciation string `json:"pronunciation"` - ProficiencyLevel uint `json:"proficiencyLevel"` - CreatedAt *gtime.Time `json:"createdAt"` - UpdatedAt *gtime.Time `json:"updatedAt"` + Id uint `json:"id"` + Word string `json:"word"` + Definition string `json:"definition"` + ExampleSentence string `json:"exampleSentence"` + ChineseTranslation string `json:"chineseTranslation"` + Pronunciation string `json:"pronunciation"` + ProficiencyLevel ProficiencyLevel `json:"proficiencyLevel"` + CreatedAt *gtime.Time `json:"createdAt"` + UpdatedAt *gtime.Time `json:"updatedAt"` } ``` @@ -39,12 +39,15 @@ type DetailRes struct { ... func (w *Words) Detail(ctx context.Context, uid, id uint) (word *entity.Words, err error) { - word = &entity.Words{} - db := dao.Words.Ctx(ctx).Where("id", id) + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) + orm = orm.Where(cls.Id, id) if uid > 0 { - db = db.Where("uid", uid) + orm = orm.Where(cls.Uid, uid) } - err = db.Scan(word) + err = orm.Scan(&word) return } ``` @@ -64,24 +67,24 @@ import ( func (c *ControllerV1) Detail(ctx context.Context, req *v1.DetailReq) (res *v1.DetailRes, err error) { uid, err := c.users.GetUid(ctx) if err != nil { - return nil, err + return nil, err } word, err := c.words.Detail(ctx, uid, req.Id) if err != nil { - return nil, err + return nil, err } return &v1.DetailRes{ - Id: word.Id, - Word: word.Word, - Definition: word.Definition, - ExampleSentence: word.ExampleSentence, - ChineseTranslation: word.ChineseTranslation, - Pronunciation: word.Pronunciation, - ProficiencyLevel: word.ProficiencyLevel, - CreatedAt: word.CreatedAt, - UpdatedAt: word.UpdatedAt, + Id: word.Id, + Word: word.Word, + Definition: word.Definition, + ExampleSentence: word.ExampleSentence, + ChineseTranslation: word.ChineseTranslation, + Pronunciation: word.Pronunciation, + ProficiencyLevel: v1.ProficiencyLevel(word.ProficiencyLevel), + CreatedAt: word.CreatedAt, + UpdatedAt: word.UpdatedAt, }, nil } ``` diff --git "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" index ebb37520ad6..e87a3ab266e 100644 --- "a/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" +++ "b/docs/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" @@ -25,12 +25,17 @@ type DeleteRes struct { *internal/logic/words/words.go* ```go ... + func (w *Words) Delete(ctx context.Context, uid, id uint) (err error) { - db := dao.Words.Ctx(ctx).Where("id", id) + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) + orm = orm.Where(cls.Id, id) if uid > 0 { - db = db.Where("uid", uid) + orm = orm.Where(cls.Uid, uid) } - _, err = db.Delete() + _, err = orm.Delete() return } ``` diff --git "a/i18n/en/docusaurus-plugin-content-docs/current/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" "b/i18n/en/docusaurus-plugin-content-docs/current/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" index 18df36b047b..67ea541584c 100644 --- "a/i18n/en/docusaurus-plugin-content-docs/current/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" +++ "b/i18n/en/docusaurus-plugin-content-docs/current/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" @@ -263,7 +263,7 @@ Customize a data model in the `model` layer for use as input parameters in the ` ```go package model -type UserInput struct { +type RegisterInput struct { Username string Password string Email string @@ -279,7 +279,7 @@ import ( ... ) -func (u *Users) Register(ctx context.Context, in *model.UserInput) error { +func (u *Users) Register(ctx context.Context, in *model.RegisterInput) error { if err := u.checkUser(ctx, in.Username); err != nil { return err } @@ -298,7 +298,7 @@ func (u *Users) Register(ctx context.Context, in *model.UserInput) error { ... ``` -Change the `Controller` layer to pass in `UserInput`. +Change the `Controller` layer to pass in `RegisterInput`. *internal/controller/users/users_v1_register.go* ```go @@ -310,7 +310,7 @@ import ( ) func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) { - err = c.users.Register(ctx, &model.UserInput{ + err = c.users.Register(ctx, &model.RegisterInput{ Username: req.Username, Password: req.Password, Email: req.Email,