该骨架每个组件之间可单独使用,组件之间松耦合,高内聚,组件的实现基于其他三方依赖包的封装。

目前该骨架实现了大多数的组件,比如事件,中间件,日志,配置,参数验证,命令行,定时任务等功能,目前可以满足大多数开发需求,后续会持续维护更新功能。

github地址:https://github.com/czx-lab/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 ---> 单元测试目录

基础功能


路由

该骨架的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 router

import (
"net/http" "github.com/czx-lab/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 "github.com/czx-lab/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) any
GetString(key string) string
GetBool(key string) bool
GetInt(key string) int
GetInt32(key string) int32
GetInt64(key string) int64
GetFloat64(key string) float64
GetDuration(key string) time.Duration
GetStringSlice(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: 2048
HeartbeatFailMaxTimes: 4
PingPeriod: 20
ReadDeadline: 100
WriteDeadline: 35
PingMsg: "ping" # 数据库配置
Database:
# 可以查看GORM相关的配置选项
Mysql:
SlowThreshold: 5
LogLevel: 4
ConnMaxLifetime: 1
MaxIdleConn: 2
MaxOpenConn: 2
ConnMaxIdleTime: 12
Reade:
- "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: false
Uri:
MinPoolSize: 10
MaxPoolSize: 20 Redis:
Disabled: false
Addr: "192.168.1.4:6379"
Pwd: ""
Db: 0
PoolSize: 20
MaxIdleConn: 30
MinIdleConn: 10
# 单位(秒)
MaxLifeTime: 60
# 单位(分)
MaxIdleTime: 30 # 定时任务
Crontab:
Enable: true # 消息队列,使用rocketmq
MQ:
Enable: false
Servers:
- "127.0.0.1:9876"
ConsumptionSize: 1
Retries: 1

事件机制

  • 定义事件实体

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

    package entity
    
    type 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 listen
    
    import (
    "fmt"
    event2 "github.com/czx-lab/skeleton/app/event/entity"
    "github.com/czx-lab/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 controller

import "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 DemoRequest
if 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 command
    
    import (
    "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 task
    
    import "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文件

基于go语言gin框架的web项目骨架的更多相关文章

  1. 使用go语言开发一个后端gin框架的web项目

    用liteide来开发go的后端项目,需要注意的是环境变量要配置正确了 主要是GOROOT, GOPATH, GOBIN, PATH这几个, GOPATH主要用来存放要安的包,主要使用go get 来 ...

  2. SSM框架开发web项目系列(二) MyBatis真正的力量

    前言 上篇SSM框架环境搭建篇,演示了我们进行web开发必不可少的一些配置和准备工作,如果这方面还有疑问的地方,可以先参考上一篇“SSM框架开发web项目系列(一) 环境搭建篇”.本文主要介绍MyBa ...

  3. SSM框架开发web项目系列(三) MyBatis之resultMap及关联映射

    前言 在上篇MyBatis基础篇中我们独立使用MyBatis构建了一个简单的数据库访问程序,可以实现单表的基本增删改查等操作,通过该实例我们可以初步了解MyBatis操作数据库需要的一些组成部分(配置 ...

  4. SSM框架开发web项目系列(五) Spring集成MyBatis

    前言 在前面的MyBatis部分内容中,我们已经可以独立的基于MyBatis构建一个数据库访问层应用,但是在实际的项目开发中,我们的程序不会这么简单,层次也更加复杂,除了这里说到的持久层,还有业务逻辑 ...

  5. MyEclipse使用总结——在MyEclipse中新建Maven框架的web项目[转]

    前面的文章我们已经在本机安装好了maven,同时在myeclipse中配置好了maven的插件. 链接如下: Maven安装----在Windows上安装Maven myeclipse安装maven插 ...

  6. Azure Traffic Manager(二) 基于权重与基于优先级的路由策略为我们的Web项目提供负载均衡

    一,引言 上一片文章我们使用 Azure Traffic Manager 分发用户请求,同时演示了两种路由策略,“Performance”,“Geographic”的两种方式,今天我们继续讲解 Tra ...

  7. Linux使用IDEA配置maven的web项目骨架archetype(模板) 自定义骨架

    说明:本文说的骨架就是 archetype,也可以理解为模板,总是就是指你创建项目时的基本配置. 前言:在使用IDEA创建maven的web项目时,一般都是直接使用提供的默认web项目,如图 然而创建 ...

  8. JFinal -基于Java 语言的MVC极速 web 开发框架

    JFinal概述 JFinal 是基于Java 语言的极速 web 开发框架,其核心设计目标是开发迅速.代码量少.学习简单.功能强大.轻量级.易扩展.Restful.在拥有Java语言所有优势的同时再 ...

  9. GO语言GIN框架入门

    Gin框架介绍 Gin是一个用Go语言编写的web框架.它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍. 中文文档 Gin框架安装与使用 ...

  10. 手工搭建ABP框架(1) - Web项目

    为了防止不提供原网址的转载,特在这里加上原文链接: http://www.cnblogs.com/skabyy/p/7295533.html ABP是 ASP.NET Boilerplate Proj ...

随机推荐

  1. [Java SE] 彻底搞懂Java程序的三大参数配置途径:系统变量与JVM参数(VM Option)/环境变量/启动程序参数args

    0 序言 一次没搞懂,处处受影响.这个问题属于基础问题,但又经常踩坑,不得不重视一下了. 1 Java程序动态参数的配置途径:系统变量与JVM参数(VM Option) vs 环境变量 vs 启动程序 ...

  2. 快速取模算法(Barrett Reduction)

    原理:取模运算低效的原因本质是除法运算的低效.如果能将除法变成其它运算就可以加速.具体地,将除以任意数转化成"乘一个数.除以一个 \(2^k\) "(取 \(2^{62}\) 即可 ...

  3. 如何构建您的第一部AWS机器学习服务

    目录 <如何构建您的第一部 AWS 机器学习服务> 背景介绍 随着深度学习的广泛应用于机器学习领域的各个方面,AWS 成为了一种重要的深度学习平台.作为 AWS 机器学习服务的第一部,如何 ...

  4. BeEF记录

    前情提要 最近项目上常规手段遇阻,计划进行水坑钓鱼,一番搜索找到近期SolarMarker组织的手法,但是没有找到相关样本,于是就自己实现了一个类似的前端功能(水坑手法项目会持续记录学习,但目前不会放 ...

  5. Zabbix Timeout 设置不当导致的问题

    哈喽大家好,我是咸鱼 今天跟大家分享一个关于 zabbix Timeout 值设置不当导致的问题,这个问题不知道大家有没有碰到过 问题 事情经过是这样的: 把某一台 zabbix agent 的模板由 ...

  6. 使用mybatis-generator 能生成 但是实际使用时抛出异常Invalid bound statement (not found)

    好多好多好多红 但是重点是一句org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): {}.d ...

  7. vue: 在页面中单独引入elment-ui

    引入资源 首先引入Vue,之后引入element-ui. 引入组件 返回数据 全部代码 <!DOCTYPE html> <html lang="en"> & ...

  8. Cilium系列-16-CiliumNetworkPolicy 实战演练

    系列文章 Cilium 系列文章 前言 今天我们进入 Cilium 安全相关主题, 基于 Cilium 官方的<星球大战> Demo 做详细的 CiliumNetworkPolicy 实战 ...

  9. 解决Avalonia 11.X版本的中文字体问题

    网上搜索的方法使用接口"IFontManagerImpl"这个方法目前只能用于Avalonia 10.X版本,因为11版本后官方把这个接口的成员都设置成了非plubic,所以之前的 ...

  10. 2023-08-14:用go语言写算法。给出两个长度相同的字符串 str1 和 str2 请你帮忙判断字符串 str1 能不能在 零次 或 多次 转化 后变成字符串 str2 每一次转化时,你可以将

    2023-08-14:用go语言写算法.给出两个长度相同的字符串 str1 和 str2, 请你帮忙判断字符串 str1 能不能在 零次 或 多次 转化 后变成字符串 str2, 每一次转化时,你可以 ...