Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve logic in the starbook #135

Merged
merged 2 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions docs/course/starbook/第三章-会话管理/3.2.登录.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -56,42 +58,42 @@ import (
"star/utility"
)

type UserClaims struct {
type jwtClaims struct {
Id uint
Username string
jwt.RegisteredClaims
}

func Login(ctx context.Context, username, password string) (tokenString string, err error) {
func (u *Users) Login(ctx context.Context, username, password string) (tokenString string, err error) {
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
userClaims := &UserClaims{
uc := &jwtClaims{
Id: user.Id,
Username: user.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(6 * time.Hour)),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, userClaims)
return token.SignedString(utility.JwtKey)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, uc)
return token.SignedString(consts.JwtKey)
}
```

从上面的代码可以看到,我们需要声明一个结构体`UserClaims`保存签名信息,这里保存了`Id`和`Username`并设置了`Token`有效期为 6 个小时。最后的声明对象还需要调用`SignedString`方法传入`JwtKey`生成签名。
从上面的代码可以看到,我们需要声明一个结构体`userClaims`保存签名信息,这里保存了`Id`和`Username`并设置了`Token`有效期为 6 个小时。最后的声明对象还需要调用`SignedString`方法传入`JwtKey`生成签名。

## Controller调用Logic
---
Expand All @@ -107,7 +109,7 @@ import (
)

func (c *ControllerV1) Login(ctx context.Context, req *v1.LoginReq) (res *v1.LoginRes, err error) {
token, err := users.Login(ctx, req.Username, req.Password)
token, err := c.users.Login(ctx, req.Username, req.Password)
if err != nil {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type InfoRes struct {

## 编写Logic
---
*internal/logic/users/account.go*
*internal/logic/users/users_account.go*
```go
package users

Expand All @@ -92,15 +92,13 @@ import (

...

func Info(ctx context.Context) (user *entity.Users, err error) {
user = new(entity.Users)
func (u *Users) Info(ctx context.Context) (user *entity.Users, err error) {
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
Expand All @@ -113,29 +111,51 @@ func Info(ctx context.Context) (user *entity.Users, err error) {

## Controller调用Logic
---
同样将`logic`注册到控制器中。

*internal/controller/account/account_new.go*
```go
...

package account

import (
"star/api/account"
"star/internal/logic/users"
)

type ControllerV1 struct {
users *users.Users
}

func NewV1() account.IAccountV1 {
return &ControllerV1{
users: users.New(),
}
}
```

*internal/controller/account/account_v1_info.go*
```go
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 := users.Info(ctx)
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
}
```

Expand Down
47 changes: 44 additions & 3 deletions docs/course/starbook/第二章-用户注册/2.3.注册接口.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,23 @@ done!
---
`Logic` 是业务逻辑层,存放在`internal/logic`下,供`Controller`调用从而实现具体的业务逻辑。

*internal/logic/users/register.go*
定义一个`Users`对象,并新建一个`New`函数用作实例化它:

*internal/logic/users/users.go*
```go
package users

type Users struct {
}

func New() *Users {
return &Users{}
}
```

编写注册方法:

*internal/logic/users/users_register.go*
```go
package users

Expand All @@ -58,7 +74,7 @@ import (
"star/internal/model/do"
)

func Register(ctx context.Context, username, password, email string) error {
func (u *Users) Register(ctx context.Context, username, password, email string) error {
_, err := dao.Users.Ctx(ctx).Data(do.Users{
Username: username,
Password: password,
Expand All @@ -77,6 +93,31 @@ func Register(ctx context.Context, username, password, email string) error {
---
`Controller` 层负责接收 `Req` 请求对象后调用一个或多个`Logic`完成业务逻辑,一些简单的逻辑也可以直接放在`Controller`中处理。处理完成后的结果封装在约定的 `Res` 数据结构中返回。这里的`Res`数据结构为空,返回`nil`即可。

将`Users`对象封装到控制器中,方便后续调用。

*internal/controller/users/users_new.go*
```go
...

package users

import (
"star/api/users"
userLogic "star/internal/logic/users"
)

type ControllerV1 struct {
users *userLogic.Users
}

func NewV1() users.IUsersV1 {
return &ControllerV1{
users: userLogic.New(),
}
}
```


*internal/controller/users/users_v1_register.go*
```go
package users
Expand All @@ -90,7 +131,7 @@ import (
)

func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) {
err = users.Register(ctx, req.Username, req.Password, req.Email)
err = c.users.Register(ctx, req.Username, req.Password, req.Email)
return nil, err
}
```
Expand Down
62 changes: 35 additions & 27 deletions docs/course/starbook/第二章-用户注册/2.4.业务优化.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,14 @@ $ 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

...

func Register(ctx context.Context, username, password, email string) error {
if err := checkUser(ctx, username); err != nil {
func (u *Users) Register(ctx context.Context, username, password, email string) error {
if err := u.checkUser(ctx, username); err != nil {
return err
}

Expand All @@ -169,7 +169,7 @@ func Register(ctx context.Context, username, password, email string) error {
return nil
}

func checkUser(ctx context.Context, username string) error {
func (u *Users) checkUser(ctx context.Context, username string) error {
count, err := dao.Users.Ctx(ctx).Where("username", username).Count()
if err != nil {
return err
Expand Down Expand Up @@ -201,33 +201,35 @@ 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

...

func Register(ctx context.Context, username, password, email string) error {
func (u *Users) Register(ctx context.Context, username, password, email string) error {
...

_, err := dao.Users.Ctx(ctx).Data(do.Users{
Username: username,
Password: encryptPassword(password),
Password: u.encryptPassword(password),
Email: email,
}).Insert()
if err != nil {
Expand Down Expand Up @@ -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

Expand All @@ -279,8 +285,8 @@ import (
...
)

func Register(ctx context.Context, in *model.UserInput) error {
if err := CheckUser(ctx, in.Username); err != nil {
func (u *Users) Register(ctx context.Context, in RegisterInput) error {
if err := u.checkUser(ctx, in.Username); err != nil {
return err
}

Expand All @@ -298,22 +304,24 @@ func 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 = 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
}
Expand Down
Loading
Loading