「项目阅读系列」go-gin-example star 6.5k!(1)

文章目录

  • 准备工作
    • 适宜人群
    • 项目信息
  • 项目结构
  • 代码阅读
    • 主要模块代码
      • 主函数模块
      • router 路由模块
      • auth 授权模块
      • 数据库
    • 修改文章请求分析
    • 其他依赖
  • 总结

准备工作

适宜人群

初学 go 语法,希望了解 go 项目的构建过程和方式。

项目信息

go-gin-example 项目是使用 gin 框架构建一个简易的 blog 服务,包括对 blog 的增删改查操作以及 blog tag 的增删改查等。

  • 代码仓库:https://github.com/eddycjy/go-gin-example
  • 版本:565e1a9395471e829abdb2201e00321c327626cd 第一次提交版本

项目结构

项目代码结构如下

  • conf 配置相关
  • middleware 中间件
  • models 数据库相关对象以及操作
  • pkg 项目相关的模块包
  • routers 路由相关
  • main 主函数
    在这里插入图片描述

代码阅读

主要模块代码

首先看一下整体项目中比较重要的模块,包括 主函数、路由模块、授权模块、数据库模块。

主函数模块

func main() {router := routers.InitRouter()s := &http.Server{Addr:           fmt.Sprintf(":%d", setting.HTTPPort),Handler:        router,ReadTimeout:    setting.ReadTimeout,WriteTimeout:   setting.WriteTimeout,MaxHeaderBytes: 1 << 20,}s.ListenAndServe()
}

主函数工作

  • 初始化路由
  • server 配置
  • 启动

router 路由模块

func InitRouter() *gin.Engine {r := gin.New()r.Use(gin.Logger())r.Use(gin.Recovery())gin.SetMode(setting.RunMode)r.GET("/auth", api.GetAuth)apiv1 := r.Group("/api/v1")apiv1.Use(jwt.JWT()){//获取标签列表apiv1.GET("/tags", v1.GetTags)//新建标签apiv1.POST("/tags", v1.AddTag)//更新指定标签apiv1.PUT("/tags/:id", v1.EditTag)//删除指定标签apiv1.DELETE("/tags/:id", v1.DeleteTag)//获取文章列表apiv1.GET("/articles", v1.GetArticles)//获取指定文章apiv1.GET("/articles/:id", v1.GetArticle)//新建文章apiv1.POST("/articles", v1.AddArticle)//更新指定文章apiv1.PUT("/articles/:id", v1.EditArticle)//删除指定文章apiv1.DELETE("/articles/:id", v1.DeleteArticle)}return r
}

主要工作

  • 创建 gin 对象
  • 添加 Logger、Recovery 中间件
    • Logger 日志处理
    • Recovery 异常捕获
  • 设置 /auth 路径的处理器
  • 设置 /api/v1 group,通过 JWT 进行授权验证
  • 设置 /api/v1 下各个请求的处理方式

auth 授权模块

type auth struct {Username string `valid:"Required; MaxSize(50)"`Password string `valid:"Required; MaxSize(50)"`
}func GetAuth(c *gin.Context) {username := c.Query("username")password := c.Query("password")valid := validation.Validation{}a := auth{Username: username, Password: password}ok, _ := valid.Valid(&a)data := make(map[string]interface{})code := e.INVALID_PARAMSif ok {isExist := models.CheckAuth(username, password)if isExist {token, err := util.GenerateToken(username, password)if err != nil {code = e.ERROR_AUTH_TOKEN} else {data["token"] = tokencode = e.SUCCESS}} else {code = e.ERROR_AUTH}} else {for _, err := range valid.Errors {logging.Info(err.Key, err.Message)}}c.JSON(http.StatusOK, gin.H{"code" : code,"msg" : e.GetMsg(code),"data" : data,})
}

工作:

  • 获取用户 username password
  • 验证 username password 是否符合格式
    • 符合,通过 models 进行授权检查,组装 data
    • 不符合,打印错误日志
  • 使用 JSON 格式返回

数据库

数据库信息的设置在 models 文件夹下,主要包括 model 文件以及其他具体表文件。

  • model.go 模块通用参数、全局数据库对象、初始化方法&&数据库关闭方法
  • article.go article 对象及其操作方法

重点看一下 model 文件。

var db *gorm.DBtype Model struct {ID int `gorm:"primary_key" json:"id"`CreatedOn int `json:"created_on"`ModifiedOn int `json:"modified_on"`
}func init() {var (err errordbType, dbName, user, password, host, tablePrefix string)sec, err := setting.Cfg.GetSection("database")if err != nil {log.Fatal(2, "Fail to get section 'database': %v", err)}dbType = sec.Key("TYPE").String()dbName = sec.Key("NAME").String()user = sec.Key("USER").String()password = sec.Key("PASSWORD").String()host = sec.Key("HOST").String()tablePrefix = sec.Key("TABLE_PREFIX").String()db, err = gorm.Open(dbType, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", user, password, host, dbName))if err != nil {log.Println(err)}gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {return tablePrefix + defaultTableName;}db.SingularTable(true)db.DB().SetMaxIdleConns(10)db.DB().SetMaxOpenConns(100)
}func CloseDB() {defer db.Close()
}

组成部分

  • 全局变量 db,用于操作数据库
  • Model 结构体,各个数据库表的通用字段
  • init 方法 && closeDB 方法:数据库初始化以及关闭方法

从中可知,db 主要是通过使用 gorm 框架进行操作,操作中需要设置数据库相关参数以及最大连接数。

models 下其他具体的数据库表信息在此不做赘述,基本就是数据库表的 struct 定义以及相应操作,下面贴出该项目涉及的三个数据库表及其字段。

type Article struct {ModelTagID int `json:"tag_id" gorm:"index"`Tag   Tag `json:"tag"`Title string `json:"title"`Desc string `json:"title"`Content string `json:"content"`CreatedBy string `json:"created_by"`ModifiedBy string `json:"modified_by"`State int `json:"state"`
}type Tag struct {ModelName string `json:"name"`CreatedBy string `json:"created_by"`ModifiedBy string `json:"modified_by"`State int `json:"state"`
}type Auth struct {ID int `gorm:"primary_key" json:"id"`Username string `json:"username"`Password string `json:"password"`
}

修改文章请求分析

主要模块已经了解后,我们查看修改文章 api 的请求过程和具体实现。

  • 请求 URL:/articles/:id
  • 请求方法 :PUT
  • 处理函数: v1.EditArticle

上述主要代码模块,已经了解到 main 函数启动后,会初始化路由,路由中包含了「修改文章」请求的具体处理函数,这里看看具体函数操作。

func EditArticle(c *gin.Context) {valid := validation.Validation{}id, _ := com.StrTo(c.Param("id")).Int()tagId, _ := com.StrTo(c.Query("tag_id")).Int()title := c.Query("title")desc := c.Query("desc")content := c.Query("content")modifiedBy := c.Query("modified_by")var state int = -1if arg := c.Query("state"); arg != "" {state, _ = com.StrTo(arg).Int()valid.Range(state, 0, 1, "state").Message("状态只允许0或1")}valid.Min(id, 1, "id").Message("ID必须大于0")valid.MaxSize(title, 100, "title").Message("标题最长为100字符")valid.MaxSize(desc, 255, "desc").Message("简述最长为255字符")valid.MaxSize(content, 65535, "content").Message("内容最长为65535字符")valid.Required(modifiedBy, "modified_by").Message("修改人不能为空")valid.MaxSize(modifiedBy, 100, "modified_by").Message("修改人最长为100字符")code := e.INVALID_PARAMSif ! valid.HasErrors() {if models.ExistArticleByID(id) {if models.ExistTagByID(tagId) {data := make(map[string]interface {})if tagId > 0 {data["tag_id"] = tagId}if title != "" {data["title"] = title}if desc != "" {data["desc"] = desc}if content != "" {data["content"] = content}data["modified_by"] = modifiedBymodels.EditArticle(id, data)code = e.SUCCESS} else {code = e.ERROR_NOT_EXIST_TAG}} else {code = e.ERROR_NOT_EXIST_ARTICLE}} else {for _, err := range valid.Errors {logging.Info(err.Key, err.Message)}}c.JSON(http.StatusOK, gin.H{"code" : code,"msg" : e.GetMsg(code),"data" : make(map[string]string),})
}// models article.go
func EditArticle(id int, data interface {}) bool {db.Model(&Article{}).Where("id = ?", id).Updates(data)return true
}

工作:

  • 参数获取
  • 设置校验规则并进行校验
    • 校验成功
      • 文章是否已经存在
        • 是,构建 data 参数,通过 models EditAriticle 修改,具体修改逻辑:找到主键等于 id 的数据,并通过 update 进行更新。
        • 否,设置文章不存在错误码
    • 校验失败
      • 打印错误日志
    • JSON 响应

其他依赖

  • go-ini 库
    • 该项目通过 ini 文件进行配置管理,go-ini 是 Go 语言中用于操作 ini 文件的第三方库。
  • beego
    • 另一个 go web 框架,项目中主要使用了 beego 的vaild 功能
  • gorm
    • go ORM 框架
  • go-vendor
    • 该项目第一次提交为 18 年,通过 vendor 来管理依赖,现在 go mod 诞生后,这种方式已被放弃

总结

该项目第一版提交,大体上完成了 blog 项目所需的基本功能,在目录结构上也相对清晰。

不足:

  1. 返回信息的 Code、Msg 对象设计比较一般。每次响应需要自己构建响应格式{code;msg;data}。
  2. 配置管理相对粗糙,直接读取配置文件而不是通过 global 统一管理调度。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

integrin + Receptor ; platelet ; Ig-CAM

platelet HPA ; integrin ; Cell adhesion in cancer: Beyond the migration of single cells

ROS参数服务器(Param):通信模型、Hello World与拓展

参数服务器在ROS中主要用于实现不同节点之间的数据共享。 参数服务器相当于是独立于所有节点的一个公共容器&#xff0c;可以将数据存储在该容器中&#xff0c;被不同的节点调用&#xff0c;当然不同的节点也可以往其中存储数据。 使用场景一般存储一些机器人的固有参数&…

大反转!OpenAI董事会辞职,求奥特曼重返OpenAI?「奥特曼24小时流放」大揭秘...

大家好&#xff0c;我是二狗。 想必大家昨天都被Sam Altman被董事会解雇的事情刷屏了。 然而才仅仅过去一天&#xff0c;OpenAI 董事会就反悔了&#xff01;正和Sam Altman 商量让他重返CEO职位。 这一反转和Altman被炒鱿鱼一样突然&#xff0c;凄凄惨惨真真假假真真&#x…

通过汇编理解cortex-m3:第0章

第0章&#xff1a;准备工作 基本想法&#xff1a;利用汇编和gdb调试&#xff0c;来学习cortex-m3汇编指令&#xff0c;以及一些寄存器的功能。 软件和硬件&#xff1a; 硬件&#xff1a;韦东山瑞士军刀中的最小核心板&#xff08;STM32F103C8T6&#xff09; STLINK-V2&#…

Linux线程编程

Linux线程编程初步 一些历史背景 Linux间接起源于Unix&#xff0c;而Linux诞生时并不存在 "线程"的概念。在20世纪90年代线程才流行起来&#xff0c;POSIX Thread标准于 1995年确立。Unix中引入 Thread 之后&#xff0c;大量函数被重写&#xff0c;信号机制也变得复…

Mybatis-Plus《学习笔记 22版尚硅谷 》——感谢【尚硅谷】官方文档

Mybatis-Plus《学习笔记 22版尚硅谷 》 一、MyBatis-Plus1.简介2.特性3.支持数据库4.框架结构5.官方地址 二、入门案例1.开发环境2.建库建表3.创建工程4.配置编码5.测试查询 三、增删改查1.BaseMapper<T>2.调用Mapper层实现CRUD2.1 插入2.2 删除a、根据ID删除数据b、根据…

OpenAI 董事会与 Sam Altman 讨论重返 CEO 岗位事宜

The Verge 援引多位知情人士消息称&#xff0c;OpenAI 董事会正在与 Sam Altman 讨论他重新担任首席执行官的可能性。 有一位知情人士表示&#xff0c;Altman 对于回归公司一事的态度暧昧&#xff0c;尤其是在他没有任何提前通知的情况下被解雇后。他希望对公司的治理模式进行重…

矩阵代数概论

矩阵代数 共轭转置 对于矩阵 A [ a i j ] A[a_{ij}] A[aij​]&#xff0c;共轭矩阵被定义为 A ‾ [ a ‾ i j ] \overline{A}[\overline{a}_{ij}] A[aij​]&#xff0c;所以 A A A的共轭转置 A ‾ T A T ‾ \overline{A}^T\overline{A^T} ATAT&#xff0c;其中 A ‾ T \ov…

基于PHP+MySql的酒店信息管理系统的设计与实现

一、系统开发环境 运行环境&#xff1a;phpstudy或者wampserver&#xff0c; 开发工具&#xff1a;vscodephpstorm 数据库&#xff1a;mysql 二、酒店管理系统功能 1.前台功能&#xff1a; 首页客房推荐&#xff0c;周边特色介绍 酒店在线预订 订单查询&#xff0c;可以…

腾讯云轻量数据库1核1G评测和租用价格表

腾讯云轻量数据库测评&#xff0c;轻量数据库100%兼容MySQL 5.7和8.0&#xff0c;腾讯云提供1C1G20GB、1C1G40GB、1C2G80GB、2C4G120GB、2C8G240GB五种规格轻量数据库&#xff0c;阿腾云atengyun.com分享腾讯云轻量数据库测评、轻量数据库详细介绍、特性、配置价格和常见问题解…

Spring Framework IOC依赖查找 - 按名称查找解析

IoC按名称查找共分为三类&#xff1a; 按名称按类型按集合 按名称查找 在Spring Framework中&#xff0c;实时加载和延迟加载是指在容器启动时是否立即实例化bean的不同策略。下面我们将分别介绍这两种加载方式及其应用场景。 tips: 当涉及到懒加载和延时加载时&#xff0…

卡尔曼滤波器在车流量检测中的应用

目录 1. 作者介绍2. 卡尔曼滤波器2.1 卡尔曼滤波概述2.2 标志性发展2.3 卡尔曼公式理解 3. 车流量检测3.1 背景介绍3.2 实现过程3.2.1 YOLOv3网络模型结构3.2.2 SORT算法3.2.3 基于虚拟线圈法的车辆统计 4. 算法实现4.1 Kalman.py4.2 完整代码4.3 结果展示 1. 作者介绍 吴思雨…