经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
基于go语言gin框架的web项目骨架
来源:cnblogs  作者:青丝南开  时间:2023/9/25 16:50:13  对本文有异议

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

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

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

设置环境变量并下载项目依赖

  1. go env -w GO111MODULE=on
  2. go env -w GOPROXY=https://goproxy.cn,direct
  3. go mod download

运行项目

  1. go run ./cmd/main.go

项目编译打包运行

  1. go build ./cmd/main.go
  2. // 编译
  3. make build
  4. // 运行
  5. make run
  6. // 编译与运行
  7. make
  8. // 运行项目
  9. ./main

项目目录结构说明

  1. ├─app
  2. ├─command ---> 命令行
  3. ├─controller
  4. └─base.go ---> BaseController,主要定义了request参数验证器validator
  5. ├─event
  6. ├─entity ---> 事件实体目录
  7. ├─listen ---> 事件监听执行脚本目录
  8. └─event.go ---> 事件注册代码
  9. ├─middleware ---> 中间件代码目录
  10. ├─request ---> 请求参数校验代码目录
  11. └─request.go ---> 参数验证器
  12. └─task ---> 定时任务代码目录
  13. └─task.go ---> 注册定时任务脚本
  14. ├─cmd ---> 项目入口目录
  15. └─cli ---> 项目命令行模式入口目录
  16. ├─config
  17. └─config.yaml ---> 配置文件
  18. ├─internal ---> 包含第三方包的封装
  19. ├─router ---> 路由目录
  20. └─router.go
  21. ├─storage ---> 日志、资源存储目录
  22. └─logs
  23. └─test ---> 单元测试目录

基础功能


路由

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

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

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

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

  1. package router
  2. import (
  3. "net/http"
  4. "skeleton/internal/server"
  5. "github.com/gin-gonic/gin"
  6. )
  7. type CustomRouter struct {
  8. server server.HttpServer
  9. }
  10. func NewCustom(srv server.HttpServer) *CustomRouter {
  11. return &CustomRouter{
  12. srv,
  13. }
  14. }
  15. func (*CustomRouter) Add(srv *gin.Engine) {
  16. srv.GET("/custom", func(ctx *gin.Context) {
  17. ctx.String(http.StatusOK, "custom router")
  18. })
  19. }

需要注意的是,如果是自定义路由注册,需要修改项目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

  1. type Custom struct{}
  2. func (c *Custom) Handle() gin.HandlerFunc {
  3. return func(ctx *gin.Context) {
  4. fmt.Println("Custom middleware exec...")
  5. }
  6. }

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

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

日志

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

  1. package demo
  2. import "skeleton/internal/variable"
  3. func Demo() {
  4. variable.Log.Info("info message")
  5. }

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

配置

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

  1. type ConfigInterface interface {
  2. Get(key string) any
  3. GetString(key string) string
  4. GetBool(key string) bool
  5. GetInt(key string) int
  6. GetInt32(key string) int32
  7. GetInt64(key string) int64
  8. GetFloat64(key string) float64
  9. GetDuration(key string) time.Duration
  10. GetStringSlice(key string) []string
  11. }

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

  1. type ConfigRedisCache struct {}
  2. var _ config.CacheInterface = (*ConfigRedisCache)(nil)
  3. func (c *ConfigRedisCache) Get(key string) any {
  4. return nil
  5. }
  6. func (c *ConfigRedisCache) Set(key string, value any) bool {
  7. return true
  8. }
  9. func (c *ConfigRedisCache) Has(key string) bool {
  10. return true
  11. }
  12. func (c *ConfigRedisCache) FuzzyDelete(key string) {
  13. }

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

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

config.yaml基础配置如下:

  1. # http配置
  2. HttpServer:
  3. Port: ":8888"
  4. # 服务模式,和gin的gin.SetMode的值是一样的
  5. Mode: "debug"
  6. # socket配置
  7. Websocket:
  8. WriteReadBufferSize: 2048
  9. HeartbeatFailMaxTimes: 4
  10. PingPeriod: 20
  11. ReadDeadline: 100
  12. WriteDeadline: 35
  13. PingMsg: "ping"
  14. # 数据库配置
  15. Database:
  16. # 可以查看GORM相关的配置选项
  17. Mysql:
  18. SlowThreshold: 5
  19. LogLevel: 4
  20. ConnMaxLifetime: 1
  21. MaxIdleConn: 2
  22. MaxOpenConn: 2
  23. ConnMaxIdleTime: 12
  24. Reade:
  25. - "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"
  26. Write: "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"
  27. # mongo数据库的基础配置
  28. Mongo:
  29. Enable: false
  30. Uri:
  31. MinPoolSize: 10
  32. MaxPoolSize: 20
  33. Redis:
  34. Disabled: false
  35. Addr: "192.168.1.4:6379"
  36. Pwd: ""
  37. Db: 0
  38. PoolSize: 20
  39. MaxIdleConn: 30
  40. MinIdleConn: 10
  41. # 单位(秒)
  42. MaxLifeTime: 60
  43. # 单位(分)
  44. MaxIdleTime: 30
  45. # 定时任务
  46. Crontab:
  47. Enable: true
  48. # 消息队列,使用rocketmq
  49. MQ:
  50. Enable: false
  51. Servers:
  52. - "127.0.0.1:9876"
  53. ConsumptionSize: 1
  54. Retries: 1

事件机制

  • 定义事件实体

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

    1. package entity
    2. type DemoEvent struct {}
    3. func (d *DemoEvent) EventName() string {
    4. return "demo-event"
    5. }
    6. func (d *DemoEvent) GetData() any {
    7. return "demo param"
    8. }
  • 定义事件监听

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

    1. package listen
    2. import (
    3. "fmt"
    4. event2 "skeleton/app/event/entity"
    5. "skeleton/internal/event"
    6. )
    7. type DemoEventListen struct {
    8. }
    9. func (*DemoEventListen) Listen() event.EventInterface {
    10. return &event2.DemoEvent{}
    11. }
    12. func (*DemoEventListen) Process(data any) (any, error) {
    13. return fmt.Sprintf("%v --> %s", data, "exec DemoEventListen.Process"), nil
    14. }
  • 最后需要将事件进行注册,在app/event/event.go文件中的Init方法内执行:

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

    1. variable.Event.Dispatch(&entity.DemoEvent{})

验证器

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

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

  1. type Param struct {
  2. Name int `binding:"required" form:"name" query:"name" json:"name"`
  3. }
  4. appRequest, err := AppRequest.New("zh")
  5. if err != nil {
  6. return
  7. }
  8. var data Param
  9. errMap := appRequest.Validator(ctx, &data)
  10. fmt.Println(errMap)

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

  1. package controller
  2. import "github.com/gin-gonic/gin"
  3. type DemoController struct {
  4. base
  5. }
  6. type DemoRequest struct {
  7. Id int `binding:"required" form:"id" query:"id" json:"id"`
  8. }
  9. func (d *DemoController) Index(ctx *gin.Context) {
  10. var param DemoRequest
  11. if err := d.base.Validate(ctx, &param); err == nil {
  12. ctx.JSON(http.StatusOK, gin.H{"data": param})
  13. } else {
  14. ctx.JSON(http.StatusBadRequest, gin.H{"message": err})
  15. }
  16. }

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

命令行

基于github.com/spf13/cobra封装

  • 定义命令

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

    1. package command
    2. import (
    3. "fmt"
    4. "github.com/spf13/cobra"
    5. )
    6. type FooCommand struct {}
    7. func (f *FooCommand) Command() *cobra.Command {
    8. return &cobra.Command{
    9. Use: "foo",
    10. Short: "命令使用简介.",
    11. Long: `命令介绍.`,
    12. Run: func(cmd *cobra.Command, args []string) {
    13. str, _ := cmd.Flags().GetString("name")
    14. fmt.Printf("success, %s", str)
    15. },
    16. }
    17. }
    18. func (f *FooCommand) Flags(root *cobra.Command) {
    19. root.PersistentFlags().String("name", "", "命令参数")
    20. }
  • 注册命令

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

  • 执行命令

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

    1. go run cmd/cli/cli.go help
    2. // 或者
    3. go run cmd/cli/cli.go foo --help

定时任务

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

  • 定义定时任务方法

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

    1. package task
    2. import "fmt"
    3. type SuccessTask struct {
    4. }
    5. // 时间规则
    6. func (s *SuccessTask) Rule() string {
    7. return "* * * * *"
    8. }
    9. func (s *SuccessTask) Execute() func() {
    10. return func() {
    11. fmt.Println("success")
    12. }
    13. }
  • 加载定时任务

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

原文链接:https://www.cnblogs.com/smallKilts/p/17726021.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号