【ginny 系列】 基于Go的web后台开发,实现一个简单的用户系统

实现一个简单的用户系统

代码写到现在,你会发现我们已经拥有了一个简短(只有60行!),但是却能实现一个基本的后台接口,且能和数据库有交互的程序!这实在是很让人激动!那么接下来保持这股热情,让我们来实现一个基本的用户系统吧!

在此之前,我们要整理一下我们的代码(尽管它只有60行):

3.1 开始整理我们的代码结构

我们的main.go里现在包括了几个部分:声明路由,建立数据库连接,还有一个处理函数,但是这些东西都放在一起就会觉得有点乱。所以我们来建立一个良好的目录吧!

重建后的目录结构如下:

├── handler
│   ├── handler.go
│   ├── login
│   └── register
│       └── register.go
├── model
│   ├── init.go
│   └── user.go
├── router
│   └── router.go
└── main.go

然后我们来解释一下各个部分的作用:

  • handler
    • handler其实就是之前在main.go中的对应路由的处理函数,虽然我们当时使用了匿名函数的方式,但是真正的业务逻辑代码其实就是这个处理函数,因此要抽离出来,这样便于维护和trouble shooting
  • model
    • model是模型,主要对应数据库的获取,连接,各个表结构的映射和增删查改的方法。
  • router
    • router是路由表,包括以后一些中间件存放的目录
  • main.go
    • 应用程序的入口,包含最基本的逻辑

那么我们开始着手重构吧!

首先,我们要把数据库的连接和模型都放入model:

// ./model/init.go

package model

import (
    "fmt"
    "log"
    "database/sql"
    
    _ "github.com/go-sql-driver/mysql"
)

type Database struct {  // 对数据库进行封装
    Self *sql.DB
}

var DB *Database

func getDatabase(username, password, host, port, dbname string) (*sql.DB, error) {
    address := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, host, port, dbname)
    fmt.Println("Start to connect:", address)
    db, err := sql.Open("mysql", address)
    if err != nil {
        return nil, err
    }
    err = db.Ping()
    if err != nil {
        return nil, err
    }
    return db, nil
}

func (db *Database) Init() {
    newdb, err := getDatabase("root", "ginny", "127.0.0.1", "3306", "go_blog")
    if err != nil {
        log.Fatal(err)  // 如果有错误,那么打印,而不是直接退出
    }
    DB = &Database{
        Self: newdb,
    }
}

func (db *Database) Close() {
    DB.Self.Close()
}

这样我们就可以通过调用model.DB.Self来使用*sql.DB,所以接下来我们要手动封装一些对于数据库增删查改的方法。

// ./model/user.go
package model

// 检查数据库中是否有相同用户名的用户
func CheckUserByUsername(username string) bool {
    userID := 0
    err := DB.Self.QueryRow("SELECT  FROM users WHERE username = ?", username).Scan(&userID)
    return err == nil
}

// 检查用户名密码是否匹配
func CheckPasswordValidate(username, password string) bool {
    var record string
    err := DB.Self.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&record)
    return password == record && err == nil
}

func CreateUser(username, password string) {
    DB.Self.Query("INSERT INTO users (username, password) VALUES (?, ?)", username, password)
}

在将数据库操作抽离出来之后,我们可以抽离路由中的处理函数。为什么要先抽离数据库操作呢?因为处理函数的内部实现是依赖数据库操作的。

// ./handler/register/register.go

package register

import (
    "github.com/ShiinaOrez/ginny/model"
    "github.com/gin-gonic/gin"
)

type RegisterPayload struct {
    Username  string  `json:"username"`
    Password  string  `json:"password"`
}

func Register(c *gin.Context) {
    var data RegisterPayload
    if err := c.BindJSON(&data); err != nil {
        c.JSON(400, gin.H{
            "message": "Bad Request!",
        })
        return
    }
    if model.CheckUserByUsername(data.Username) {
        c.JSON(401, gin.H{
            "message": "User Already Existed!",
        })
        return
    }
    model.CreateUser(data.Username, data.Password)
    c.JSON(200, gin.H{
        "message": "Register Successful!",
    })
    return
}

到这里我们已经把独立的业务逻辑抽离出来了,可以明显的觉得这样去撰写接口的逻辑是更加简洁和便于维护的。

接下来是router。

// ./router/router.go

package router

import (
    "github.com/gin-gonic/gin"
    "github.com/ShiinaOrez/ginny/handler/register"
)

var Router *gin.Engine

func InitRouter() {
    Router = gin.Default()
    Router.POST("/register", register.Register)
    return
}

我们定义了Router这个全局变量,并且定义了初始化函数。到现在我们已经把model, handler, router都抽离出来了,那么我们的main.go应该如何撰写呢?

// ./main.go
package main

import (
    "fmt"

    "github.com/ShiinaOrez/ginny/router"
    "github.com/ShiinaOrez/ginny/model"
)

func main() {
    model.DB.Init()        // 初始化数据库
    defer model.DB.Close() // 记得关闭数据库

    router.InitRouter()    // 初始化路由
    router.Router.Run()    // 运行
    fmt.Println("Running... Successful!")
}

然后使用以下命令来运行你的程序:

go build -o main && ./main
提示:可以在本教程的附属仓库 https://github.com/ShiinaOrez/ginny.git 中通过标签``v0.2.0``来查看这个示例。

然后让我们来实现用户系统的其他接口,但是用户系统应该有哪些接口呢?

3.2 Let‘s 实现一个用户系统吧!

首先,除了注册接口,我们还需要一个登录接口吧,不妨叫做login,实现的功能应该是:提供用户名和密码,如果匹配成功则返回200,否则返回401(这里我们只实现最简单的接口,它当然不够健壮,但是我们以后会让他变得健壮起来)

我们会用到model中的CheckPasswordValidate()方法:

// ./handler/login/login.go

package login

import (
    "github.com/ShiinaOrez/ginny/model"
    "github.com/gin-gonic/gin"
)

type LoginPayload struct {  // 用于接收payload的结构体
    Username  string  `json:"username"`
    Password  string  `json:"password"`
}

func Login(c *gin.Context) { // 用于登录路由的处理函数
    var data LoginPayload    // 声明payload变量,因为BindJSON方法需要接收一个指针进行操作
    if err := c.BindJSON(&data); err != nil {
        c.JSON(400, gin.H{
            "message": "Bad Request!", // 接收过程中的错误视为Bad Request
        })
        return
    }
    if !model.CheckPasswordValidate(data.Username, data.Password) { // 检查失败的情况
        c.JSON(401, gin.H{
            "message": "Authentication Failed.",
        })
        return
    } else {
        c.JSON(200, gin.H{
            "message": "Authentiaction Success.",
        })
    }
    return
}

然后我们只需要在./router/router.go中加入这样一行代码就可以使用这个函数了:

    Router.POST("/login", login.Login)

接下来我们启动程序,当我们POST请求localhost:8080/login接口时,就可以获得我们预期中的效果了。

一个简单的用户系统大致需要以下接口:

  • 注册/登录接口
  • 更改密码/忘记密码接口

然后读者可以去试着自己实现这两个接口,当然也可以直接看这个教程的示例仓库

提示:可以在本教程的附属仓库 https://github.com/ShiinaOrez/ginny.git 中通过标签``v0.2.1``来查看这个示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夕Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值