最近学习了gin基础,实现了一个简单的基于gin+postgresql+redis技术的登陆注册接口,今天是周五,就总结一下在项目中学到的东西吧
前言
项目的需求很简单,实现一个登陆注册的接口,当用户第一次登陆时,系统会去redis中验证用户信息,如果没有则会去postgresql中验证,同时将信息加入到redis中,同理,当用户第一次进行注册时,用户的数据在保存到postgresql的同时还要加入到redis数据库缓存中
项目结构
项目的结构遵循web工程结构的规范,目录如下:
实现过程
整个项目结构很简单,完全可以按照mvc的思想去开发,在这里还要自己定义一些项目的中间件
数据库连接池的设计
项目的表很简单,在postgresql中导入即可
create table test_user(
user_id serial primary key,
user_name varchar(50) not null,
password varchar(50) not null,
unique(user_name)
);
项目的数据库连接池配置文件都是来自于toml文件,先定义好,后面要解析,文件放在docs目录下,命名为valiben.toml
listen = ":8080"
user_api = "http://127.0.0.1:8080"
[dbservers]
[dbservers.testdb]
host = "127.0.0.1"
port = 5432
dbname = "mydb"
user = "postgres"
[redisservers]
[redisservers.usercache]
addr = "127.0.0.1:6379"
db = 0
password = ""
外部文件都已经配置好了,现在去写数据库连接池,在pkg的文件下的utils下新建一个config文件夹,新建一个db.go文件
package config
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/jmoiron/sqlx"
"github.com/garyburd/redigo/redis"
"time"
)
/**
初始化数据库连接池
*/
/**
初始化pgsql数据库的配置
*/
//注意结构体的字段名必须大写,不然外部的包会访问不到
type DBServer struct {
Host string `toml:"host"`
Port int `toml:"port"`
Dbname string `toml:"dbname"`
User string `toml:"user"`
Password string `toml:"password"`
}
//初始化连接数据库的字符串
func (m DBServer) ConnectString() string{
return fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=disable",
m.Host,m.Port,m.User,m.Dbname)
}
//初始化pgsql的连接
func (m DBServer) NewPostgresDB(idleConnection int) (db *sqlx.DB,err error){
db,err = sqlx.Open("postgres",m.ConnectString())
if err!=nil {
return
}
//设置最大空闲连接数
db.SetMaxIdleConns(idleConnection)
return
}
//初始化gorm的连接
func (m DBServer) NewGormDB(openConnection int) (db *gorm.DB,err error){
db,err=gorm.Open("postgres",m.ConnectString())
if err != nil {
return
}
//设置最大连接数
db.DB().SetMaxOpenConns(openConnection)
return db,err
}
//初始化redis数据库的配置
type RedisServer struct {
Addr string `toml:"addr"`
Password string `toml:"password"`
DB int `toml:"db"`
}
//初始化redis连接池
func (c RedisServer) NewRedisPool(maxIdle int) *redis.Pool {
return &redis.Pool{
MaxIdle: maxIdle,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", c.Addr, redis.DialDatabase(c.DB), redis.DialPassword(c.Password))
if err != nil {
return nil, err
}
return c, err
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}
模型的设计
接下来去定义模型,在pkg文件下新建一个model文件夹,新建user.go文件定义user的dao层操作
package models
import (
"github.com/jinzhu/gorm"
"github.com/jmoiron/sqlx"
)
type User struct {
UserID int `db:"user_id"`
UserName string `db:"user_name"`
Password string `db:"password"`
}
//获取所有用户
func GetUsers(testDB *gorm.DB) (users []User,err error){
var u []User
if err = testDB.Table("test_user").Find(&u).Error;err!=nil {
return nil,err
}
return u,nil
}
//从查找单个用户
func GetUser(testDB *gorm.DB,userName string) (user *User,exists bool,err error){
var tempUser User
if err = testDB.Table("test_user").Where("user_name = ?",userName).Find(&tempUser).Error;err!=nil{
if err != gorm.ErrRecordNotFound{
//如果并不是找不到用户的操作
return nil,false,err
}
//如果是找不到的问题,直接返回
return nil,false,nil
}
return &tempUser,true,err
}
//增加用户
func AddUser(testdb *sqlx.DB,user *User)(err error){
psql:= `insert into test_user(user_name,password) values($1,$2)`
_,err = testdb.Exec(psql,user.UserName,user.Password)
if err != nil {
return err
}
return nil
}
//删除用户
func DelUser(testDB *gorm.DB,username string) (err error) {
if err = testDB.Table("test_user").Where("user_name = ?",username).Delete(&username).Error;err!=nil{
return err
}
return nil
}
//更改用户密码
func UpdUser(testDB *sqlx.DB,user User) (err error){
psql:= `update test_user set password = $1 where user_name = $2`
testDB.Prepare(psql)
_,err = testDB.Exec(psql,user.Password,user.UserName)
if err != nil {
return err
}
return nil
}
缓存层的设计
缓存是用来存储用户的临时数据,使用redis的hash进行存储,在pkg文件夹下新建一个cache文件夹,在下面新建一个userCache.go文件
package cache
import "github.com/garyburd/redigo/redis"
/**
用户缓存配置
*/
const(
//hash storage
cacheNameUser = "user"
)
//获取缓存中用户
func GetUser(testcache *redis.Pool,userName string) (userBytes string,exists bool,err error){
conn:=testcache.Get()
defer conn.Close() //延迟关闭数据库连接
userBytes,err = redis.String(conn.Do("hget",cacheNameUser,userName))
if err != nil {
if err != redis.ErrNil {
//如果并不是redis服务器的错误
return userBytes,false,err
}
//如果是服务器的错误
return userBytes,false ,nil
}
//返回正确的结果集
return userBytes,true,nil
}
//在缓存中存入用户信息
func SetUser(testcache *redis.Pool,userName,userInfo string) (err error){
conn:=testcache.Get()
defer conn.Close()
_,err = conn.Do("hset",cacheNameUser,userName,userInfo)
return
}
中间件的设计
接下来到了关键的一步,中间件的设计
服务器状态码的封装
// pkg/middleware/responseCode.go
package middleware
var (
successCode int = 200 //成功
SeverErrorCode int = 500 //服务器请求错误
NotFoundErrCode int = 404 //找不到此数据
ValidateErrCode int = 400 //检验错误
)
// pkg/utils/middleware/responseStatus.go
package middlewares
import (
"github.com/gin-gonic/gin"
"encoding/json"
)
/**
封装服务器返回状态吗
*/
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
//错误状态处理
func ResposeError(c *gin.Context,code int,err error,msg string){
resp := &Response{Code:code,Msg:msg,Data:nil}
c.JSON(code,resp)
response,_:=json.Marshal(resp)
c.Set("response",string(response))
c.AbortWithError(code,err)
}
//正确状态处理
func ResponseSuccess(c *gin.Context,msg string,data interface{}) {
resp := &Response{Code: 200, Msg: msg, Data: data}
c.JSON(200, resp)
response, _ := json.Marshal(resp)
c.Set("response", string(response))
}
文件配置解析封装
package config
// pkg/config/config.go
import (
"fmt"
"github.com/BurntSushi/toml"
cfg "github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/utils/config"
)
//配置类
type MyApiConfig struct {
Listen string `toml:"listen"`
DBServers map[string]cfg.DBServer `toml:"dbservers"`
RedisServers map[string]cfg.RedisServer `toml:"redisservers"`
UserAPI string `toml:"user_api"`
}
//解析toml配置
func UnmarshalConfig(tomlfile string) (*MyApiConfig,error) {
c:=&MyApiConfig{}
if _,err:=toml.DecodeFile(tomlfile,c);err!=nil {
//如果解析错误则返回错误
return c,err
}
return c,nil
}
//获取pgsql数据库的配置
func (c MyApiConfig) DBServerConf(key string)(cfg.DBServer,bool){
s,ok := c.DBServers[key]
return s,ok
}
//获取redis数据库的配置
func (c MyApiConfig) RedisServerConf(key string)(cfg.RedisServer,bool){
s,ok := c.RedisServers[key]
return s,ok
}
//监听地址
func (c MyApiConfig) GetListenAddr() string {
return c.Listen
}
// Validate 验证配置
func (c *MyApiConfig) Validate() error {
if c.Listen == "" {
return fmt.Errorf("listen未配置")
}
if c.UserAPI == "" {
return fmt.Errorf("user_api未配置")
}
return nil
}
package middlewares
// pkg/utils/middleware/set_middleware.go
import (
"github.com/gin-gonic/gin"
)
// SetMiddleware 设置中间件
func SetMiddleware(key string, value interface{}) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set(key, value)
c.Next()
}
}
package middleware
// pkg/middleware/constant.go
/**
中间件服务
*/
const (
// 中间件服务
MiddlewareConfig = "config"
MiddlewareTestDB = "testdb"
MiddlewareParam = "param"
MiddlewareUserCache = "usercache"
MiddlewareTestDBORM = "testdborm"
)
控制器的设计
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/middleware"
"log"
"fmt"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/utils/middleware"
)
/**
处理用户相关逻辑的控制器
*/
//用户注册控制器
type UserRegisterReq struct{
//ID int64 `json:"user_id" form:"user_id" binding:"required"`
UserName string `json:"user_name" form:"user_name" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
}
func (c *UserRegisterReq) Validate() error {
if len(c.UserName) < 5 {
return fmt.Errorf("用户名太短")
}
return nil
}
func UserRegister(c *gin.Context) {
req:=c.MustGet(middleware.MiddlewareParam).(*UserRegisterReq)
_,existsUser,err := getUser(c,req.UserName)
if err != nil {
log.Fatalf("UserRegister:查询用户信出错,req:%v,err:%v",req,err)
middlewares.ResposeError(c,middleware.SeverErrorCode,err,"用户查询信息出错")
return
}
//如果用户已经存在
if existsUser {
middlewares.ResposeError(c,middleware.SeverErrorCode,err,"用户已经存在")
}
//如果注册失败
if err = addUser(c,req.UserName,req.Password);err!=nil {
log.Fatalf("UserRegister:注册用户信息失败,req:%v,err:%v",req,err)
middlewares.ResposeError(c,middleware.SeverErrorCode,err,"服务器错误")
}
middlewares.ResponseSuccess(c,"请求成功",nil)
}
//用户登陆注册器
type UserLoginReq struct {
UserName string `json:"user_name" form:"user_name" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
}
func UserLogin(c *gin.Context){
req:=c.MustGet(middleware.MiddlewareParam).(*UserLoginReq)
//查询用户信息
user,existsUser,err:=getUser(c,req.UserName)
if err!=nil {
log.Fatalf("UserLogin:查询用户信出错,req:%v,err:%v",req,err)
middlewares.ResposeError(c,middleware.SeverErrorCode,err,"查询用户信息出错")
return
}
if !existsUser{
middlewares.ResposeError(c,middleware.NotFoundErrCode,err,"用户不存在请你先注册")
return
}
if user.Password != req.Password {
middlewares.ResposeError(c,middleware.ValidateErrCode,err,"密码验证错误")
return
}
middlewares.ResponseSuccess(c,"请求成功",user)
}
func UserGetAll(c *gin.Context){
users,err:=getUsers(c)
if err!= nil {
middlewares.ResposeError(c,middleware.SeverErrorCode,err,"服务器错误")
return
}
middlewares.ResponseSuccess(c,"请求成功",users)
return
}
//用户删除表单
type UserDelreq struct {
Username string `json:"user_name" form:"user_name" binding:"required"`
}
func DelUser(c *gin.Context) {
req:=c.MustGet(middleware.MiddlewareParam).(*UserDelreq)
err:=delUser(c,req.Username)
if err!=nil {
middlewares.ResposeError(c,middleware.SeverErrorCode,err,"删除错误,服务器错误")
return
}
middlewares.ResponseSuccess(c,"删除成功",nil)
}
type UserUptreq struct {
Username string `json:"user_name" form:"user_name" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
}
func (c *UserUptreq) Validate(){
if c.Username == "" || len(c.Username) == 0 {
fmt.Println("用户名不能空")
}
}
func UptUser(c *gin.Context) {
req:=c.MustGet(middleware.MiddlewareParam).(*UserUptreq)
UsrReq:=UserUptreq{
Username:req.Username,
Password:req.Password,
}
err:=uptUser(c,UsrReq)
if err!=nil {
middlewares.ResposeError(c,middleware.SeverErrorCode,err,"更新失败,服务器错误")
return
}
middlewares.ResponseSuccess(c,"更新成功",nil)
}
注册路由
package router
import (
"flag"
"fmt"
"github.com/gin-gonic/gin"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/middleware"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/config"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/utils/middleware"
"runtime"
)
/**
初始化路由操作
*/
var (
tomlFilePath,mode string
)
//初始化engin
func InitEngine() (engine *gin.Engine,tomlConfig *config.MyApiConfig,err error){
flag.StringVar(&tomlFilePath, "config", "/Users/zonst/go/gopro/src/github.com/zxhjames/Zonst/LoginAndRegistDemo/main/docs/valiben.toml", "服务配置文件")
flag.StringVar(&mode, "mode", "release", "模型-debug还是release还是test")
runtime.GOMAXPROCS(runtime.NumCPU())
flag.Parse()
gin.SetMode(mode)
//解析配置文件
tomlConfig,err = config.UnmarshalConfig(tomlFilePath)
if err != nil {
return nil ,tomlConfig,fmt.Errorf("解析配置文件出错")
}
//初始化路由
engine = gin.New()
//初始化中间件
initMiddleware(engine,tomlConfig)
//初始化路由
addHandler(engine)
//返回服务器对象实例,配置文件和错误信息
return engine,tomlConfig,nil
}
//初始化路由
func addHandler(engine *gin.Engine) {
//学生信息子路由
userGroup(engine)
}
//initMiddleware 初始化中间件
func initMiddleware(router *gin.Engine, apiConfig *config.MyApiConfig) {
//跨域模块
router.Use(middlewares.Cors())
//捕获错误,
router.Use(gin.Recovery())
//将配置文件引入中间件
router.Use(middlewares.SetMiddleware(middleware.MiddlewareConfig, apiConfig))
//返回一个DBServer的结构体
testdbCfg, ok := apiConfig.DBServerConf(middleware.MiddlewareTestDB)
if !ok {
panic(fmt.Sprintf("initMiddleware: %v配置不存在\n", middleware.MiddlewareTestDB))
}
//调用DBServer的方法连接数据库
testdb, err := testdbCfg.NewPostgresDB(15)
if err != nil {
panic(fmt.Sprintf("initMiddleware: 连接数据库%v出错, err:%v\n", middleware.MiddlewareTestDB, err))
}
router.Use(middlewares.SetMiddleware(middleware.MiddlewareTestDB, testdb))
testdborm, err := testdbCfg.NewGormDB(20)
if err != nil {
panic(fmt.Sprintf("initMiddleware: 连接数据库%v出错, err:%v\n", middleware.MiddlewareTestDBORM, err))
}
router.Use(middlewares.SetMiddleware(middleware.MiddlewareTestDBORM, testdborm))
usercacheCfg, ok := apiConfig.RedisServerConf(middleware.MiddlewareUserCache)
if !ok {
panic(fmt.Sprintf("initMiddleware: %v配置不存在\n", middleware.MiddlewareUserCache))
}
usercache := usercacheCfg.NewRedisPool(15)
router.Use(middlewares.SetMiddleware(middleware.MiddlewareUserCache, usercache))
}
package router
import (
"github.com/gin-gonic/gin"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/handlers"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/middleware"
"github.com/zxhjames/Zonst/LoginAndRegistDemo/main/pkg/utils/handler_wrapper"
"reflect"
)
func userGroup(engin *gin.Engine) {
// 用户注册
engin.POST("/user/register", handler_wrapper.WrapperHandler(
handlers.UserRegister,
handler_wrapper.ParamBindWrapper,
reflect.TypeOf(handlers.UserRegisterReq{}),
middleware.MiddlewareParam,
))
// 用户登录
engin.POST("/user/login", handler_wrapper.WrapperHandler(
handlers.UserLogin,
handler_wrapper.ParamBindWrapper,
reflect.TypeOf(handlers.UserLoginReq{}),
middleware.MiddlewareParam,
))
//获取所有用户
engin.GET("/user/all",handlers.UserGetAll)
engin.POST("/user/delete", handler_wrapper.WrapperHandler(
handlers.DelUser,
handler_wrapper.ParamBindWrapper,
reflect.TypeOf(handlers.UserDelreq{}),
middleware.MiddlewareParam,
))
engin.POST("/user/update",handler_wrapper.WrapperHandler(
handlers.UptUser,
handler_wrapper.ParamBindWrapper,
reflect.TypeOf(handlers.UserUptreq{}),
middleware.MiddlewareParam,
))
}