Go 搭建用户模块

2021年02月27日 252次浏览 Golang

服务端开发我们会分为model/service/controller,这样让项目结构清晰明了。这三个模块作用分别如下:

  • model:用来定义结构体,结构体和数据库表结构一一对应
  • service:用来编写业务逻辑,读写数据库
  • controller:对外提供数据接口

首先我们来看server/main.go中的代码,他是我们整个程序的入口,在main.go中我们将初始化数据库,配置iris路由,他的完整代码如下:

package main

import (
    "github.com/jinzhu/gorm"
    "github.com/kataras/iris"
    "github.com/kataras/iris/mvc"

    "github.com/iris-contrib/middleware/cors"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

var db *gorm.DB

func main() {
    initDB()

    app := iris.New()

    // 跨域配置
    app.Use(cors.New(cors.Options{
        AllowedOrigins:   []string{"*"}, // allows everything, use that to change the hosts.
        AllowCredentials: true,
        MaxAge:           600,
        AllowedMethods:   []string{iris.MethodGet, iris.MethodPost, iris.MethodOptions, iris.MethodHead, iris.MethodDelete, iris.MethodPut},
        AllowedHeaders:   []string{"*"},
    }))
    app.AllowMethods(iris.MethodOptions)

    mvc.Configure(app.Party("/api/user"), func(mvcApp *mvc.Application) {
        mvcApp.Handle(new(UserController))
    })

    _ = app.Run(iris.Addr(":8081"), iris.WithoutServerError(iris.ErrServerClosed))
}

// 初始化数据库链接
func initDB() {
    var err error
    db, err = gorm.Open("mysql", "root@tcp(localhost:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local")
    if err != nil {
        panic(err)
    }
    db.LogMode(true)
}

结构体

新建/server/user_model.go文件,在该文件中定义我们用户结构体。用户相关的有两个结构体,分别如下:

  • 用户(user):用于保存用户资料
  • 用户授权令牌(user_token):用户的登录标识,通过令牌能获取到当前登录用户/server/user_model.go完整代码如下:
package main

import (
    "time"
)

const (
    UserRoleAdmin  = "管理员"
    UserRoleNormal = "普通用户"
)

// 用户表
type User struct {
    Id         int64     `gorm:"PRIMARY_KEY;AUTO_INCREMENT" json:"id"`    // 编号
    Username   string    `gorm:"size:32;not null;unique" json:"username"` // 用户名,添加唯一标识
    Password   string    `gorm:"size:128;not null" json:"password"`       // 密码
    Nickname   string    `gorm:"size:32;not null" json:"nickname"`        // 昵称
    Role       string    `gorm:"size:32;not null" json:"role"`            // 用户角色,管理员、普通用户
    CreateTime time.Time `gorm:"not null" json:"createTime"`              // 创建时间
}

// 用户授权令牌,用户的登录标识
type UserToken struct {
    Id         int64     `gorm:"PRIMARY_KEY;AUTO_INCREMENT" json:"id"`              // 编号
    UserId     int64     `gorm:"not null" json:"userId"`                            // 用户编号
    Token      string    `gorm:"size:32;unique;not null" json:"token" form:"token"` // 令牌
    ExpiredAt  int64     `gorm:"not null" json:"expiredAt" form:"expiredAt"`        // 过期时间戳
    CreateTime time.Time `gorm:"not null" json:"createTime"`                        // 创建时间
}

用户相关的结构体定义完成之后,需要将他们放入gorm的AutoMigrate,这样在启动服务的时候gorm会自动创建用户相关的表,打开server/main.go,修改gorm配置如下:

err = db.AutoMigrate(&User{}, &UserToken{}).Error
if err != nil {
  panic(err)
}

业务服务(user_service)

UserService是我们的业务代码,我们将在这里完成用户相关的核心逻辑、并操作数据库完成数据的读写操作。主要实现功能如下:
-用户注册
-用户登录
-获取当前登录用户

接下来我们新增文件server/user_service.go,该文件完整代码如下

package main

import (
    "time"

    "github.com/kataras/iris/context"
    "github.com/mlogclub/simple"
)

var UserService = &userService{}

type userService struct {
}

// 创建用户
func (userService) Create(username, password, nickname, role string) error {
    return db.Create(&User{
        Username:   username,
        Password:   password,
        Nickname:   nickname,
        CreateTime: time.Now(),
    }).Error
}

// 根据id查询用户
func (userService) Get(id int64) *User {
    ret := &User{}
    if err := db.First(ret, "id = ?", id).Error; err != nil {
        return nil
    }
    return ret
}

// 根据用户名查找
func (userService) GetByUsername(username string) *User {
    ret := &User{}
    if err := db.Take(ret, "username = ?", username).Error; err != nil {
        return nil
    }
    return ret
}

// 用户登录
func (userService) Login(username, password string) (*User, string) {
    // 查找用户
    user := UserService.GetByUsername(username)
    if user == nil {
        return nil, ""
    }

    // 验证密码
    passwordValidated := simple.ValidatePassword(user.Password, password)
    if !passwordValidated {
        return nil, ""
    }

    // 生成授权令牌
    token := simple.Uuid()
    expiredAt := time.Now().Add(time.Hour * 24 * 7) // 7天后过期
    db.Create(&UserToken{
        UserId:     user.Id,
        Token:      token,
        ExpiredAt:  simple.Timestamp(expiredAt),
        CreateTime: time.Now(),
    })
    return user, token
}

// 获取当前登录用户
func (u userService) GetCurrent(ctx context.Context) *User {
    token := u.GetUserToken(ctx)
    if len(token) == 0 {
        return nil
    }
    userToken := &UserToken{}
    if err := db.Take(userToken, "token = ?", token).Error; err != nil {
        return nil
    }
    return u.Get(userToken.UserId)
}

// 从请求体中获取UserToken
func (userService) GetUserToken(ctx context.Context) string {
    userToken := ctx.FormValue("userToken")
    if len(userToken) > 0 {
        return userToken
    }
    return ctx.GetHeader("X-User-Token")
}

控制器(user_controller)

controller中我们对外提供数据接口,我们所有的接口都返回JsonResult对象,该对象最终会被序列化成JSON返回,下面通过 JsonResult 的代码+注释来了解下JsonResult中每个字段的含义。

type JsonResult struct {
    ErrorCode int         `json:"errorCode"`  // 错误码,当接口发生错误的时候可以指定错误码
    Message   string      `json:"message"`    // 错误消息,当接口发生错误的时候返回的错误消息
    Data      interface{} `json:"data"`       // 业务数据
    Success   bool        `json:"success"`    // 接口调用是否成功
}

接下来我们定义UserController,创建文件server/user_controller.go代码如下:

package main

import (
    "github.com/kataras/iris/context"
    "github.com/mlogclub/simple"
)

// controller
type UserController struct {
    Ctx context.Context
}

// 用户注册
func (this *UserController) PostAdd() *simple.JsonResult {
    var (
        username   = this.Ctx.FormValue("username")
        password   = this.Ctx.FormValue("password")
        rePassword = this.Ctx.FormValue("rePassword")
        nickname   = this.Ctx.FormValue("nickname")
    )

    // 数据校验
    if len(username) == 0 || len(password) == 0 || len(nickname) == 0 {
        return simple.JsonErrorMsg("请认真填写用户名、密码、昵称")
    }
    if password != rePassword {
        return simple.JsonErrorMsg("两次填写密码不同,请检查后重新填写")
    }

    // 密码加密
    password = simple.EncodePassword(password)

    // 判断用户名是否存在
    tmp := UserService.GetByUsername(username)
    if tmp != nil {
        return simple.JsonErrorMsg("用户名【" + username + "】已经存在")
    }

    // 执行注册操作
    err := UserService.Create(username, password, nickname, UserRoleNormal)
    if err != nil {
        return simple.JsonErrorMsg(err.Error())
    }
    return simple.JsonSuccess()
}

// 用户登录
func (this *UserController) PostLogin() *simple.JsonResult {
    var (
        username = this.Ctx.FormValue("username")
        password = this.Ctx.FormValue("password")
    )

    user, token := UserService.Login(username, password)
    if user == nil {
        return simple.JsonErrorMsg("用户名密码错误")
    }
    // 登录成功返回用户信息和授权令牌
    return simple.NewRspBuilder(user).Put("token", token).JsonResult()
}

// 获取当前登录用户
func (this *UserController) GetCurrent() *simple.JsonResult {
    user := UserService.GetCurrent(this.Ctx)
    if user != nil {
        return simple.JsonData(user)
    }
    return simple.JsonSuccess()
}

在UserCotroller中我们定义了三个接口,分别为:用户注册、用户登录、获取当前登录用户。接下来我们将UserController配置到iris路由中,打开server/main.go新增如下代码:

mvc.Configure(app.Party("/api/user"), func(mvcApp *mvc.Application) {
    mvcApp.Handle(new(UserController))
})

如果不明白如何使用iris的同学,请认真温习前面实验中关于iris使用方法的讲解

至此我们就完成了用户模块的服务端开发。然后在server目录下执行以下命令来启动接口服务

Axios 插件