WordPress禁用评论回收站,wordpress使用memcached,对钩网机械加工订单,phpcms 怎么做视频网站节省时间与精力#xff0c;更高效地打造稳定可靠的Web项目#xff1a;基于Go语言和Gin框架的完善Web项目骨架。无需从零开始#xff0c;直接利用这个骨架#xff0c;快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势#xff0c;轻松处理高并发、大… 节省时间与精力更高效地打造稳定可靠的Web项目基于Go语言和Gin框架的完善Web项目骨架。无需从零开始直接利用这个骨架快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势轻松处理高并发、大流量的请求。构建可扩展性强、易于维护的代码架构保证项目的长期稳定运行。同时通过集成常用功能模块和最佳实践减少繁琐的开发工作使您专注于业务逻辑的实现。 该骨架每个组件之间可单独使用组件之间松耦合高内聚组件的实现基于其他三方依赖包的封装。 目前该骨架实现了大多数的组件比如事件,中间件,日志,配置,参数验证,命令行,定时任务等功能目前可以满足大多数开发需求后续会持续维护更新功能。 青丝南开https://www.cnblogs.com/smallKilts/p/17726021.html github地址https://github.com/czx-lab/skeletongitee地址https://gitee.com/neddie/skeleton 设置环境变量并下载项目依赖
go env -w GO111MODULEon
go env -w GOPROXYhttps://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/httpskeleton/internal/servergithub.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:roottcp(192.168.1.4:3306)/test?charsetutf8mb4locLocalparseTimeTrueWrite: root:roottcp(192.168.1.4:3306)/test?charsetutf8mb4locLocalparseTimeTrue# mongo数据库的基础配置Mongo:Enable: falseUri:MinPoolSize: 10MaxPoolSize: 20Redis:Disabled: falseAddr: 192.168.1.4:6379Pwd: Db: 0PoolSize: 20MaxIdleConn: 30MinIdleConn: 10# 单位秒MaxLifeTime: 60# 单位分MaxIdleTime: 30# 定时任务
Crontab:Enable: true# 消息队列使用rocketmq
MQ:Enable: trueServers:- 127.0.0.1:9876Retries: 1ProducerGroupName: ProducerGroupConsumerGroupName: ConsumerGroup# RabbitMQ
Amqp:Enable: trueAddr: amqp://guest:guest127.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 (fmtevent2 skeleton/app/event/entityskeleton/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/gintype 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 (fmtgithub.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 fmttype 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: %vdata: %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/uuidgithub.com/gin-gonic/ginAppSocket 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连接uuidGetClientState(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 (fmtskeleton/internal/mqgithub.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/consumerskeleton/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单元测试