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

news2024/11/18 11:26:19

文章目录

  • 准备工作
    • 适宜人群
    • 项目信息
  • 项目结构
  • 代码阅读
    • 主要模块代码
      • 主函数模块
      • 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_PARAMS
	if 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"] = token
				
				code = 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.DB

type Model struct {
	ID int `gorm:"primary_key" json:"id"`
	CreatedOn int `json:"created_on"`
	ModifiedOn int `json:"modified_on"`
}

func init() {
	var (
		err error
		dbType, 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 {
	Model

	TagID 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 {
	Model

	Name 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 = -1
	if 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_PARAMS
  	if ! 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"] = modifiedBy

	  			models.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.coloradmin.cn/o/1226355.html

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

相关文章

动态规划解背包问题

题目 题解 def knapsac(W: int, N: int, wt: List[int], val: List[int]) -> int:# 定义状态动作价值函数: dp[i][j]&#xff0c;对于前i个物品&#xff0c;当前背包容量为j&#xff0c;最大的可装载价值dp [[0 for j in range(W1)] for i in range(N1)]# 状态动作转移for…

STM32CubeMX学习笔记(2)--DSP库的使用

1.DSP库简介 STM32的DSP库是为了支持数字信号处理应用而设计的&#xff0c;它包含了一系列优化的数学函数和算法&#xff0c;能够在STM32微控制器上高效地执行数字信号处理任务。 DSP库通常包括以下主要特性&#xff1a; 1.数学函数库&#xff1a; 包括各种基本的数学运算函数…

第80篇:Weblogic上传漏洞在不知绝对路径情况下拿shell方法

Part1 前言 大家好&#xff0c;我是ABC_123。Weblogic曾经爆出一个上传漏洞&#xff0c;漏洞编号是CVE-2018-2894&#xff0c;这个漏洞利用起来稍微有点麻烦&#xff0c;很多朋友由于不知道绝对路径而没法上传shell&#xff0c;从而放弃对其的进一步利用&#xff0c;ABC_123曾…

【网络奇遇记】那年我与计算机网络的浅相知

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. 计算机网络的定义1.1 计算机早期的一个最简单的定义1.2 现阶段计算机网络的一个较好的定义 二. …

网络运维与网络安全 学习笔记2023.11.19

网络运维与网络安全 学习笔记 第二十天 今日目标 STP工作原理、STP高级配置、MSTP工作原理 MSTP配置案例、MSTP负载均衡 STP工作原理 单点故障 PC之间的互通链路仅仅存在1个 任何一个设备或链路出现问题&#xff0c;PC之间都会无法通信 解决方案 增加冗余/备份设备 增加冗…

4.5每日一题(幂指函数(复合函数)求导)

方法一 &#xff1a;把幂指函数用e改写 方法二&#xff1a;用对数改写

宏--offsetof使用

文章目录 宏介绍结构体测试代码运行结果 宏介绍 宏--offsetof(type, member)&#xff0c;type就是结构的类型&#xff0c;member就是需要的成员名。表达式的结果是一个size_t的值&#xff0c;表示这个指定成员开始存储的位置距离结构开始存储的位置偏移几个字节结构体 typede…

Canal+Kafka实现MySQL与Redis数据同步(二)

CanalKafka实现MySQL与Redis数据同步&#xff08;二&#xff09; 创建MQ消费者进行同步 在application.yml配置文件加上kafka的配置信息&#xff1a; spring:kafka:# Kafka服务地址bootstrap-servers: 127.0.0.1:9092consumer:# 指定一个默认的组名group-id: consumer-group…

JavaScript实现飞机发射子弹详解(内含源码)

JavaScript实现飞机发射子弹 前言实现过程源码展示源码讲解HTML结构CSS结构js结构 前言 文本主要讲解如何利用JavaScript实现飞机发射子弹&#xff0c;实现过程以及源码讲解。实现效果图如下&#xff1a; 实现过程 首先&#xff0c;找到飞机和子弹的UI图&#xff0c;gif图最…

C++虚函数(定义,作用,原理,案例)

一.定义&#xff1a; C的虚函数是在父类(基类)中声明的的函数&#xff0c;它可在子类(派生类)中重写。二.作用 虚函数的目的是实现多态性&#xff0c;即在程序运行时根据对象的实际类型确定调用哪个函数。三.使用方法&#xff1a; 在基类中声明虚函数时&#xff0c;需要在函…

全链路监控--pinpoint

一、pinpoint架构原理 架构组成 Pinpoint Agent:和自己运行的应用关联起来的探针 Pinpoint Collector:收集各种性能数据 Pinpoint-Web: 将收集到的数据显成为 WEB网页显示 HBase Storage: 存储收集到的数据 工作原理 pinpoint的核心思想是在各个服务节点之间彼此调用时&a…

【自然语言处理】【大模型】赋予大模型使用工具的能力:Toolformer与ART

赋予大模型使用工具的能力&#xff1a;Toolformer与ART ​ 本文介绍两种赋予大模型使用外部工具能力的方法&#xff1a;Toolformer和ART。 Toolformer论文地址&#xff1a;https://arxiv.org/pdf/2302.04761.pdf ART论文地址&#xff1a;https://arxiv.org/pdf/2303.09014.pd…

《视觉SLAM十四讲》-- 回环检测

文章目录 10 回环检测10.1 概述10.1.1 回环检测的意义10.1.2 回环检测的方法10.1.3 准确率和召回率 10.2 词袋模型10.3 字典10.3.1 字典的结构10.3.2 实践&#xff1a;创建字典 10.4 相似度计算10.4.1 理论部分10.4.2 实践&#xff1a;相似度的计算 10.5 实验分析与评述 10 回环…

Java(二)(String的常见方法,ArrayList的常见方法)

String 创建string对象 package Helloworld;public class dome1 {public static void main(String[] args) {// 1.直接双引号得到字符串对象,封装字符串对象String name "lihao";System.out.println(name);// 2. new String 创建字符串对象,并调用构造器初始化字符…

html综合笔记:设计实验室主页

&#xff11; 主页来源及效果 Overview - Lab Website Template docs (gitbook.io) greenelab/lab-website-template: An easy-to-use, flexible website template for labs (github.com) 2 创建网页 3 主要的一些file 3.1 index.md 主页面 3.1.1 intro 3.1.2 highlight …

庖丁解牛:NIO核心概念与机制详解 02 _ 缓冲区的细节实现

文章目录 PreOverview状态变量概述Position 访问方法 Pre 庖丁解牛&#xff1a;NIO核心概念与机制详解 01 接下来我们来看下缓冲区内部细节 Overview 接下来将介绍 NIO 中两个重要的缓冲区组件&#xff1a;状态变量和访问方法 (accessor) 状态变量是"内部统计机制&quo…

【汇编】处理字符问题

文章目录 前言一、处理字符问题1.1 汇编语言如何处理字符1.2 asciiascii码是什么&#xff1f;ascii码表是什么&#xff1f; 1.3 汇编语言字符示例代码 二、大小写转换2.1 问题&#xff1a;对datasg中的字符串2.2 逻辑与和逻辑或2.3 程序&#xff1a;解决大小写转换的问题一个新…

我终于体会到了:代码竟然不可以运行,为什么呢?代码竟然可以运行,为什么呢?

废话不多说&#xff0c;直接上图 初看只当是段子&#xff0c;再看已是段中人 事情经过&#xff1a; 我在写动态顺序表的尾插函数时&#xff0c;写出了如下代码&#xff0c;可以跑&#xff0c;但是这段代码有一个bug暂时先不提 //动态顺序表的尾插 void SLPushBack(SL* psl, …

python中列表的基础解释

列表&#xff1a; 一种可以存放多种类型数据的数据结构 列表的创建&#xff1a; 1.用【】创建列表 #创建一个空列表 list1[] #创建一个非空列表 list2 [zhang,li,ying,1,2,3] #输出内容及类型 print(list1,type(list1)) print(list2,type(list2))结果&#xff1a; 2.使用list…

详细步骤记录:持续集成Jenkins自动化部署一个Maven项目

Jenkins自动化部署 提示&#xff1a;本教程基于CentOS Linux 7系统下进行 Jenkins的安装 1. 下载安装jdk11 官网下载地址&#xff1a;https://www.oracle.com/cn/java/technologies/javase/jdk11-archive-downloads.html 本文档教程选择的是jdk-11.0.20_linux-x64_bin.tar.g…