「项目阅读系列」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_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 统一管理调度。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

qt-C++笔记之两个窗口ui的交互

qt-C笔记之两个窗口ui的交互 code review! 文章目录 qt-C笔记之两个窗口ui的交互0.运行1.文件结构2.先创建widget项目&#xff0c;搞一个窗口ui出来3.项目添加第二个widget窗口出来4.补充代码4.1.qt_widget_interaction.pro4.2.main.cpp4.3.widget.h4.4.widget.cpp4.5.second…

JAVA for 循环训练 Pattern

import java.util.Scanner;public class Pattern {public static void main(String[] args) {int[] arr {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};Scanner in new Scanner(System.in);System.out.print("请输入n:");int n in.nextInt();in.close();for …

LeetCode27.移除元素(暴力法、快慢指针法)

每日一题&#xff1a;LeetCode27.移除元素 1.问题描述2.解题思路3.代码 1.问题描述 问题描述&#xff1a;给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。不要使用额外的数组空间&#xff0c;你必…

Linux(Ubuntu)安装JDK环境

系统环境 Ubuntu20.04 下载JDK压缩包 前往Oracle官网进行后续下载或单击下载JDK压缩包 下拉找到JDK8&#xff0c;在Linux板块下选择适配系统架构的压缩包文件(后缀为tar.gz)&#xff0c;系统架构可通过uname -m命令查看 安装JDK 安装环境通常放在/usr/local下&#xff0c;进入…

免费稳定几乎无门槛,我的ChartGPT助手免费分享给你

公众号「架构成长指南」&#xff0c;专注于生产实践、云原生、分布式系统、大数据技术分享。 概述 ChatGPT想必大家应该都不陌生了&#xff0c;大部分人或多或少都接触了&#xff0c;好多应该都是通过openAi的官方进行使用的&#xff0c;这个门槛对大部分人有点高&#xff0c;…

公共字段自动填充-@TableField的fill实现(2)

TheadLocal 客户端发送的每次http请求&#xff0c;在服务端都会分配新的线程。因此登录检查过滤器、controller、元数据对象处理器属于一个线程。 TheadLocal是线程的局部变量&#xff1a; TheadLocal常用方法&#xff1a; 如何在元数据对象处理器中获取当前登录用户的id&…

开发知识点-uniapp微信小程序-开发指南

uniapp uni.chooseLocationgetCurrentPages美团外卖微信小程序开发uniapp-美团外卖微信小程序开发P1 成果展示P2外卖小程序后端&#xff0c;学习给小程序写http接口P3 主界面配置P4 首页组件拆分P13 外卖列表布局筛选组件商家 布局测试数据创建样式 请求商家外卖数据封装请求并…

酷柚易汛ERP - 序列号盘点操作指南

1、应用场景 将系统中开启序列号的商品数量与与实际存放的数量进行对比。 2、主要操作 2.1 录入序列号 打开【盘点】-【序列号盘点】&#xff0c;新增序列号盘点单&#xff0c;点击【SN】按钮&#xff0c;在弹框中输入序列号。 支持扫描枪录入序列号支持复制粘贴序列号录入…

JS特效:跟随鼠标移动的小飞机

前端网页中&#xff0c;用JS实现鼠标移动时&#xff0c;页面中的小飞机向着鼠标移动。 效果 源码 <!DOCTYPE html> <html><head><style>*{margin: 0;padding: 0;}body{height: 100vh;background: linear-gradient(200deg,#005bea,#00c6fb);}#plane{…

【WiFI问题自助】解决WiFi能连上但是没有网的问题

WiFi能连上但是没有网的问题 背景&#xff1a;wifi能连上&#xff0c;但是没有网 解决 遇事不决&#xff0c;先重启啊&#xff01;怎么重启&#xff1f;拔掉电源再插上&#xff01;拔掉网线再插上&#xff01; 直接ok了。 思考记录 今天WiFi又上不了网了&#xff0c;昨天报…

大数据Doris(二十六):数据导入(Routine Load)介绍

文章目录 数据导入(Routine Load)介绍 一、​​​​​​​适用场景

数据结构及八种常用数据结构简介

data-structure 数据结构是一种存在某种关系的元素的集合。“数据” 是指元素&#xff1b;“结构” 是指元素之间存在的关系&#xff0c;分为 “逻辑结构” 和 “物理结构&#xff08;又称存储结构&#xff09;”。 常用的数据结构有 数组&#xff08;array&#xff09;、栈&…

详细步骤记录:持续集成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…

关于卓越服务的调研报告

NetSuite知识会发起的本次调研从2023年11月2日开始&#xff0c;到11月12日结束。16日已向参与调研的朋友邮件回复&#xff0c;感谢您的付出&#xff01;今朝分享此报告&#xff0c;各位同学参考。 调研问题与反馈总结 问题1&#xff1a;您能想到哪些服务组织能够提供高满意度&…

[CUDA]去除Eigen库中的warning

一、问题提出 假如使用nvcc对cuda代码进行编译时&#xff0c;如果代码中使用了Eigen库&#xff08;头文件&#xff09;&#xff0c;编译时可能会显示很多warning information&#xff0c;如下图红框中所示&#xff1a; 这些warning信息虽然不会影响代码的实际运行&#xff0c;…

linux如何使用Xshell远程连接

目录 1、创建虚拟机&#xff1a; 2、使用命令查看网段信息 拓展1&#xff1a;&#xff08;若网卡上没有网段信息&#xff0c;可以使用任意两种方法&#xff09;&#xff1a; 准备工作&#xff1a; 1、点击左上角的编辑后再点击虚拟网络编辑器。 2、打开以后&#xff0c…

C++__string

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 一、string是什么&#xff1f; 二、string的构造函数 1.string&#xff08;&#xff09;&#xff1b; 2.string&#xff08;const char * s&#xff09;&…

PHP常用的数组函数

PHP是一种流行的服务器端脚本语言&#xff0c;广泛用于Web开发。数组是PHP中最重要且最常用的数据类型之一&#xff0c;它提供了许多强大的数组函数&#xff0c;用于在数组上执行各种操作。在本文中&#xff0c;我们将深入解析PHP中一些常用的数组函数&#xff0c;以便更好地理…

07.webpack的性能优化 -- 产出代码

目标&#xff1a; 体积更小合理分包&#xff0c;不重复加载速度更快&#xff0c;使用内存更小 实现功能 小图片的base64编码提取公共代码bundle加hashIngorePlugin懒加载使用CDN使用productionScope Hosting 1. 使用production module.exports smart(webpackCommonConf, …