从零开始撸,基于gin+gorm+grpc实现项目改造

拆箱gin框架

  • 参考文档
  • 参考文档
  • 创建项目gin_grpc
  • 目录下写一个入口函数main.go
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}
  • 开启gomod go mod init gin_grpc
  • 加载依赖 go mod tidy
  • 此时我们查看项目下多了一个go.mod文件
    从零开始撸,基于gin+gorm+grpc实现项目改造_第1张图片

目录的配置

  • 有了入口文件,我们再创建一个app目录,用来存放我们的项目代码
  • 在app目录下创建一个router目录存放路由,在app目录下创建一个controller存放控制器层的代码
  • 在app/router目录下创建api.go,用来注册api相关的路由
package router

import (
	"gin_grpc/app/controller"
	"github.com/gin-gonic/gin"
)

// UserRouter 用户模块的路由
func UserRouter(r *gin.Engine) {
	r.GET("/index", controller.User.Index)
}

// InitRouter 路由注册
func InitRouter(r *gin.Engine) {
	// 用户相关的路由注册
	UserRouter(r)
}
  • 在app/controller下创建user.go作为user的控制器
package controller

import (
	"github.com/gin-gonic/gin"
)

// Controller controller基类
type Controller struct{}

// UserController 继承controller的user
type UserController struct {
	Controller
}

// User UserController的变量
var User=UserController{
}

func (t *UserController)Index(c *gin.Context){
	 c.JSON(200, gin.H{
		"message": "pong",
	})
	 return
}


  • 修改一下入口文件main.go,一个是启动gin服务,一个是注册路由
package main
import (
	"gin_grpc/app/router"
	"github.com/gin-gonic/gin"
)
func main() {
	//r:=gin.New()//启动gin服务
	r := gin.Default()//Default returns an Engine instance with the Logger and Recovery middleware already attached.
	router.InitRouter(r)//注册路由
	r.Run("127.0.0.1:8089") // listen and serve on 0.0.0.0:8080
}

  • 至此,一个简易版本的gin部署完毕了。我们继续往下看

配置文件

  • 我们读取mysql,读取redis配置,肯定少不了配置文件

  • 这里,我们使用viper来读取配置文件

  • 相关参考文档

  • 安装vipergo get github.com/spf13/viper

  • 在项目根目录下创建config文件夹,在config下创建base.yaml,因为yaml文件相对来说比json更轻量级

#项目相关配置
app:
  name: gin_grpc
  debug: true
#数据库相关配置
database:
  driver: mysql
  host: 127.0.0.1
  port: 3306
  username: acurd_com
  dbname: cms
  password: acurd_com
#redis相关配置
redis:
  host: 127.0.0.1
  port: 6379
  • 在config目录下创建一个config.go,用来加载配置文件
package config

import (
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

// InitConfig 初始化配置文件
func InitConfig() {
	viper.AddConfigPath("./config")
	viper.AddConfigPath(".")//多路径查找
	viper.SetConfigName("base")
	viper.SetConfigType("yaml")
	if err := viper.ReadInConfig(); err != nil {
		panic(err)
	}
	//监控并重新读取配置文件
	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		// 配置文件发生变更之后会调用的回调函数
		fmt.Println("Config file changed:", e.Name)
	})
}
  • 在入口文增加一个init函数,完成对配置文件的初始化
package main
import (
	"gin_grpc/app/router"
	"gin_grpc/config"
	"github.com/gin-gonic/gin"
)
//初始化配置文件
func init() {
	config.InitConfig()
}
func main() {
	//r:=gin.New()//启动gin服务
	r := gin.Default()//Default returns an Engine instance with the Logger and Recovery middleware already attached.
	router.InitRouter(r)//注册路由
	r.Run("127.0.0.1:8089") // listen and serve on 0.0.0.0:8080
}

gorm的使用

  • 在config/config.go文件中写方法连接MySQL,这里使用gorm
  • 在app下创建model目录,在model目录下新建文件model.go
package model

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"github.com/spf13/viper"
	"log"
)

// Db 用来承接db变量
var Db *gorm.DB

// InitDb 初始化数据库连接
func InitDb()*gorm.DB{
	var (
	Username = viper.GetString("database.username")
	Password = viper.GetString("database.password")
	Host = viper.GetString("database.host")
	Port = viper.GetInt("database.port")
	DbName = viper.GetString("database.dbname")
	)
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", Username, Password, Host, Port, DbName)
	fmt.Println(Username)
	db, err := gorm.Open("mysql", dsn)
	if err != nil {
		log.Fatal("数据库连接失败,报错信息"+err.Error())
	}
	// 设置连接池,空闲连接
	db.DB().SetMaxIdleConns(50)
	// 打开链接
	db.DB().SetMaxOpenConns(100)
	// 表明禁用后缀加s
	db.SingularTable(true)
	// 启用Logger,显示详细日志
	db.LogMode(viper.GetBool("app.debug"))
	return db
}
  • 在model下继续创建文件blog,用于博文的model
package model

// Blog 定义博文结构体
type Blog struct {
	AutoID      uint   `gorm:"column:auto_id;primary_key;AUTO_INCREMENT"` // ID
	ID          string `gorm:"column:id;NOT NULL"`                        // 业务id
	Title       string `gorm:"column:title;NOT NULL"`                     // 标题
	TitleStyle  string `gorm:"column:title_style;NOT NULL"`               // 标题样式
	Thumb       string `gorm:"column:thumb;NOT NULL"`                     // 缩略图
	Keywords    string `gorm:"column:keywords;NOT NULL"`                  // 关键词
	Description string `gorm:"column:description;NOT NULL"`               // 描述
	Content     string `gorm:"column:content;NOT NULL"`                   // 内容
	CreateTime  uint   `gorm:"column:create_time;default:0;NOT NULL"`     // 创建时间
	UpdateTime  uint   `gorm:"column:update_time;default:0;NOT NULL"`     // 更新时间
	Author      string `gorm:"column:author;NOT NULL"`                    // 作者
	Source      string `gorm:"column:source;NOT NULL"`                    // 来源
	Summary     string `gorm:"column:summary;NOT NULL"`                   // 摘要
}

// TableName 标记Blog结构体使用的表名
func (m *Blog) TableName() string {
	return "cms_blog"
}

// BlogModel 用来继承model
type BlogModel struct{
}

// GetOne 获取一篇文章
func (m *BlogModel)GetOne(i int)Blog{
	var blog Blog
	Db.First(&blog,i)
	return blog
}

  • 增加一个路由,用来获取文章
// BlogRouter  博客相关路由
func BlogRouter(r *gin.Engine) {
	r.GET("/blog/:id", controller.Blog.GetOne)
}
  • 在入口文件中增加对MySQL的初始化
func main() {
	//r:=gin.New()//启动gin服务
	r := gin.Default()//Default returns an Engine instance with the Logger and Recovery middleware already attached.
	model.Db=model.InitDb()
	defer model.Db.Close()
	router.InitRouter(r)//注册路由
	r.Run("127.0.0.1:8089") // listen and serve on 0.0.0.0:8080
}
  • 访问路由
    从零开始撸,基于gin+gorm+grpc实现项目改造_第2张图片

使用grpc完成服务间的调用

grpc的入门的例子,我再之前的文章golang入门微服务
中详细的讲过,这里简单记录一下步骤

定义protobuf文件

//我们定义一个博文的数据结构,
syntax="proto3";
package blog;
option  go_package="/Users/zhangguofu/website/gin_grpc/app/proto/gen/go;blog";//生成的go文件存放目录在哪;包名叫什么
message Blog {
  uint32 auto_id = 1; // ID
  string id = 2; // 业务id
  string title = 4; // 标题
  string title_style = 5; // 标题样式
  string thumb = 6; // 缩略图
  string keywords = 7; // 关键词
  string description = 8; // 描述
  string content = 9; // 内容
  uint32 create_time = 14; // 创建时间
  uint32 update_time = 15; // 更新时间
  string author = 16; // 作者
  string source = 17; // 来源
  string summary = 18; // 摘要
}
// 定义blog服务
service BlogInfo{
  //定义方法
  rpc getBlog(Id) returns(Blog);
}
//定义id的消息类型
message Id{
  int64 value=1;
}
  • 在proto文件目录下执行命令 生成pb文件,其中,blog.pb.go中定义了结构体,字段映射等信息,blog_grpc.pb.go中定义了一些和rpc交互的一些方法,包括获取grpc客户端,和我们在protobuf文件中顶一顶

│ ├── proto
│ │ ├── blog.proto
│ │ └── gen
│ │ └── go
│ │ └── blog
│ │ ├── blog.pb.go
│ │ └── blog_grpc.pb.go

gprc服务端实现

  • 接下来我们改造一下,由原来的请求MySQL 改为请求grpc
func (t *BlogController) RpcGetOne(c *gin.Context) {
	id := c.Param("id")
	id_int, _ := strconv.Atoi(id)

	//监听端口
	conn, err := grpc.Dial("localhost:9988", grpc.WithInsecure())
	if err!=nil {
		log.Fatal(err)
	}
	defer conn.Close()
	//取到客户端连接
	client:=blog2.NewBlogInfoClient(conn)
	//组装结构体
	blogId:=new(blog2.Id)
	blogId.Value=int64(id_int)
	info, _ :=client.GetBlog(context.Background(),blogId)
	fmt.Println(id)
	c.JSON(200, gin.H{
		"message": "success",
		"data":    info,
	})
	return
}

gprc服务端实现

  • 接下来我们编写服务端的实现
package main

import (
	"context"
	"fmt"
	"github.com/blog-server/proto/gen/go/blog"
	"google.golang.org/grpc"
	"log"
	"net"
	"strconv"
)

type  Server struct{

}
/**
 * @Description
 * @Author 老a技术联盟
 * @Date 2023/2/6 11:48
 **/
func main() {
	listen, err := net.Listen("tcp", "localhost:9988")
	if err != nil {
		return
	}
	//创建grpc服务
	s:=grpc.NewServer()
	blog.RegisterBlogInfoServer(s,Server{})
	if err := s.Serve(listen); err != nil {
		log.Println("failed to serve...", err)
		return
	}
}

//继承接口
func (s Server)GetBlog(c context.Context,b *blog.Id) (*blog.Blog, error) {
	//从mysql中取数据的过程忽略,直接返回结果
	blog:=new(blog.Blog)
	blog.Id= strconv.Itoa(int(b.Value))
	fmt.Printf("接收到的id是:%d",b.Value)
	blog.Author="grpc返回"
	return blog,nil
}

重启服务

  • 先启动grpc服务端,在启动客户端
  • 然后请求获取一条博文

从零开始撸,基于gin+gorm+grpc实现项目改造_第3张图片

  • 服务端信息打印
  • 从零开始撸,基于gin+gorm+grpc实现项目改造_第4张图片
  • 其中 pb文件在项目中往往是作为一个包文件被引用的,这样就不会重复拷贝文件到目录

你可能感兴趣的:(golang,linux技术,gin,json,github)