Go 语言gin框架的web

节省时间与精力,更高效地打造稳定可靠的Web项目:基于Go语言和Gin框架的完善Web项目骨架。无需从零开始,直接利用这个骨架,快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势,轻松处理高并发、大流量的请求。构建可扩展性强、易于维护的代码架构,保证项目的长期稳定运行。同时,通过集成常用功能模块和最佳实践,减少繁琐的开发工作,使您专注于业务逻辑的实现。

该骨架每个组件之间可单独使用,组件之间松耦合,高内聚,组件的实现基于其他三方依赖包的封装。
目前该骨架实现了大多数的组件,比如事件,中间件,日志,配置,参数验证,命令行,定时任务等功能,目前可以满足大多数开发需求,后续会持续维护更新功能。

青丝南开:https://www.cnblogs.com/smallKilts/p/17726021.html

github地址:https://github.com/czx-lab/skeleton
gitee地址:https://gitee.com/neddie/skeleton

设置环境变量并下载项目依赖
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go mod download
运行项目
go run ./cmd/main.go
项目编译打包运行
go build ./cmd/main.go// 编译
make build// 运行
make run// 编译与运行
make// 运行项目
./main
项目目录结构说明
├─app
│  ├─command ---> 命令行
│  ├─controller
│  │    └─base.go ---> BaseController,主要定义了request参数验证器validator
│  ├─event
│  │  ├─entity ---> 事件实体目录
│  │  ├─listen ---> 事件监听执行脚本目录
│  │  └─event.go ---> 事件注册代码
│  │       
│  ├─middleware ---> 中间件代码目录
│  ├─request ---> 请求参数校验代码目录
│  │   └─request.go ---> 参数验证器
│  └─task ---> 定时任务代码目录
│     └─task.go ---> 注册定时任务脚本
├─cmd ---> 项目入口目录
│  └─cli ---> 项目命令行模式入口目录
├─config
│  └─config.yaml ---> 配置文件
├─internal ---> 包含第三方包的封装
├─router ---> 路由目录
│  └─router.go
├─storage ---> 日志、资源存储目录
│  └─logs
└─test ---> 单元测试目录

骨架全局变量

该骨架中全局变量如下,可直接查看internal/variable.go文件。

var (// 项目更目录BasePath string// Log日志Log      *zap.Logger// 配置,Viper封装Config   *config.Config// 数据库GormDB       *gorm.DBMongoDB  *mongo.MongoDBRedis    *redis.ClientCrontab  *crontab.Crontab// RabbitMQAmqp     mq.RabbitMQInterface// rocketmq, 目前官方RocketMQ Golang SDK一些功能尚未完善,暂时不可用MQ       mq.Interface// 事件Event    *event.Event
)

基础功能


路由

该骨架的web框架是gin,所以路由定义可直接阅读Gin框架的文档。

在该骨架中定义注册路由需要在router文件夹下面的router.go文件中的func (*AppRouter) Add(server *gin.Engine)方法定义注册:

server.GET("/foo", func(ctx *gin.Context) {ctx.String(http.StatusOK, "hello word!")
})

也可以通过自己定义路由的定义注册,只需要实现github.com/czx-lab/skeleton/internal/server/router下面的Interface接口。如下示例:
在router目录下定义了一个CustomRouter结构体,该结构体实现了Interface接口

package routerimport ("net/http""skeleton/internal/server""github.com/gin-gonic/gin"
)type CustomRouter struct {server server.HttpServer
}func NewCustom(srv server.HttpServer) *CustomRouter {return &CustomRouter{srv,}
}func (*CustomRouter) Add(srv *gin.Engine) {srv.GET("/custom", func(ctx *gin.Context) {ctx.String(http.StatusOK, "custom router")})
}

需要注意的是,如果是自定义路由注册,需要修改项目cmd文件夹下面的main.go入口文件,通过http.SetRouters(router.NewCustom(http))注册给gin

中间件

定义中间件与gin框架一样,该估计默认实现了panic异常的中间件,可以查看internal/server/middleware文件夹中的exception.go文件。

如果需要定义其他的中间件并加载注册,可以将定义好的中间件通过server.HttpServer接口的SetMiddleware(middlewares ...middleware.Interface)方法注册加载,
比如我们实现如下自定义全局中间件middleware/custom.go

type Custom struct{}func (c *Custom) Handle() gin.HandlerFunc {return func(ctx *gin.Context) {fmt.Println("Custom middleware exec...")}
}

然后在定义路由的地方使用server.SetMiddleware(&middleware.Custom{})注册中间件。
定义全局路由中间件可以参考router/router.go中的New方法。

如果是局部中间件,可以直接在具体的路由上注册,参考gin路由中间件的用法

日志

在该骨架中的日志是直接对go.uber.org/zap的封装,使用时,直接通过全局变量variable.Log访问写入日志,可直接使用zap支持的所有方法。

package demo
import "skeleton/internal/variable"
func Demo() {variable.Log.Info("info message")
}

日志文件默认是以json格式写入到storage/logs/system.log里面

配置

配置项的定义直接在config/config.yaml文件中定义,并且配置的读取写入是通过封装github.com/spf13/viper实现,在该骨架中,只提供了如下一些获取配置的方法:

type ConfigInterface interface {Get(key string) anyGetString(key string) stringGetBool(key string) boolGetInt(key string) intGetInt32(key string) int32GetInt64(key string) int64GetFloat64(key string) float64GetDuration(key string) time.DurationGetStringSlice(key string) []string
}

需要注意的是,骨架中对配置项的获取做了缓存的处理,第一次加载是在文件中获取,后面每次回去都是在cache中获取,目前cache默认只支持memory,骨架中也支持自定义cache的方法,只需要实现config.CacheInterface接口就可以,比如需要使用redis作为配置缓存,可以通过下面的方式处理:

type ConfigRedisCache struct {}var _ config.CacheInterface = (*ConfigRedisCache)(nil)func (c *ConfigRedisCache) Get(key string) any {return nil
}func (c *ConfigRedisCache) Set(key string, value any) bool {return true
}func (c *ConfigRedisCache) Has(key string) bool {return true
}func (c *ConfigRedisCache) FuzzyDelete(key string) {}

然后将ConfigRedisCache结构体配置到config.Options中,如下所示,修改internal/bootstrap/init.go初始化配置的方法:

variable.Config, err := config.New(driver.New(), config.Options{BasePath: './',Cache: &ConfigRedisCache{}
})

config.yaml基础配置如下:

# http配置
HttpServer:Port: ":8888"# 服务模式,和gin的gin.SetMode的值是一样的Mode: "debug"
# socket配置
Websocket:WriteReadBufferSize: 2048HeartbeatFailMaxTimes: 4PingPeriod: 20ReadDeadline: 100WriteDeadline: 35PingMsg: "ping"# 数据库配置
Database:# 可以查看GORM相关的配置选项Mysql:SlowThreshold: 5LogLevel: 4ConnMaxLifetime: 1MaxIdleConn: 2MaxOpenConn: 2ConnMaxIdleTime: 12Reade:- "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"Write: "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"# mongo数据库的基础配置Mongo:Enable: falseUri:MinPoolSize: 10MaxPoolSize: 20Redis:Disabled: falseAddr: "192.168.1.4:6379"Pwd: ""Db: 0PoolSize: 20MaxIdleConn: 30MinIdleConn: 10# 单位(秒)MaxLifeTime: 60# 单位(分)MaxIdleTime: 30# 定时任务
Crontab:Enable: true# 消息队列,使用rocketmq
MQ:Enable: trueServers:- "127.0.0.1:9876"Retries: 1ProducerGroupName: "ProducerGroup"ConsumerGroupName: "ConsumerGroup"# RabbitMQ
Amqp:Enable: trueAddr: "amqp://guest:guest@127.0.0.1:5672/"  
事件机制
  • 定义事件实体

    app/event/entity目录下定义一个事件实体,该实体实现了event.EventInterface接口:

    package entitytype DemoEvent struct {}func (d *DemoEvent) EventName() string {return "demo-event"
    }func (d *DemoEvent) GetData() any {return "demo param"
    }
    
  • 定义事件监听

    app/event/listen目录中定义一个DemoEventListen事件监听,并且该DemoEventListen结构体必须要实现event.Interface接口:

    package listenimport ("fmt"event2 "skeleton/app/event/entity""skeleton/internal/event"
    )type DemoEventListen struct {
    }func (*DemoEventListen) Listen() event.EventInterface {return &event2.DemoEvent{}
    }func (*DemoEventListen) Process(data any) (any, error) {return fmt.Sprintf("%v --> %s", data, "exec DemoEventListen.Process"), nil
    }
  • 最后需要将事件进行注册,在app/event/event.go文件中的Init方法内执行:

    variable.Event.Register(&listen.DemoEventListen{})
    
  • 调用事件执行

    variable.Event.Dispatch(&entity.DemoEvent{})
    
验证器

gin框架本身内置了validator校验,骨架里面只是对其参数的校验做了统一的校验入口。

通过如下方式获取进行参数的校验,并设置中文错误提示:

type Param struct {Name  int    `binding:"required" form:"name" query:"name" json:"name"`
}
appRequest, err := AppRequest.New("zh")
if err != nil {return
}
var data Param
errMap := appRequest.Validator(ctx, &data)
fmt.Println(errMap)

骨架里面已经实现了默认的参数校验,可以在app/request/request.go文件中查看。并且在controller目录中base.go有一个Validate(ctx *gin.Context, param any)方法,在其他controller中要进行参数校验的时候,只需要继承base结构体,然后调用Validate方法。

package controllerimport "github.com/gin-gonic/gin"type DemoController struct {base
}type DemoRequest struct {Id int `binding:"required" form:"id" query:"id" json:"id"`
}func (d *DemoController) Index(ctx *gin.Context) {var param DemoRequestif err := d.base.Validate(ctx, &param); err == nil {ctx.JSON(http.StatusOK, gin.H{"data": param})} else {ctx.JSON(http.StatusBadRequest, gin.H{"message": err})}
}

验证规格参考github.com/go-playground/validator官方文档

命令行

基于github.com/spf13/cobra封装

  • 定义命令

    app/command目录中定义自己的命令,比如自定义一个输出success ok的命令

    package commandimport ("fmt""github.com/spf13/cobra"
    )type FooCommand struct {}func (f *FooCommand) Command() *cobra.Command {return &cobra.Command{Use:   "foo",Short: "命令使用简介.",Long: `命令介绍.`,Run: func(cmd *cobra.Command, args []string) {str, _ := cmd.Flags().GetString("name")fmt.Printf("success, %s", str)},}
    }func (f *FooCommand) Flags(root *cobra.Command) {root.PersistentFlags().String("name", "", "命令参数")
    }
    
  • 注册命令

    需要在cmd/cli/cli.go中的main方法内注册自定义命令。

  • 执行命令

    go run cmd/cli/cli.go foo --name ok
    
  • 查看命令信息

    go run cmd/cli/cli.go help// 或者
    go run cmd/cli/cli.go foo --help
    
定时任务

定时是通过封装github.com/robfig/cron/v3实现

  • 定义定时任务方法

    app/task目录下定义执行方法,比如每一分钟打印success字符

    package taskimport "fmt"type SuccessTask struct {
    }// 时间规则
    func (s *SuccessTask) Rule() string {return "* * * * *"
    }func (s *SuccessTask) Execute() func() {return func() {fmt.Println("success")}
    }
    
  • 加载定时任务

    需要在app/task/task.go文件中的Tasks方法内,加载自定义的任务,参考task目录下的task.go文件

Websocket
  • 消息处理与链接关闭监听

    该骨架中的websocket是对github.com/gorilla/websocket依赖库的封装,在编写通信功能时,只需要实现websocket.MessageHandler接口:

    import 	(AppSocket "skeleton/internal/server/websocket"
    )type socketHandler struct {}// 消息处理
    func (s *socketHandler) OnMessage(message AppSocket.Message) {fmt.Println(fmt.Sprintf("mt: %v,data: %s, uuids: %v", message.MessageType, message.Data, message.Subkeys))
    }func (s *socketHandler) OnError(key string, err error) {fmt.Printf("socket err: %s, client: %s", err, key)
    }func (s *socketHandler) OnClose(key string) {fmt.Printf("socket closed. client:%s", key)
    }
    
  • 创建链接Websocket

    import 	("github.com/google/uuid""github.com/gin-gonic/gin"AppSocket "skeleton/internal/server/websocket"
    )var client AppSocket.SocketClientInterfacefunc init() {client, _ = AppSocket.NewSocket(AppSocket.WithHandler(&socketHandler{}))
    }func Connect(ctx *gin.Context) {subkey := uuid.New().String()client.Connect(ctx, subkey)
    }
    
  • 发送消息

    这里可以发送全部信息给全部用户,或者发送给用户,AppSocket.Message结构体中Subkeys []string表示需要发送给哪些用户:

    client.WriteMessage(AppSocket.Message{MessageType: websocket.TextMessage,Data:        []byte("服务端收到消息并回复ok"),Subkeys:     []string{"9bae9c4f-87a8-46b1-b1b9-4f37b63a7d19"}})
    

    如果Subkeys是空切片数组,会将消息推送给全部在线用户

  • 心跳消息

    websocket标准协议实现隐式心跳,Server端向Client端发送ping格式数据包,浏览器收到ping标准格式,自动将消息原路返回给服务器

  • 其他方法

    • GetAllKeys() []string:获取所有websocket连接uuid
    • GetClientState(key string) bool:获取指定客户端在线状态

消息中间件

RabbitMQ

RabbitMQ消息中间件的使用可参考test/rmq_test.go单元测试,同时骨架中也实现了RabbitMQ的简单模式示例:app/amqp目录下可查看

全局变量variable.Amqp返回mq.RabbitMQInterface接口,接口中的方法可查看源文件查看

  • 定义消费者consumer

    定义consumer,需要实现mq.ConsumerHandler接口,该接口可查看internal/mq/rabbitmq.go文件中的定义,比如定义一个简单模式的消费者:

    import ("fmt""skeleton/internal/mq""github.com/streadway/amqp"
    )type FooConsumer struct{}func (*FooConsumer) Option() mq.ConsumerOption {return mq.ConsumerOption{CommonOption: mq.CommonOption{Mode:       mq.SimpleMode,QueueName:  "foo",Durable:    false,AutoDelete: false,Exclusive:  false,NoWait:     false,Args:       nil,},AutoAck: true,NoLocal: false,}
    }func (*FooConsumer) Exec(msg <-chan amqp.Delivery) {for v := range msg {fmt.Printf("consumer message:%v\n", string(v.Body))}
    }
    

    当我们定义好消费者后,需要通过mq.InitConsumer中的InitConsumers() []ConsumerHandler进行注册,骨架中注册文件已写死,项目启动时,会执行InitConsumers方法,查看app/amqp/amqp.go文件的代码:

    package amqpimport ("skeleton/app/amqp/consumer""skeleton/internal/mq"
    )type Amqp struct{}func (*Amqp) InitConsumers() []mq.ConsumerHandler {return []mq.ConsumerHandler{&consumer.FooConsumer{},}
    }
    
  • 发送消息producer

    通过全局变量variable.Amqp的方法发送消息:

    opts := mq.ProducerOption{CommonOption: mq.CommonOption{Mode:       mq.SimpleMode,QueueName:  "foo",Durable:    false,AutoDelete: false,Exclusive:  false,NoWait:     false,Args:       nil,},Message: amqp.Publishing{ContentType: "text/plain",Body:        []byte("test message"),},Mandatory: false,Immediate: false,}variable.Amqp.Publish(opts)
    
RocketMQ

注意:目前官方RocketMQ Golang SDK一些功能尚未完善,暂时不可用

消息中间件的使用可参考test/mq_test.go单元测试

骨架对外提供了mq.Interface接口中的方法,可查看internal/mq/rocketmq.go文件下的Interface接口定义

定义consumer时,只需要实现mq.ConsumerInterface接口即可,详见mq_test单元测试

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

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

相关文章

Qt实现卡牌对对碰游戏

效果 闲来无事&#xff0c;实现一个对对碰游戏&#xff0c;卡牌样式是火影动漫。 先上效果&#xff1a; 卡牌对对碰_火影主题 玩法 启动游戏&#xff0c;进入第一关卡&#xff0c;所有卡牌都为未翻开状态&#xff0c;即背面朝上&#xff1b;点击卡牌&#xff0c;则将卡牌翻开…

jenkins工具系列 —— 删除Jenkins JOB后清理workspace

文章目录 问题现象分析解决思路脚本实现问题现象分析 Jenkins使用过程中,占用空间最大的两个位置: 1 、workspace: 工作空间,可以随便删除,删除后再次构建时间可能会比较长,因为要重新获取一些资源。 2 、job: 存放的是项目的配置、构建结果、日志等。不建议手动删除,…

双亲委派模式

双亲委派模型 双亲委派的工作过程 一个类加载器收到类加载的请求时&#xff0c;它不会马上加载该类&#xff0c;而是把这个请求委托给父加载器去完成&#xff0c;每一个层次的类加载器都是如此&#xff0c;因此所有的类加载请求都必须先通过启动类加载器尝试加载&#xff0c;只…

【快报】正在把教学视频搬运到B站和油管

hello 大家好&#xff0c;我是老戴。 熟悉我的同学知道&#xff0c;我从14年开始录制GIS相关的教学视频&#xff0c;之前是放到优酷上给大家下载&#xff0c;后期发现很多人把视频弄下来淘宝上卖&#xff0c;然后我就把视频整体放到了我自己的网站上。 随着视频录制的数量越来…

HBuilderX实现安卓真机调试

1. 简介 HBuilderX 简称 HX&#xff0c;HBuilder&#xff0c;H 是 HTML 的缩写&#xff0c;Builder 是建设者。是为前端开发者服务的通用 IDE&#xff0c;或者称为编辑器。与 vscode、sublime、webstorm 类似。 它可以开发普通 web 项目&#xff0c;也可以开发 DCloud 出品的 u…

nodejs+vue+python+php基于微信小程序的在线学习平台设计与实现-计算机毕业设计

困扰管理层的许多问题当中,在线学习也是不敢忽视的一块。但是管理好在线学习又面临很多麻烦需要解决,例如&#xff1a;如何在工作琐碎,记录繁多的情况下将在线学习的当前情况反应给课程问题管理员决策,等等。 流,开发一个在线学习平台小程序一方面的可能会更合乎时宜,另一方面来…

Java IDEA设置环境变量 以及代码获取

IDEA 设置环境变量 1.进入如图设置&#xff0c;一般的 java 程序和 spring Boot &#xff0c;还是tomcat 都可以从这里进入 2.可以在如下地方手动添加 3. tomcat 类 4.spring boot 类 代码获取指定值 假定我设置如下 代码则如下获取&#xff08;类均为JDK 自带类&…

代理模式代理模式

目录 1、使用场景 2、静态代理 3、动态代理 JDK动态代理 CGlib 动态代理实现 1、使用场景 使用代理模式主要有两个目的&#xff1a;一是保护目标对象&#xff0c;二是增强目标对象。 2、静态代理 NO.1 抽象接口&#xff1a;定义视频播放器接口Player public interface P…

http1,https,http2,http3总结

1.HTTP 当我们浏览网页时&#xff0c;地址栏中使用最多的多是https://开头的url&#xff0c;它与我们所学的http协议有什么区别&#xff1f; http协议又叫超文本传输协议&#xff0c;它是应用层中使用最多的协议&#xff0c; http与我们常说的socket有什么区别吗&#xff1f; …

怎么在电脑桌面上添加待办事项?

在电脑桌面上选择一款待办事项工具&#xff0c;可以高效率地督促各项任务的按时完成&#xff0c;大大地提高工作的效率&#xff0c;支持在电脑上安装待办事项的工具类型是比较多的&#xff0c;为更好的辅助日常办公&#xff0c;建议大家可以选择高效率辅助办公的电脑便签工具&a…

网络协议--TCP的未来和性能

24.1 引言 TCP已经在从1200 b/s的拨号SLIP链路到以太数据链路上运行了许多年。在80年代和90年代初期&#xff0c;以太网是运行TCP/IP最主要的数据链路方式。虽然TCP在比以太网速率高的环境&#xff08;如T2电话线、FDDI及千兆比网络&#xff09;中也能够正确运行&#xff0c;但…

高并发和存储之间的关系是什么?

文章目录 &#x1f50a;博主介绍&#x1f916;博主的简介&#x1f4e5;博主的目标 &#x1f964;本文内容&#x1f34a; 一、高并发对存储的压力&#x1f34a; 二、存储的性能和可扩展性 &#x1f4e2;总结 &#x1f50a;博主介绍 &#x1f4d5;我是廖志伟&#xff0c;一名Java…

[Unity+智谱AI开放平台]调用ChatGLM Tuobo模型驱动AI小姐姐数字人

1.简述 本篇文章主要介绍一下&#xff0c;在Unity端&#xff0c;集成智谱AI开放平台提供的chatglm模型api&#xff0c;实现AI聊天互动相关的功能。从智谱AI官方站点上看到&#xff0c;提供有chatglm turbo的公共模型服务&#xff0c;能够实现32K超长上下文&#xff0c;应用到我…

Spring Security 6.1.x 系列(3)—— 基于过滤器的基础原理(二)

四、SecurityFilterChain 在Serlvet中&#xff0c;一组Security Filter组成SecurityFilterChain&#xff0c;SecurityFilterChain的概念就比较好理解&#xff0c;是Spring Security 提供的过滤器链&#xff0c;用于管理本身所有的过滤器&#xff0c;在上面的流程图中已有说明。…

【嵌入式】Linux C编程——C要注意的东西

1、语法分析中的“贪心法”&#xff1a; 编译器将程序分解成符号的方法是&#xff0c;从左到右一个字符一个字符地读入&#xff0c;如果该字符可能组成一个符号&#xff0c;那么再读入下一个字符&#xff0c;判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分&…

Lvs+Nginx+NDS

什么是&#xff1f;为什么&#xff1f;需要负载均衡 一个网站在创建初期&#xff0c;一般来说都是只有一台服务器对用户提供服务 ​ 从图里可以看出&#xff0c;用户经过互联网直接连接了后端服务器&#xff0c;如果这台服务器什么时候突然 GG 了&#xff0c;用户将无法访问这…

Python构造代理IP池提高访问量

目录 前言 一、代理IP是什么 二、代理IP池是什么 三、如何构建代理 IP 池 1. 从网上获取代理 IP 地址 2. 对 IP 地址进行筛选 3. 使用筛选出来的 IP 地址进行数据的爬取 四、总结 前言 爬虫程序是批量获取互联网上的信息的重要工具&#xff0c;在访问目标网站时需要频…

QT实现用本地资源管理器来打开文件夹

QString path"文件夹路径";QDesktopServices::openUrl(QUrl("file:"path, QUrl::TolerantMode)); 在windows中QT编程&#xff0c;使用资源管理器来打开指定本地文件夹的方法&#xff1a; 第一种&#xff1a;使用Qprocess命令&#xff08;相当于在cmd命令管…

【面试经典150 | 链表】反转链表 II

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;迭代 其他语言python3 写在最后 Tag 【头插法】【迭代】【链表】 题目来源 92. 反转链表 II 题目解读 返回部分链表。 解题思路 方法一&#xff1a;迭代 我们首先需要找到开始反转链表的前一个节点&#xff0c;为此…

React 你还在用 Redux 吗?更简化的状态管理工具(Recoil)

以往传统的 Redux 状态管理工具使用起来代码太过于复杂。 你需要通过纯函数触发 action 再去修改 data 中定义的数据&#xff0c;而且要通过接口请求数据还需要借助 redux - think 这个中间件才能完成。。。 更加方便使用的工具&#xff1a;Recoil ~ 由 facebook 推出契合 R…