
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, }) }