本篇博客是Go-web开发快速入门系类的第一篇,本篇博客将介绍如何使用gin和gorm实现用户注册登录及用数据可存储用户信息。
简单来说,gin用于web开发,获取用户信息,而gorm用于连接数据库,将用户数据迁移到我们的数据库中进行存储
参考资料:
一、准备
在开始我们的项目之前,首先确保你已安装了Go语言并配置好其环境并拥有Go语言编辑器,同时也要有数据库相关的工具,其次,为了方便调试,可安装相应的接口测试工具和数据库可视化工具。
本项目所使用的所有工具如下:
- Visual Studio Code —— 编写并运行Go程序
- MySql —— 建立数据库
- Postman —— 接口测试
- Navicat Premium —— 数据库可视化
在敲代码之前,我们先把项目模块化并引入相关的依赖
1.新建一个项目文件夹
2.使用go mod初始化项目模块
go mod init 项目模块名称
执行完毕后项目下会出现一个go.mod文件,描述项目使用的模块名称及go的版本
3.安装gin和gorm
在终端输入以下代码:
go get -u github.com/gin-gonic/gin
go get -u github.com/jinzhu/gorm
安装完成后,可以发现go.mod文件更新了项目所需的依赖
4.引入mysql
在终端输入以下代码:
go get github.com/go-sql-driver/mysql
5.在mysql中创建一个数据库
在终端输入mysql -u 你的mysql用户名 -p
并输入密码即可进入mysql
输入create database 数据库名
即可创建数据库
二、编写Go程序
新建一个main.go文件
首先构建我们的用户结构体,由于要迁移到数据库,必须引入gorm.Model和相关的描述
type User struct {
gorm.Model
Name string `gorm:"varchar(20);not null"`
Telephone string `gorm:"varchar(20);not null;unique"`
Password string `gorm:"size:255;not null"`
}
然后创建路由引擎
func main() {
//创建一个默认的路由引擎
r := gin.Default()
//在9090端口启动服务
panic(r.Run(":9090"))
}
我们在127.0.0.9090/register和127.0.0.1.9090/login实现用户的注册和登录,将注册和登录的部分下入main函数的两条语句之间
//注册
r.POST("/register", func(ctx *gin.Context) {
//获取参数
name := ctx.PostForm("name")
telephone := ctx.PostForm("telephone")
password := ctx.PostForm("password")
//数据验证
if len(name) == 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户名不能为空",
})
return
}
if len(telephone) != 11 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "手机号必须为11位",
})
return
}
if len(password) < 6 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码不能少于6位",
})
return
}
//判断手机号是否存在
var user User
db.Where("telephone = ?", telephone).First(&user)
if user.ID != 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户已存在",
})
return
}
//创建用户
hasedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 500,
"message": "密码加密错误",
})
return
}
newUser := User{
Name: name,
Telephone: telephone,
Password: string(hasedPassword),
}
db.Create(&newUser)
//返回结果
ctx.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "注册成功",
})
})
//登录
r.POST("/login", func(ctx *gin.Context) {
//获取参数
telephone := ctx.PostForm("telephone")
password := ctx.PostForm("password")
//数据验证
if len(telephone) != 11 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "手机号必须为11位",
})
return
}
if len(password) < 6 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码不能少于6位",
})
return
}
//判断手机号是否存在
var user User
db.Where("telephone = ?", telephone).First(&user)
if user.ID == 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户不存在",
})
return
}
//判断密码是否正确
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码错误",
})
}
//返回结果
ctx.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "登录成功",
})
})
接着构建我们的数据库,编写InitDB()函数
func InitDB() *gorm.DB {
driverName := "mysql"
host := "127.0.0.1"
port := "3306"
database := "你的数据库名"
username := "你的mysql用户名"
password := "你的mysql密码"
charset := "utf8"
args := fmt.Sprintf("%s:%s@(%s:%s)/%s?charset=%s&parseTime=true",
username,
password,
host,
port,
database,
charset)
db, err := gorm.Open(driverName, args)
if err != nil {
panic("failed to connect database, err:" + err.Error())
}
//迁移
db.AutoMigrate(&User{})
return db
}
最后在main函数开头引入InitDB()函数,并延迟关闭数据库,在main函数开头写入:
//获取初始化的数据库
db := InitDB()
//延迟关闭数据库
defer db.Close()
完整代码如下:
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"golang.org/x/crypto/bcrypt"
)
type User struct {
gorm.Model
Name string `gorm:"varchar(20);not null"`
Telephone string `gorm:"varchar(20);not null;unique"`
Password string `gorm:"size:255;not null"`
}
func main() {
//获取初始化的数据库
db := InitDB()
//延迟关闭数据库
defer db.Close()
//创建一个默认的路由引擎
r := gin.Default()
//注册
r.POST("/register", func(ctx *gin.Context) {
//获取参数
name := ctx.PostForm("name")
telephone := ctx.PostForm("telephone")
password := ctx.PostForm("password")
//数据验证
if len(name) == 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户名不能为空",
})
return
}
if len(telephone) != 11 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "手机号必须为11位",
})
return
}
if len(password) < 6 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码不能少于6位",
})
return
}
//判断手机号是否存在
var user User
db.Where("telephone = ?", telephone).First(&user)
if user.ID != 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户已存在",
})
return
}
//创建用户
hasedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 500,
"message": "密码加密错误",
})
return
}
newUser := User{
Name: name,
Telephone: telephone,
Password: string(hasedPassword),
}
db.Create(&newUser)
//返回结果
ctx.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "注册成功",
})
})
//登录
r.POST("/login", func(ctx *gin.Context) {
//获取参数
telephone := ctx.PostForm("telephone")
password := ctx.PostForm("password")
//数据验证
if len(telephone) != 11 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "手机号必须为11位",
})
return
}
if len(password) < 6 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码不能少于6位",
})
return
}
//判断手机号是否存在
var user User
db.Where("telephone = ?", telephone).First(&user)
if user.ID == 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户不存在",
})
return
}
//判断密码是否正确
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码错误",
})
}
//返回结果
ctx.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "登录成功",
})
})
//在9090端口启动服务
panic(r.Run(":9090"))
}
func InitDB() *gorm.DB {
driverName := "mysql"
host := "127.0.0.1"
port := "3306"
database := "你的数据库名"
username := "你的mysql用户名"
password := "你的mysql密码"
charset := "utf8"
args := fmt.Sprintf("%s:%s@(%s:%s)/%s?charset=%s&parseTime=true",
username,
password,
host,
port,
database,
charset)
db, err := gorm.Open(driverName, args)
if err != nil {
panic("failed to connect database, err:" + err.Error())
}
//迁移
db.AutoMigrate(&User{})
return db
}
三、测试
用postman进行接口测试
点击"+"新建用于测试的request
输入相应的url发送请求,得到如下结果:
用navicat连接数据库可视化显示
填写相关信息连接数据库,用户名为MySql的用户名,密码为MySql的密码
连接后可看到数据库的内容如下:
在vs code终端有如下显示
四、重构与代码优化
为了使代码更好的复用,我们对代码进行重构,重构的结构如下:
model/user.go,用于定义我们的用户
package model
import "github.com/jinzhu/gorm"
type User struct {
gorm.Model
Name string `gorm:"varchar(20);not null"`
Telephone string `gorm:"varchar(20);not null;unique"`
Password string `gorm:"size:255;not null"`
}
common/database 用于数据库的构建与连接
package common
import (
"demo/model"
"fmt"
"github.com/jinzhu/gorm"
)
var DB *gorm.DB
func InitDB() *gorm.DB {
driverName := "mysql"
host := "127.0.0.1"
port := "3306"
database := "你的数据库名称"
username := "你的mysql用户名"
password := "你的mysql密码"
charset := "utf8"
args := fmt.Sprintf("%s:%s@(%s:%s)/%s?charset=%s&parseTime=true",
username,
password,
host,
port,
database,
charset)
db, err := gorm.Open(driverName, args)
if err != nil {
panic("failed to connect database, err:" + err.Error())
}
//迁移
db.AutoMigrate(&model.User{})
DB = db
return db
}
func GetDB() *gorm.DB{
return DB
}
controller/userController 用于编写用户相关的函数,如注册、登录
package controller
import (
"demo/common"
"demo/model"
"net/http"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
func Register(ctx *gin.Context) {
db := common.GetDB()
//获取参数
//此处使用Bind()函数,可以处理不同格式的前端数据
var requestUser model.User
ctx.Bind(&requestUser)
name := requestUser.Name
telephone := requestUser.Telephone
password := requestUser.Password
//数据验证
if len(name) == 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户名不能为空",
})
return
}
if len(telephone) != 11 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "手机号必须为11位",
})
return
}
if len(password) < 6 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码不能少于6位",
})
return
}
//判断手机号是否存在
var user model.User
db.Where("telephone = ?", telephone).First(&user)
if user.ID != 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户已存在",
})
return
}
//创建用户
hasedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 500,
"message": "密码加密错误",
})
return
}
newUser :=model.User{
Name: name,
Telephone: telephone,
Password: string(hasedPassword),
}
db.Create(&newUser)
//返回结果
ctx.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "注册成功",
})
}
func Login(ctx *gin.Context) {
db := common.GetDB()
//获取参数
//此处使用Bind()函数,可以处理不同格式的前端数据
var requestUser model.User
ctx.Bind(&requestUser)
telephone := requestUser.Telephone
password := requestUser.Password
//数据验证
if len(telephone) != 11 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "手机号必须为11位",
})
return
}
if len(password) < 6 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码不能少于6位",
})
return
}
//判断手机号是否存在
var user model.User
db.Where("telephone = ?", telephone).First(&user)
if user.ID == 0 {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "用户不存在",
})
return
}
//判断密码是否正确
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
ctx.JSON(http.StatusUnprocessableEntity, gin.H{
"code": 422,
"message": "密码错误",
})
}
//返回结果
ctx.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "登录成功",
})
}
routes.go 用于存放我们的路由
package main
import (
"demo/controller"
"github.com/gin-gonic/gin"
)
func CollectRoutes(r *gin.Engine) *gin.Engine {
//注册
r.POST("/register", controller.Register)
//登录
r.POST("/login", controller.Login)
return r
}
main.go 用于数据库初始化、路由与服务的启动
package main
import (
"demo/common"
"github.com/gin-gonic/gin"
)
func main() {
//获取初始化的数据库
db := common.InitDB()
//延迟关闭数据库
defer db.Close()
//创建一个默认的路由引擎
r := gin.Default()
//启动路由
CollectRoutes(r)
//在9090端口启动服务
panic(r.Run(":9090"))
}
注意在重构之后,运行时需要同时运行main.go和routes.go
go run main.go routes.go
或同时编译main.go和routes.go再运行main.exe
go build main.go routes.go
main.exe
测试一下,重构后的代码正常运行~
下一篇博客将介绍如何使用jwt配合中间件进行用户认证~
Go-web开发快速入门——二、使用jwt配合中间件进行用户认证