Go Web开发进阶实战(gin框架) 讲师:李文周老师

https://study.163.com/course/introduction.htm?courseId=1210171207&trace_c_p_k2=09f1339f33db432ab570cbf2e92c15b0

CLD架构的错误,参数和响应处理

路由请求进来以后,由routes转发到Controller层,

Controller层负责处理路由、参数校验、请求转发。

Controller层调用Logic层的函数,请求转发到Logic层

Logic层负责处理业务逻辑。Logic调用Dao层

DAO负责数据与存储相关功能

以注册功能为例,走一下流程,体验一下CLD

我们从main函数开始。

初始化配置,每个函数都返回一个err,假如err!=nil,直接控制台上打印到那里出错停止的

注意,这里没打印err。接着return,让主程序停止

  • 1.加载配置
  • 2.初始化日志
  • 3.初始化数据库连接
  • 4.初始化Redis连接
  • 5.注册路由
  • 6.启动服务

举个例子

// 3.初始化数据库连接
if err = repo.InitDB(settings.Conf.MySQLConfig);err != nil{
fmt.Println("程序退出,因为数据库错误")
return
}
defer repo.CloseDB()

routes

注册路径,转发请求

r.POST("/login",controller.LoginHandler)

controller

1.获取参数和参数校验

2.业务处理

3.返回响应

1.首先去获取参数和对参数进行校验,七米老师说不能相信前端,万一有恶意攻击就不好了。

2.业务处理中。。。还记得我们在main函数里出错了以后做的啥吗?fmt.Println("程序退出,因为数据库错误") return ,所以我们是在Controller层记录的错误日志。

Controller层调用的Logic层的方法,或者其他的一些方法,都会得到返回值至少包括一个错误。这个错误就是出错了层层往上递,假如Controller层调用Logic层的函数,Logic层的函数又调用了DAO层的函数,假如dao层出错了,就会停止执行,然后返回一个err给Logic层,logic层发现错误不为空后,也停止执行,把错误往Controller层送,最终的错误值就在Controller层,所以我们在Controller层要判断错误,假如错误不为空,那么就记录在日志了,给前端一个响应后停止执行。

3.返回响应。出错了以后直接返回响应。假如一直没出错,就返回一个success响应

func SignUpHandler(c *gin.Context) {
var err error
// 1.获取参数和参数校验
var p = new(models.ParamSignUp)
// 这个p是指向结构体的指针类型
if err = c.ShouldBindJSON(p); err != nil {
//请求参数有误,直接返回响应
zap.L().Error("SignUp with unValid Param", zap.Error(err))
//类型断言,判断err是不是validator类型
errs, ok := err.(validator.ValidationErrors)
// 注意这里,这个errs不是error类型,切记切记
// 不要先声明成error类型,后面的.Translate 会报错的
if !ok {
// 非validator.ValidationErrors类型错误直接返回
ResponseError(c,CodeInvalidParam)
return
}
// validator.ValidationErrors类型错误则进行翻译
ResponseErrorWithMsg(c,CodeInvalidParam,removeTopStruct(errs.Translate(trans)))
} // 2.业务处理
if err=service.SignUP(p);err!=nil{
zap.L().Error("SignUp in Mysql failed", zap.Error(err))
if errors.Is(err, models.ErrorUserExist){
ResponseError(c,CodeUserExist)
}else{
// 应该是数据库的其他错误
//错误已经记录在日志里了,这里就是给前端个返回
ResponseError(c,CodeServerBusy)
}
return
}
//3.假如没出错返回成功响应
ResopnseSuccess(c,nil)
}

Logic 层

Logic层就对业务进行处理呗,调用dao层的方法和函数

func SignUP(p *models.ParamSignUp) (err error){
// 1.判断用户是否已经存在
if err = mysql.CheckUserExist(p.UserName);err!=nil{
return err
}
// 2.生成UID
userID :=snowflake.GenID()
//创建一个user实例
var u = models.User{
UserID: userID,
UserName: p.UserName,
Password: p.PassWord,
}
// 3.保存进数据库
err = mysql.InsertUser(&u)
return err
}

Dao

数据库存储

func CheckUserExist(username string) (err error){
sqlStr := "select count(user_id) from user where username = ?"
var count int
if err = db.Get(&count,sqlStr,username);err!=nil{
return err
}
if count >0 {
return models.ErrorUserExist
}
return
}
func InsertUser(u *models.User) error{
// 对密码进行加密
u.Password = encryptPassword(u.Password)
sqlStr:="insert into user(user_id,username,password) values(?,?,?)"
_,err:=db.Exec(sqlStr,u.UserID,u.UserName,u.Password)
return err
}

参数

我们可以看到,Controller层传下去的参数是models.ParamSignUp,而到了Logic层传下去的就变成了models.User,也就是真正对应数据库的表所建的模型。

有啥区别呢,就是Param就是前端给啥或者要啥,就做这样的结构体。比如前端要个首页的帖子列表,那就ParamPost只需要标题,作者,还有内容的一部分。而比如Post是和数据库进行交互,数据库有啥,它就有啥。

注意:这属于模型的两种方面,放一个包里,但不要放在一个go文件里,

//与前端联系
type ParamSignUp struct{
UserName string `json:"username"`
PassWord string `json:"password"`
RePassWord string `json:"re_password"`
}
//与后端数据库联系
type User struct{
UserID int64 `db:"user_id"`
UserName string `db:"username"`
Password string `db:"password"`
}

错误和状态码和响应

c.JSON(http.StatusOK, gin.H{

""msg": err.Error(),})

太琐碎了,所以定义业务状态码,并封装响应方法。如何判断业务状态码呢,看错误的类型。所以我们又自定义了错误类型。现在,错误,状态码和响应方法有对应关系了。

另外给响应方法一个固定格式,这样前端也方便处理。

error

//我在model层的error.go定义的定制错误
var(
ErrorUserExist= errors.New("用户已存在")
ErrorUserNotExist = errors.New("用户不存在")
ErrorNotMatch = errors.New("用户名或密码错误")
)

所以之前的数据库文件中,注册查询到用户名已存在,返回models.ErrorUserExist

登录时查不到该用户,返回models.ErrorUserNotExist

因为错误不可能都定义完,有些也不能去定义,查询时出错,你知道是哪错了吗?。所以一部分判断不了的直接返回这个err。

在Controller层,假如err!=nil,对err进行判断errors.Is(myerr, targetErr)

,然后选择对应的状态码和响应

code

因为只有在Controller层我们返回请求,所以code和Resonse就直接放在Controller层了

//code
package controller
//这里定义的状态码
type Rescode int64
//在const里使用了iota自增
const(
CodeSuccess Rescode = 1000+ iota
CodeInvalidParam
CodeUserExist
CodeUserNotExist
CodeNotMatch
CodeServerBusy
)
//给对应状态码加上消息
var codeMsgMap = map[Rescode]string{
CodeSuccess :"success",
CodeInvalidParam:"请求参数错误",
CodeUserExist:"用户名已存在",
CodeUserNotExist:"用户不存在",
CodeNotMatch:"用户名或密码错误",
CodeServerBusy:"服务器繁忙",
// 比如数据库连接错误啥的,前端不用知道,
// 就说服务器繁忙就行
}
//获得对应状态码的消息
func (c Rescode) GetMsg() string{
msg,ok := codeMsgMap[c]
if !ok{
msg = codeMsgMap[CodeServerBusy]
}
return msg
}

Response

package controller

import (
"github.com/gin-gonic/gin"
"net/http"
) /*
封装响应方法,固定格式,方便前端
{
"code": , //程序中的错误码
"msg": ,// 错误的提示信息
"data":{} //数据
}
*/
type ResopnseDate struct{
Code Rescode `json:"code"`
Msg interface{} `json:"msg"`
Date interface{} `json:"data"`
} func ResponseError(c *gin.Context,code Rescode){
//只有状态码
c.JSON(http.StatusOK,&ResopnseDate{
Code: code,
Msg: code.GetMsg(),
Date: nil,
})
}
func ResponseErrorWithMsg(c *gin.Context,code Rescode,msg interface{}){
//有状态码和自己的信息
c.JSON(http.StatusOK,&ResopnseDate{
Code: code,
Msg: msg,
Date: nil,
})
}
func ResopnseSuccess(c *gin.Context,data interface{}){
//成功以后返回的,自带数据
c.JSON(http.StatusOK,&ResopnseDate{
Code: CodeSuccess,
Msg: CodeSuccess.GetMsg(),
Date: data,
})
}

举例子

if err=service.Login(p);err != nil{
//错误记录在日志里
zap.L().Error("Login failed", zap.String("username",p.UserName),zap.Error(err)) if errors.Is(err, models.ErrorUserNotExist){
//根据错误判断code
ResponseError(c,CodeUserNotExist)
}else if errors.Is(err,models.ErrorNotMatch){
ResponseError(c,CodeNotMatch)
}else{
// 应该是数据库的其他错误
//错误已经记录在日志里了,这里就是给前端个返回
//y=有些错误没必要让前端知道,前端知道你查询映射数目对不上干嘛?
//可以直接服务器繁忙
ResponseError(c,CodeServerBusy)
}
return
}

gowWeb之错误处理和返回响应的更多相关文章

  1. HttpServletResponse ServletResponse 返回响应 设置响应头设置响应正文体 重定向 常用方法 如何重定向 响应编码 响应乱码

    原文地址:HttpServletResponse ServletResponse 返回响应 设置响应头设置响应正文体 重定向 常用方法 如何重定向 响应编码 响应乱码 HttpServletRespo ...

  2. ASP.NET Core错误处理中间件[4]: 响应状态码页面

    StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件类似,它们都是在后续请求处理过程中"出错"的情况下利用一个错误处 ...

  3. springmvc全局异常处理ControllerAdvice区分返回响应类型是页面还是JSON

    思路: 加一个拦截器,在preHandler中取得HandlerMethod,判断其方法的返回类型,以及方法的注解和类的注解. 如果返回是json,收到异常则返回默认的异常包装类型. 如果返回是页面, ...

  4. win10系统Mysql5.7服务启动报:"1053错误:服务没有及时响应启动或控制请求"

    win10安装Mysql5.7: MySQL压缩包解压后,在目录下增加my.ini配置文件 [mysqld] port = basedir=D:\Mysql datadir=D:\Mysql\data ...

  5. WCF错误远程服务器返回了意外响应: (413) Request Entity Too Large。解决方案

    这个问题出现的原因是  调用wcf服务的时候传递的参数 长度太大   wcf数据传输采用的默认的大小是65535字节. ---------------------------------------- ...

  6. IIS7.0设置404错误页,返回500状态码

    一般在II6下,设置自定义404错误页时,只需要在错误页中选择自定义的页面,做自己的404页面即可.但是在IIS7.0及以上时,设置完404错误页后,会发现状态码返回的是500,并且可能会引起页面乱码 ...

  7. http请求返回响应码的意思

    HTTP 状态响应码 意思详解/大全 HTTP状态码(HTTP Status Code)是用以表示网页服务器HTTP响应状态的3位数字代码.它由 RFC 2616 规范定义的,并得到RFC 2518. ...

  8. http请求返回响应码及意义

    http 响应码及意义 HTTP状态码(HTTP Status Code)是用以表示网页服务器HTTP响应状态的3位数字代码.它由 RFC 2616 规范定义的,并得到RFC 2518.RFC 281 ...

  9. iis设置404错误页,返回500状态码

    一般在II6下,设置自定义404错误页时,只需要在错误页中选择自定义的页面,做自己的404页面即可.但是在IIS7.0及以上时,设置完404错误页后,会发现状态码返回的是500,并且可能会引起页面乱码 ...

  10. Java开发学习(二十六)----SpringMVC返回响应结果

    SpringMVC接收到请求和数据后,进行了一些处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户. 比如:根据用户ID查 ...

随机推荐

  1. OctConv:八度卷积复现

    摘要:不同于传统的卷积,八度卷积主要针对图像的高频信号与低频信号. 本文分享自华为云社区<OctConv:八度卷积复现>,作者:李长安 . 论文解读 八度卷积于2019年在论文<Dr ...

  2. 百度松果菁英班--oj赛(第二次)

    目录 一.小码哥剪绳子 二.咖啡品鉴师小码哥 三.均分糖果 四.持盾 五.活动安排 六.甜品供应 七.斐波那契数列的组合 八.小码哥的布阵指挥 九.活动分组 十.外卖递送 一.小码哥剪绳子 题目:马上 ...

  3. Sql批量替换字段字符,Sql批量替换多字段字符,Sql替换字符

    update phome_ecms_news_check set filename= replace(filename,'Under4-',''); update phome_ecms_news_ch ...

  4. T-SQL基础教程Day3

    第三章 联接3.1交叉联接交叉联接是最简单的联接类型.交叉联接仅执行一个逻辑查询处理阶段--笛卡尔乘积将一个输入表的每一行与另一个表的所有行匹配SQL Server支持交叉联接的两种标准语法:ANSI ...

  5. 开源后台管理系统解决方案 boot-admin 简介

    介绍 boot-admin 是一款采用前后端分离架构模式的后台管理框架.系统提炼自实际项目,兼具RuoYi-Vue前端分离版和Ruoyi-Cloud微服务版功能与技术特点. boot-admin 既有 ...

  6. RabbitMQ详解(上)

    一:MQ的相关概念 MQ(message queue),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是message 而已,还是一种跨进程的通信机制,用于上下游传递消息.在 ...

  7. 第138篇:了解HTTP协议(TCP/IP协议,DNS域名解析,浏览器缓存)

    好家伙,发现自己的网络知识十分匮乏,赶紧补一下   这里先举个我生活中的例子 欸,作业不会写了,上网搜一下 用edge浏览器上bing必应搜一下(百度广告太多了,真不想用百度举例子)   假设这是我们 ...

  8. [C++基础入门] 1、C++初识

    文章目录 1 C++初识 1.1 第一个C++程序 1.1.1 创建项目 1.1.2 创建文件 1.1.3 编写代码 1.1.4 运行程序 1.2 注释 1.3 变量 1.4 常量 1.5 关键字 1 ...

  9. Ansible中的变量

    Ansible中的变量 目录 Ansible中的变量 变量概述 变量定义的方式 变量的优先级 如何定义变量 playbook中定义变量 vars_file中定义变量 系统内置变量 inventory定义 ...

  10. 2021-03-11:go中,协程内部再启用协程,它们是没关系,对吧?外部协程奔溃,内部协程还会执行吗?外部协程执行结束的时候,如何让内部协程也停止运行?golang原生提供的包里,让内部协程停止运行,如何实现?

    2021-03-11:go中,协程内部再启用协程,它们是没关系,对吧?外部协程奔溃,内部协程还会执行吗?外部协程执行结束的时候,如何让内部协程也停止运行?golang原生提供的包里,让内部协程停止运行 ...