gin项目登陆注册demo

最近学习了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,
	))
}

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值