- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
节省时间与精力,更高效地打造稳定可靠的Web项目:基于Go语言和Gin框架的完善Web项目骨架。无需从零开始,直接利用这个骨架,快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势,轻松处理高并发、大流量的请求。构建可扩展性强、易于维护的代码架构,保证项目的长期稳定运行。同时,通过集成常用功能模块和最佳实践,减少繁琐的开发工作,使您专注于业务逻辑的实现.
该骨架每个组件之间可单独使用,组件之间松耦合,高内聚,组件的实现基于其他三方依赖包的封装。 目前该骨架实现了大多数的组件,比如 事件 , 中间件 , 日志 , 配置 , 参数验证 , 命令行 , 定时任务 等功能,目前可以满足大多数开发需求,后续会持续维护更新功能.
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"
"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) 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 "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 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, ¶m); 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项目骨架的文章就讲到这里了,如果你想了解更多关于基于go语言gin框架的web项目骨架的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个几乎可以构建的Maven / Grails应用,但在web.xml上找不到[my-app]\webapp\WEB-INF\web.xml。但是目录结构像往常一样包含web-app文件夹,而不是
正如我在标题中提到的:我想知道 web-service 和 web-socket 之间的区别?我们什么时候使用每一个? 谢谢! 最佳答案 一个web service是一个响应客户端 SOAP/REST
让我们看一个示例场景: 客户端打开一个网站并找到他从文本框中输入的两个数字的总和。然后单击“添加”按钮。两个参数通过 HTTP GET 发送到服务器,在服务器上写入 PHP 代码以添加数字,结果为回声
我知道这是一个老问题,肯定已经被回答了数百次,但我还无法找到令人满意的答案。 我正在创建一个应用程序,其他应用程序(移动/网络)将使用该应用程序来获取数据。现在我有两个选择: 将我的应用程序创建为简单
通过 Web 作业部署新功能有 3 种方法: 创建一个新的 Web 应用,并部署一个包含该函数的 Web 作业。 向现有 Web 作业添加一项新函数(这样您现在在一个 Web 作业中就拥有了多个函数)
我收到来自网络场景的通知,上面写着“问题”和“确定”。我想在问题发生时包含网络响应的内容。我不担心标题值,只担心网页的内容. 这是我可以在通知设置中引用的变量吗? 最佳答案 不幸的是 zabbix 不
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the he
嗨,这是一个理论问题,但我真的无法弄清楚 Web 应用程序、基于 Web 的应用程序和基于云的应用程序之间的区别。这个你能帮我吗。 最佳答案 @Matt 是对的 - 这真的无关紧要,但是,为了清楚起见
我正在尝试使用多个 Web 服务,这些服务在它们的 wsdl 中重新定义了一些相同的公共(public)类。我目前在网站中引用了它们,但我想转换为 Web 应用程序。 由于一些相同的类是从多个 Web
一个。我必须考虑哪些事项?b.当前应用程序正在执行多个存储过程。如果我创建等效的方法来执行这些过程,会有什么风险或挑战。 最佳答案 在架构上,将网络应用程序转换为网络服务时必须考虑的一件事是,对方法和
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 5年前关闭。 Improve thi
网络 API 和网络服务之间有什么区别吗?或者它们是同一个吗? 最佳答案 网络服务通常提供 WSDL您可以从中自动创建客户端 stub 。 Web 服务基于 SOAP protocol 。ASP.NE
我已经获得了我需要的所有资源。我将 Web 服务放入应用程序服务环境中,然后将 NSG 连接到应用程序服务环境使用的子网。然后,我允许 VNET 内的应用程序与 Web 服务进行通信,但它无法正常工作
我已经获得了我需要的所有资源。我将 Web 服务放入应用程序服务环境中,然后将 NSG 连接到应用程序服务环境使用的子网。然后,我允许 VNET 内的应用程序与 Web 服务进行通信,但它无法正常工作
我正在使用 stub 将我的网络服务相关测试与实际网络服务隔离开来。 你/我应该如何合并测试以确保我制作的响应与实际的网络服务匹配(我无法控制它)? 我不想知道怎么做,而是何时何地? 我应该为测试数据
我在互联网上搜索了很多,但我仍然没有得到网络服务和网络 API 之间的明显区别?我在某处读到所有 Web 服务都是 API,但所有 API 都不是 Web 服务。如何? 我所知道的是两者都允许利用其他
假设我已经完成了使用 JavaEE 制作的 Web 应用程序。这个 Web 应用程序包含登录系统,但最后它是非常基本的 Web 应用程序。我使用的是 GlassFish 3.1.2.2。 我想知道一旦
我希望设计者能够打开与我相同的解决方案文件。这可以通过 Expressions Web 实现吗? 最佳答案 简短的回答是“不”;但这是一个非常常见的请求,我知道很多人都希望下一个版本(无论何时)对此有
我正在尝试在 CF10 中创建一个 Web 服务对象。我已验证它在 SoapUI 中按预期工作。但是,当我在 CF 中运行它时,我得到一个错误,它无法找到在 WSDL 的导入语句中导入的 XSD。这是
我的要求是开发一个 Web 服务,充当外部 Web 服务和客户端之间的中间人。 我知道,我可以为我的服务设计一个wsdl,然后将外部wsdl映射到代码中我的wsdl。我的问题是有一个开源 api/工具
我是一名优秀的程序员,十分优秀!