学习gin框架知识的注意点

news2024/11/19 5:57:19

这几天重新学习了一遍gin框架:收获颇多

Gin框架的初始化

有些项目中 初始化gin框架写的是: r := gin.New()

r.Use(logger.GinLogger(), logger.GinRecovery(true))

而不是r := gin.Default()

为什么呢?

点击进入Default源码发现其实他也是new+两个中间件,(Logger,Recovery)

default源码
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
     engine.Use(Logger(), Recovery())
    return engine
}

但是中间件已经固定了,对于一些精细化开发来说换成new+自定义中间件则更有性价比;

自定义的:
	r := gin.New()
	r.Use(logger.GinLogger(), logger.GinRecovery(true)) //中间件
更精细化,这两个区别是一个用的是基础log,一个用的是gin框架特殊处理的log

项目结构

后端处理路线通常是先写router层:各种各样的路由信息,

再写controller层:

1 获取参数和参数校验 :定义参数结构,接收参数并存放
2 业务处理 :写到逻辑层
3 返回响应 :可以写中间件方便多次处理

然后logic层:(举登录的例子)

1.一系列登录逻辑判断:
用户是否存在         sql写在dao层,供logic层调用
账号密码是否符合格式  可以不使用sql语句的,直接逻辑判断
判断完成生成对应uid   调用一系列外置方法
对数据进行加密        调用一系列外置方法
保存进数据库          调用dao层sql方法

然后是dao层,写各种数据库操作封装成的方法以供调用

在我看来,controller层最大,logic以实现controller层的业务而出现,dao层最小,以实现logic层中的数据库操作为己任;

所以每次写小需求,先写router层,再写controller层,再写logic层,然后是dao层

这样理解起来比盲目学习要好很多;

Query和Param的区别

虽然已经学了不知道多少遍了,但这两个老是弄反,再写一遍吧

Query:

  • Query 用于获取 URL 查询参数。它从 URL 中解析参数,并返回一个字符串值。

如:

name := c.Query("name")

https://example.com/search?query=golang&page=1&limit=10

Param:

  • Param 用于获取路径参数。路径参数是包含在 URL 中的参数,例如 /users/:id 中的 :id 就是路径参数。

如:

https://example.com/users/123
userID := c.Param("id") // Returns "123"

数据校验

而shouldbindjson是什么呢?

ShouldBindJSON:

  • ShouldBindJSON 用于将 JSON 数据绑定到结构体。它从请求的 JSON 主体中提取数据,并将其解析到指定的结构体中。
var user User
if err := c.ShouldBindJSON(&user); err == nil {
    // 处理 user 结构体中的数据
}

将 HTTP 请求体中的 JSON 数据绑定到变量 user

注意点:ShouldBindJSON只能识别是不是json格式,并不能保证数据是否完全符合结构体格式,例如

image-20240125171059861

image-20240125171014484image-20240125171205274

这样写都会直接通过,

只有

image-20240125171143727

会报错,所以需要手动去对数据进行业务判断(数据校验),例如数据不为空,数据类型不对,数据不符合格式等等,因此就引入了第三方的validator库进行爽歪歪!(^ ▽ ^),ps:之后说这个库

image-20240125172035094

binding就是gin框架参数校验的tag:required就是不能为空,不然直接结束

常用tag

  1. form:
    • form:"fieldName" 表示该字段的值将从 HTTP 请求的表单数据中获取,其中 "fieldName" 是表单字段的名称。
  2. query:
    • query:"paramName" 表示该字段的值将从 URL 查询参数中获取,其中 "paramName" 是查询参数的名称。
  3. json:
    • json:"fieldName" 表示该字段的值将从 JSON 数据中获取,其中 "fieldName" 是 JSON 对象中的字段名。
  4. uri:
    • uri:"paramName" 表示该字段的值将从 URL 的路径参数中获取,其中 "paramName" 是路径参数的名称。
  5. binding:
    • binding:"required" 表示该字段是必需的,如果请求中缺少该字段,将返回错误。
  6. xml:
    • xml:"fieldName" 表示该字段的值将从 XML 数据中获取,其中 "fieldName" 是 XML 对象中的字段名。
  7. header:
    • header:"Header-Name" 表示该字段的值将从 HTTP 请求头中获取,其中 "Header-Name" 是请求头的名称。
  8. time:
    • time_format:"2006-01-02" 表示该时间字段的格式,用于将字符串转换为时间类型。

这些标签值用于告诉 Gin 框架在处理请求时如何从不同的数据源(如表单、查询参数、JSON 数据等)中提取和绑定数据。在使用 Gin 进行参数绑定时,可以根据请求中的数据类型和来源选择适当的标签。

validator库

接下来是重头戏validator库的说明,validator使用起来还是非常方便的,就是配置麻烦了一点(只是亿点点…)

image-20240125172606561

validator 提供了许多内置的验证规则,如 requiredminmaxemailurl 等,同时也支持使用正则表达式、自定义函数等进行验证。使用 validator 可以有效地提高应用程序的数据完整性和安全性。

而当不符合validator的tag规则时,返回的错误也是由validator库内置的,比如image-20240125172900409

我们需要将其改成中文并符合我们自己的代码习惯(这种配置配一次就行了,以后项目框架基本不会变,validator的代码也几乎不需要变化);

例如说自动翻译成中文,将特定字段格式转为json供前端使用,去掉结构体名称,处理复杂逻辑时候的结构体转换,咳咳,想要完美道阻且艰,错误信息完美到这里已经差不多了(还有很多优化空间阿伟!)

然后是固定代码格式:(包含了初始化翻译器,可以自动翻译中文,把结构体字段转为json,去掉包名,给用户看)

最后还是只有三个功能,可以直接使用哦~~

package controller

import (
    "fmt"
    "reflect"
    "strings"

    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/locales/en"
    "github.com/go-playground/locales/zh"
    ut "github.com/go-playground/universal-translator"
    "github.com/go-playground/validator/v10"
    enTranslations "github.com/go-playground/validator/v10/translations/en"
    zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)

// 这种初始化代码,知道意思就行,因为每次的项目这个都是提前写好的;
// 定义一个全局翻译器T
var trans ut.Translator

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
    // 修改gin框架中的Validator引擎属性,实现自定制
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
       // 注册一个获取json tag的自定义方法
       v.RegisterTagNameFunc(func(fld reflect.StructField) string {
          name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
          if name == "-" {
             return ""
          }
          return name
       })

       zhT := zh.New() // 中文翻译器
       enT := en.New() // 英文翻译器

       // 第一个参数是备用(fallback)的语言环境
       // 后面的参数是应该支持的语言环境(支持多个)
       // uni := ut.New(zhT, zhT) 也是可以的
       uni := ut.New(enT, zhT, enT)

       // locale 通常取决于 http 请求头的 'Accept-Language'
       var ok bool
       // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
       trans, ok = uni.GetTranslator(locale)
       if !ok {
          return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
       }

       // 注册翻译器
       switch locale {
       case "en":
          err = enTranslations.RegisterDefaultTranslations(v, trans)
       case "zh":
          err = zhTranslations.RegisterDefaultTranslations(v, trans)
       default:
          err = zhTranslations.RegisterDefaultTranslations(v, trans)
       }
       return
    }
    return
}


// removeTopStruct 去除提示信息中的结构体名称,使用时直接包裹就行了
func removeTopStruct(field map[string]string) map[string]string {
	res := map[string]string{}
	for field, err := range field {
		res[field[strings.Index(field, ".")+1:]] = err

	}
	return res
}

使用的时候举例子:

controller/user的post登陆请求

func SignUpHandler(c *gin.Context) {
    //1 获取参数和参数校验
    p := new(models.ParamSignUp) //声明参数p是接收账号密码时的类型
    err := c.ShouldBindJSON(&p)  //获取参数传给p
    if err != nil {
       zap.L().Error("SignUp with invalid param", zap.Error(err)) //用日志输出
       //判断err是不是validator.ValidationErrors 类型,不是的话就不翻译了
       errs, ok := err.(validator.ValidationErrors)
       if !ok { //不是validator类型,正常响应
          c.JSON(http.StatusOK, gin.H{
             "msg": err.Error(),
          })
       }
       //是validator类型的,进行翻译操作
       c.JSON(http.StatusOK, gin.H{
           "msg": removeTopStruct(errs.Translate(trans)),
       })
       return
    }

只用判断是不是validator类型,是的话返回err.Translate(trans)就好了

image-20240125205830430

image-20240126103722304

至于为什么不做其他的处理直到完美,首先代码复杂了,其次对效率也有不小的影响,有这三步已经差不多了;

对状态码的定义

为什么要引入自定义状态码呢?首先我们看自己写的响应

image-20240126104153447

我们会发现很大一部分代码都用来表示重复的响应代码段了,有什么统一格式的写法么?

当然有!蹡蹡

我们可以定义成这种格式,为什么呢,因为统一格式有利于前端进行工作,包括看错误提示信息和接受数据都方便一些

image-20240126104112812

把经常会用到的响应单独拉出来写进一个文件,例如:

package controller

type MyCode int64 //自定义MyCode类型

const ( //使用 const 关键字定义了一组错误码,每个错误码都是 MyCode 类型的常量。
    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{ //map类型,映射错误码和信息
    CodeSuccess:         "success",
    CodeInvalidParams:   "请求参数错误",
    CodeUserExist:       "用户名重复",
    CodeUserNotExist:    "用户不存在",
    CodeInvalidPassword: "用户名或密码错误",
    CodeServerBusy:      "服务繁忙",

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

func (c MyCode) Msg() string { //把上边两个连起来,传入code,传出code对应的信息
    //当调用 Msg 方法时,会查找 msgFlags 中是否存在对应的错误信息,
    //如果存在则返回,否则返回默认的错误信息,例如 CodeServerBusy 对应的信息。
    msg, ok := msgFlags[c]
    if ok {
       return msg
    }
    return msgFlags[CodeServerBusy]
}


再写几个方法把这些响应给包装起来:
例如
type ResponseData struct {
	Code    MyCode      `json:"code"` //把错误码都拉过来
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}


func ResponseError(c *gin.Context, code MyCode) { //错误的,传入状态码,直接用写好的错误信息
	rd := &ResponseData{
		Code:    code,
		Message: code.Msg(),
		Data:    nil,
	}
	c.JSON(http.StatusOK, rd)
}

在代码中调用时前后比较

if err != nil {
    zap.L().Error("SignUp with invalid param", zap.Error(err)) //用日志输出
    //判断err是不是validator.ValidationErrors 类型,不是的话就不翻译了
    errs, ok := err.(validator.ValidationErrors)
    if !ok { //不是validator类型,正常响应
    //这里原本是写响应状态码和msg,但现在就可以直接调用定义好的了
       ResponseError(c, CodeUserExist)
    }

然后是登录

用户认证

cookie和session方式

我们平时访问网站,当不登录时,权限是非常低的,因为http是无状态的协议

image-20240126144148161

image-20240126144403112

需要每次访问接口时将cookie和session也访问一下是否带cookie的值

问题:

image-20240126144530440

所以token也就应运而生了

token方式

image-20240126144818406image-20240126144856271

看这个图的话会觉得和cookie,session方式基本差不多

其实差距还是很大的

image-20240126145056314

而对应的实践就是JWT了

image-20240126145925563

  • 签名

    image-20240126150526364

image-20240126150951510

三部分组成:

  1. Header(头部):

    • 头部通常由两部分组成:令牌的类型(JWT)和所使用的签名算法,例如 HMAC SHA256 或 RSA。
    • 头部是Base64Url编码的JSON字符串。

    示例:

     code{
      "alg": "HS256",
      "typ": "JWT"
    }
    
  2. Payload(负载):

    • 负载包含有关声明(claims)的信息,可以包括用户的身份信息、权限等。
    • 负载也是Base64Url编码的JSON字符串。

    示例:

     code{
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
    
  3. Signature(签名):

    • 签名是通过将编码后的头部、编码后的负载和秘钥进行签名生成的。
    • 签名用于验证消息的完整性和认证发送方。

    示例:

     codeHMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)
    

这三部分通过点号连接在一起,形成完整的JWT,如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIkpvaG4gRG9lIiwgI

需要知道这串jwt并没有进行过加密,若需要保证数据安全性,可以用加密算法处理

总结一下这几天学习的心得:

其实ginweb并不难,难的是没有web基础就进入学习,刚学时,并不清楚日志,配置文件,数据验证等等web中要用到的各种库,其实把理论学好分开来看,这些初始化以及只需后期调用的代码,是可以一直重复利用的,只要是web项目,那么只用一套完整的初始化流程代码,就可以只用专注于写接口就行了,只需要会改配置,知道什么意思就行,而接口书写上边已经说过了,只有router层,controller层,logic层,然后是dao层,甚至很多接口还是高度重复的,。

所以才有基础web开发最简单这种说法,现在我也越发的认同了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1413300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据就业方向-(工作)ETL开发

上一篇文章: 大数据 - 大数据入门第一篇 | 关于大数据你了解多少?-CSDN博客 目录 🐶1.ETL概念 🐶2. ETL的用处 🐶3.ETL实现方式 🐶4. ETL体系结构 🐶5. 什么是ETL技术? &…

Linux——搭建FTP服务器

1、FTP简介 FTP(File Transfer Protocol) :是一种处于应用层的用于文件传输的协议。FTP客户端和FTP服务器之间的通信使用TCP/IP协议族。它规定了客户端和服务器之间的通信格式和命令集,包括用户认证、文件传输、文件名和目录信息等,允许用户…

掌握可视化大屏:提升数据分析和决策能力的关键(下)

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Java - OpenSSL与国密OpenSSL

文章目录 一、定义 OpenSSL:OpenSSL是一个开放源代码的SSL/TLS协议实现,也是一个功能丰富的加密库,提供了各种主要的加密算法、常用的密钥和证书封装管理功能以及SSL协议。它被广泛应用于Web服务器、电子邮件服务器、VPN等网络应用中&#x…

dvwa靶场文件上传high

dvwa upload high 第一次尝试(查看是否是前端验证)第二次尝试我的上传思路最后发现是图片码上传修改配置文件尝试蚁🗡连接菜刀连接 第一次尝试(查看是否是前端验证) 因为我是初学者,所以无法从代码审计角度…

第14次修改了可删除可持久保存的前端html备忘录:增加一个翻牌钟,修改背景主题:现代深色

第14次修改了可删除可持久保存的前端html备忘录&#xff1a;增加一个翻牌钟&#xff0c;修改背景主题&#xff1a;现代深色 备忘录代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta http-equiv"X…

Scala基础知识

scala 1、scala简介 ​ scala是运行在JVM上的多范式编程语言&#xff0c;同时支持面向对象和面向函数式编程。 2、scala解释器 要启动scala解释器&#xff0c;只需要以下几步&#xff1a; 按住windows键 r输入scala即可 在scala命令提示窗口中执行:quit&#xff0c;即可退…

线扫相机使用教程

一.线扫相机的采集原理 在现有的工业 2D 相机中&#xff0c;主要有两种类型的相机&#xff0c;面阵相机和线扫相机。这两种相机有其 各自的特点。 面阵相机&#xff1a;主要用于采集较小尺寸的产品&#xff0c;特别是长度方向较小的产品。其采集原理是通过 单次或多次曝光&…

nav02 学习03 机器人传感器

机器人传感器 移动机器人配备了大量传感器&#xff0c;使它们能够看到和感知周围的环境。这些传感器获取的信息可用于构建和维护环境地图、在地图上定位机器人以及查看环境中的障碍物。这些任务对于能够安全有效地在动态环境中导航机器人至关重要。 机器人的传感器类似人的感官…

蓝桥杯备战——7.DS18B20温度传感器

1.分析原理图 通过上图我们可以看到DS18B20通过单总线接到了单片机的P14上。 2.查阅DS18B20使用手册 比赛的时候是会提供DS18B20单总线通讯协议的代码&#xff0c;但是没有提供读取温度数据的代码&#xff0c;所以还是需要我们去查看手册&#xff0c;我只把重要部分截下来了 …

使用Spring Boot和Tess4J实现本地与远程图片的文字识别

概要&#xff1a; 在本文中&#xff0c;我们将探讨如何在Spring Boot应用程序里集成Tess4J来实现OCR&#xff08;光学字符识别&#xff09;&#xff0c;以识别出本地和远程图片中的文字。我们将从添加依赖说起&#xff0c;然后创建服务类以实现OCR&#xff0c;最后展示如何处理…

用友移动管理系统 getApp SQL注入漏洞复现

0x01 产品简介 用友移动系统管理是用友公司推出的一款移动办公解决方案,旨在帮助企业实现移动办公、提高管理效率和员工工作灵活性。它提供了一系列功能和工具,方便用户在移动设备上管理和处理企业的系统和业务。 0x02 漏洞概述 用友移动管理系统 getApp 功能点未对用户的…

Hadoop增加新节点环境配置(自用)

完成Hadoop集群增添一个新的节点配置&#xff08;文中命名为&#xff09;Hadoop106&#xff0c;没有进行继续为该节点分配身份职能的步骤 1.在VMware中安装CentOS 7 新建虚拟机 1.⾸先我们创建⼀个新的虚拟机&#xff0c;也可以点⽂件-新建虚拟机。 2.选择⾃定义&#xff0c…

[C++]使用纯opencv部署yolov8旋转框目标检测

【官方框架地址】 https://github.com/ultralytics/ultralytics 【算法介绍】 YOLOv8是一种先进的对象检测算法&#xff0c;它通过单个神经网络实现了快速的物体检测。其中&#xff0c;旋转框检测是YOLOv8的一项重要特性&#xff0c;它可以有效地检测出不同方向和角度的物体。…

掌握可视化大屏:提升数据分析和决策能力的关键(上)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【CSS】字体效果展示

测试时使用了Google浏览器。 1.Courier New 2.monospace 3.Franklin Gothic Medium 4.Arial Narrow 5.Arial 6.sans-serif 7.Gill Sans MT 8.Calibri 9.Trebuchet MS 10.Lucida Sans 11.Lucida Grande 12.Lucida Sans Unicode 13.Geneva 14.Verdana 15.Segoe UI 16.Tahoma 17.…

PCB【基板】

1、fr4板是一种常用的绝缘基材&#xff0c;由玻璃纤维和环氧树脂组成【稳定性和耐温性、通信设备、消费电子和汽车电子、低功率电子产品】 2、铝基板&#xff0c;则是将铝合金作为基材【良好的导热性能、功率电子模块、高功率和高温环境】

(大众金融)SQL server面试题(1)-总销售量最少的3个型号的车及其总销售量

今天&#xff0c;面试了一家公司&#xff0c;什么也不说先来三道面试题做做&#xff0c;第一题。 那么&#xff0c;我们就开始做题吧&#xff0c;谁叫我们是打工人呢。 题目是这样的&#xff1a; 统计除豪车外&#xff0c;销售最差的车 车辆按批销售&#xff0c;每次销售若干…

【笔记】顺利通过EMC试验(16-41)-视频笔记

目录 视频链接 P1:电子设备中有哪些主要骚扰源 P2:怎样减小DC模块的骚扰 P3:PCB上的辐射源究竟在哪里 P4:怎样控制PCB板的电磁辐射 P5:多层线路板是解决电磁兼容问题的简单方法 P6:怎样处理地线上的裂缝 P7:怎样降低时钟信号的辐射 P8:为什么IO接口的处理特别重要 P9…

使用NVIDIA TensorRT-LLM支持CodeFuse-CodeLlama-34B上的int4量化和推理优化实践

本文首发于 NVIDIA 一、概述 CodeFuse&#xff08;https://github.com/codefuse-ai&#xff09;是由蚂蚁集团开发的代码语言大模型&#xff0c;旨在支持整个软件开发生命周期&#xff0c;涵盖设计、需求、编码、测试、部署、运维等关键阶段。 为了在下游任务上获得更好的精…