命令记不住

登陆

WebGin 简单框架 - controllers

WebGin简单框架-controllers

Web目录结构

C:.
├─.idea
│  └─dictionaries
├─controllers
├─dao
│  ├─mysql
│  └─redis
├─dist
│  └─static
│      ├─css
│      ├─fonts
│      └─js
├─logger
├─logic
├─models
├─pkg
│  ├─myJWT
│  └─snowflake
├─routers
├─setting
└─main.go

1.answer.go

answer.go

package controllers

import (
	"webapp/logic"
	"webapp/models"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func AnswerListHandler(c *gin.Context) {
	questionID, err := getIDFromQuery(c, "question_id")
	if err != nil {
		zap.L().Error("invalid query param", zap.Uint64("questionID", questionID), zap.Error(err))
	}
	offset, limit := getOffsetAndLimit(c)
	zap.L().Debug("get answer list param success",
		zap.Uint64("questionID", questionID),
		zap.Int("offset", offset),
		zap.Int("limit", limit),
	)

	data, err := logic.GetAnswerList(questionID, offset, limit)
	if err != nil {
		zap.L().Error("logic.GetAnswerList failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	ResponseSuccess(c, data)
}

func AnswerCommitHandler(c *gin.Context) {

	var answer = new(models.Answer)
	err := c.BindJSON(answer)
	if err != nil {
		zap.L().Error("bind json failed", zap.Error(err))
		ResponseError(c, CodeInvalidParams)
		return
	}
	if answer.QuestionID == 0 || len(answer.Content) == 0 {
		ResponseError(c, CodeInvalidParams)
		return
	}

	userID, err := getCurrentUserID(c)
	if err != nil {
		zap.L().Warn("user not login", zap.Error(err))
		ResponseError(c, CodeNotLogin)
		return
	}

	answer.AuthorID = userID

	if err := logic.CreateAnswer(answer); err != nil {
		zap.L().Error("logic.CreateAnswer()", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	ResponseSuccess(c, nil)
}

2.auth.go

auth.go

package controllers

import (
	"strings"
	"webapp/pkg/myJWT"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

const (
	ContextUserIDKey = "userID"
)

// 基于JWT实现的登录认证中间件
// 对于需要登陆才能访问的API来说
// 该中间件需要从请求头中获取JWT Token
// 如果没有Token --> /login
// 如果Token过期 --> /login
// 从JWT中解析我们需要的UserID字段  --> 根据userID我们就能从数据库查询到当前请求的用户是谁

// JWTAuthMiddleware 基于JWT的认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
	return func(c *gin.Context) {
		// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI
		// 这里假设Token放在Header的中 Authorization: Bearer token_string
		// 这里的具体实现方式要依据你的实际业务情况决定
		authHeader := c.Request.Header.Get("Authorization")
		if authHeader == "" {
			ResponseErrorWithMsg(c, CodeInvalidToken, "请求头缺少Auth Token")
			c.Abort()
			return
		}
		// 按空格分割
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			ResponseErrorWithMsg(c, CodeInvalidToken, "请求头中auth格式有误")
			c.Abort()
			return
		}
		// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
		mc, err := myJWT.ParseToken(parts[1])
		if err != nil {
			ResponseError(c, CodeInvalidToken)
			zap.L().Warn("invalid JWT token", zap.Error(err))
			c.Abort()
			return
		}
		// 将当前请求的userID信息保存到请求的上下文c上
		c.Set(ContextUserIDKey, mc.UserID)
		c.Next() // 后续的处理函数可以用过c.Get("userID")来获取当前请求的用户信息
		// 返回响应的时候可以做Token/Cookie续期
	}
}

// 基于Cookie和Session认证的中间件
// 对于需要登陆才能访问的API来说
// 该中间件需要从请求中获取Cookie值
// 如果没有Cookie --> /login
// 拿到Cookie值取session数据中找对应的数据,找不到(1.session过期了2.无效的cookie值) --> /login
// session值 也可以通过 c.Set() 直接赋值到 上下文c 上

3.category.go

category.go

package controllers

import (
	"webapp/dao/mysql"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func CategoryListHandler(c *gin.Context) {
	data, err := mysql.GetCategoryList()
	if err != nil {
		zap.L().Error("mysql.GetCategoryList failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	zap.L().Debug("get category list success", zap.Any("data", data))
	ResponseSuccess(c, data)
}

4.code.go

code.go

package controllers

type MyCode int64

const (
	CodeSuccess         MyCode = 1000
	CodeInvalidParams   MyCode = 1001
	CodeUserExist       MyCode = 1002
	CodeUserNotExist    MyCode = 1003
	CodeInvalidPassword MyCode = 1004
	CodeServerBusy      MyCode = 1005

	CodeInvalidToken      MyCode = 1006
	CodeInvalidAuthFormat MyCode = 1007
	CodeNotLogin          MyCode = 1008
)

var msgFlags = map[MyCode]string{
	CodeSuccess:         "success",
	CodeInvalidParams:   "请求参数错误",
	CodeUserExist:       "用户名重复",
	CodeUserNotExist:    "用户不存在",
	CodeInvalidPassword: "用户名或密码错误",
	CodeServerBusy:      "服务繁忙",

	CodeInvalidToken:      "无效的Token",
	CodeInvalidAuthFormat: "认证格式有误",
	CodeNotLogin:          "未登录",
}

func (c MyCode) Msg() string {
	msg, ok := msgFlags[c]
	if ok {
		return msg
	}
	return msgFlags[CodeServerBusy]
}

5.index.go

index.go

package controllers

import (
	"net/http"
	"time"

	"go.uber.org/zap"

	"github.com/spf13/viper"

	"github.com/gin-gonic/gin"
)

func IndexHandler(c *gin.Context) {
	zap.L().Error("this is a error")
	zap.L().Debug("this is index handler")
	zap.L().Info("this is a test log")
	time.Sleep(time.Second * 5)
	c.String(http.StatusOK, viper.GetString("app.ver"))
}

func NeedLoginHandler(c *gin.Context) {
	// 因为之前的JWT中间件中在 上下文c 中保存了 当前登录的userID
	// 我在这个函数中就可以通过 上下文c 取获取当前登录的用户
	v, ok := c.Get(ContextUserIDKey)
	if !ok {
		// 没有取到userID, ---> /login
		ResponseError(c, CodeNotLogin)
		return
	}
	userID := v.(uint64) // 因为c中取出来的值是空接口类型,需要做类型断言
	ResponseSuccess(c, userID)
}

6.question.go

question.go

package controllers

import (
	"fmt"
	"webapp/logic"
	"webapp/models"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

/*
- 参数校验 --> 校验必要的数据是否存在

- 防XSS攻击  --> `html/template`内容检测和转义
- 敏感词过滤  --> 涉黄、涉恐等等屏蔽词
- 数据库创建记录  -->dao层创建记录

*/

func QuestionSubmitHandler(c *gin.Context) {
	var question = new(models.Question)
	err := c.BindJSON(question)
	if err != nil {
		ResponseError(c, CodeInvalidParams)
		return
	}

	if len(question.Caption) == 0 || len(question.Content) == 0 {
		ResponseError(c, CodeInvalidParams)
		return
	}

	userID, err := getCurrentUserID(c)
	if err != nil {
		zap.L().Error("user is not login")
		ResponseError(c, CodeNotLogin)
		return
	}
	question.AuthorID = userID

	if err := logic.CreateQuestion(question); err != nil {
		zap.L().Error("create question failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
	}
	zap.L().Debug("create question success", zap.Any("question", question))
	ResponseSuccess(c, question.QuestionID)
}

func QuestionListHandler(c *gin.Context) {
	offset, limit := getOffsetAndLimit(c)
	zap.L().Debug("get question list param success",
		zap.Int("offset", offset),
		zap.Int("limit", limit),
	)

	data, err := logic.GetQuestionList(offset, limit)
	if err != nil {
		zap.L().Error("logic.GetQuestionList failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	fmt.Println(data, offset, limit)
	zap.L().Debug("get question list success", zap.Any("data", data))
	ResponseSuccess(c, data)
}

func QuestionDetailHandler(c *gin.Context) {

	questionID, err := getIDFromQuery(c, "question_id")
	if err != nil {
		zap.L().Error("invalid query param", zap.Error(err))
		ResponseError(c, CodeInvalidParams)
		return
	}
	data, err := logic.GetQuestionDetail(questionID)
	if err != nil {
		zap.L().Error("get question detail failed", zap.Error(err))
	}
	ResponseSuccess(c, data)
}

7.request.go

request.go

package controllers

import (
	"errors"
	"strconv"

	"github.com/spf13/viper"
	"go.uber.org/zap"

	"github.com/gin-gonic/gin"
)

var (
	ErrorUserNotLogin = errors.New("当前用户未登录")
)

func getCurrentUserID(c *gin.Context) (userID uint64, err error) {
	_userID, ok := c.Get(ContextUserIDKey)
	if !ok {
		err = ErrorUserNotLogin
		return
	}
	userID, ok = _userID.(uint64)
	if !ok {
		err = ErrorUserNotLogin
		return
	}
	return
}

func getOffsetAndLimit(c *gin.Context) (offset, limit int) {
	offsetStr := c.Query("page")
	limitStr := c.Query("limit")

	var err error
	offset, err = strconv.Atoi(offsetStr)
	if err != nil {
		zap.L().Warn("invalid offset", zap.String("offsetStr", offsetStr), zap.Error(err))
	}
	limit, err = strconv.Atoi(limitStr)
	if err != nil {
		zap.L().Warn("invalid limit", zap.String("limitStr", limitStr), zap.Error(err))
		limit = viper.GetInt("app.default_page_size")
	}
	return
}

func getIDFromQuery(c *gin.Context, queryKey string) (uint64, error) {
	idStr := c.Query(queryKey)
	return strconv.ParseUint(idStr, 10, 64)
}

8.response.go

response.go

package controllers

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type ResponseData struct {
	Code    MyCode      `json:"code"`    // 业务状态码
	Message string      `json:"message"` // 提示信息
	Data    interface{} `json:"data"`    // 数据
}

// ResponseError 根据错误状态码返回响应
func ResponseError(ctx *gin.Context, c MyCode) {
	rd := &ResponseData{
		Code:    c,
		Message: c.Msg(),
		Data:    nil,
	}
	ctx.JSON(http.StatusOK, rd)
}

// ResponseErrorWithMsg 返回自定义类型的错误信息
func ResponseErrorWithMsg(ctx *gin.Context, code MyCode, errMsg string) {
	rd := &ResponseData{
		Code:    code,
		Message: errMsg,
		Data:    nil,
	}
	ctx.JSON(http.StatusOK, rd)
}

// ResponseSuccess 返回正常响应
func ResponseSuccess(ctx *gin.Context, data interface{}) {
	rd := &ResponseData{
		Code:    CodeSuccess,
		Message: CodeSuccess.Msg(),
		Data:    data,
	}
	ctx.JSON(http.StatusOK, rd)
}

9.user.go

user.go

package controllers

import (
	"webapp/dao/mysql"
	"webapp/models"
	"webapp/pkg/myJWT"

	"go.uber.org/zap"

	"github.com/gin-gonic/gin"
)

func SignUpHandler(c *gin.Context) {
	// 1. 提取用户提交的注册信息
	// 2. 并进行数据校验
	var sd models.SignUpForm
	if err := c.ShouldBindJSON(&sd); err != nil {
		// 返回参数错误的响应
		zap.L().Error("invalid params", zap.Error(err))
		ResponseErrorWithMsg(c, CodeInvalidParams, err.Error())
		return
	}
	// 3. 保存到数据库
	u := &models.User{
		UserName: sd.UserName,
		Password: sd.Password,
	}
	if err := mysql.Register(u); err != nil {
		zap.L().Error("sign up user failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	// 4. 返回响应
	ResponseSuccess(c, nil)
}

func LoginHandler(c *gin.Context) {
	// 1. 获取登录请求携带的用户名、密码数据 -> 获取请求的数据
	var u models.User
	if err := c.ShouldBindJSON(&u); err != nil {
		ResponseErrorWithMsg(c, CodeInvalidParams, err.Error())
		return
	}
	// 2. 去数据库校验用户名和密码是否正确
	if err := mysql.Login(&u); err != nil {
		ResponseError(c, CodeInvalidPassword)
		return
	}
	// 3. 生成JWT Token
	tokenString, err := myJWT.GenToken(u.UserID)
	if err != nil {
		zap.L().Error("myJWT.GenToken failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	// 如果是基于Cookie/Session的模式
	// 生成Cookie值,并生成后端的session数据
	// Cookie值返回给客户端种到浏览器的Cookie中
	// 4. 返回响应
	ResponseSuccess(c, gin.H{
		"token":    tokenString,
		"userID":   u.UserID,
		"username": u.UserName,
	})
}
推荐阅读