feat: 添加服务和数据库初始化
This commit is contained in:
321
README.md
Normal file
321
README.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# go-web-gin
|
||||
|
||||
一个基于 [Gin](https://github.com/gin-gonic/gin) 的 Go Web 应用脚手架,提供开箱即用的基础设施组件。
|
||||
|
||||
## 特性
|
||||
|
||||
- 🚀 **快速启动** - 简洁的 API 设计,快速搭建 Web 服务
|
||||
- 🔧 **配置管理** - 支持 YAML 配置文件和环境变量覆盖
|
||||
- 📝 **结构化日志** - 基于 [Zap](https://github.com/uber-go/zap) 的高性能日志
|
||||
- 🗄️ **数据库支持** - MySQL (GORM) 和 Redis 单例连接
|
||||
- 🌍 **多环境** - 支持 Local、Development、Production 环境
|
||||
- 🧩 **单例模式** - 组件单例管理,避免重复初始化
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
go get git.hujye.com/infrastructure/go-web-gin
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 创建配置文件
|
||||
|
||||
在项目根目录创建 `config/config.yml`:
|
||||
|
||||
```yaml
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
mode: debug # debug, release, test
|
||||
|
||||
app:
|
||||
name: my-app
|
||||
environment: local
|
||||
log_level: info
|
||||
|
||||
log:
|
||||
output_to_file: true
|
||||
filename: logs/app.log
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
|
||||
mysql:
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
user: root
|
||||
password: ""
|
||||
db_name: test
|
||||
charset: utf8mb4
|
||||
parse_time: true
|
||||
max_idle_conns: 10
|
||||
max_open_conns: 100
|
||||
conn_max_lifetime: 3600
|
||||
|
||||
redis:
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
db: 0
|
||||
pool_size: 10
|
||||
min_idle_conns: 5
|
||||
max_retries: 3
|
||||
dial_timeout: 5
|
||||
read_timeout: 3
|
||||
write_timeout: 3
|
||||
```
|
||||
|
||||
### 2. 编写主程序
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
go_web_gin "git.hujye.com/infrastructure/go-web-gin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建应用实例
|
||||
app := go_web_gin.New()
|
||||
|
||||
// 初始化数据库(可选)
|
||||
app.InitMySQL()
|
||||
app.InitRedis()
|
||||
|
||||
// 注册全局中间件
|
||||
app.UseMiddleware(gin.Recovery())
|
||||
|
||||
// 注册路由
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.GET("/ping", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "pong"})
|
||||
})
|
||||
|
||||
e.GET("/users/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
// 使用日志
|
||||
app.Logger().Info(c.Request.Context(), "get user", "id", id)
|
||||
c.JSON(200, gin.H{"user_id": id})
|
||||
})
|
||||
|
||||
// 使用数据库
|
||||
e.GET("/db-test", func(c *gin.Context) {
|
||||
db := app.DB()
|
||||
if db != nil {
|
||||
// 执行数据库操作
|
||||
c.JSON(200, gin.H{"db": "connected"})
|
||||
} else {
|
||||
c.JSON(500, gin.H{"error": "db not initialized"})
|
||||
}
|
||||
})
|
||||
|
||||
// 使用 Redis
|
||||
e.GET("/redis-test", func(c *gin.Context) {
|
||||
rdb := app.Redis()
|
||||
if rdb != nil {
|
||||
rdb.Set(c.Request.Context(), "test", "value", 0)
|
||||
c.JSON(200, gin.H{"redis": "connected"})
|
||||
} else {
|
||||
c.JSON(500, gin.H{"error": "redis not initialized"})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 启动服务
|
||||
// 非生产环境可传入自定义地址,生产环境使用配置文件地址
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## API 文档
|
||||
|
||||
### App 结构
|
||||
|
||||
```go
|
||||
type App struct {
|
||||
engine *gin.Engine
|
||||
db *gorm.DB
|
||||
rdb *redis.Client
|
||||
logger *logger.Logger
|
||||
}
|
||||
```
|
||||
|
||||
### 方法列表
|
||||
|
||||
| 方法 | 说明 | 返回值 |
|
||||
|------|------|--------|
|
||||
| `New()` | 创建新的 App 实例 | `*App` |
|
||||
| `UseMiddleware(...)` | 注册全局中间件 | - |
|
||||
| `RegisterRoutes(func)` | 注册路由 | - |
|
||||
| `InitMySQL()` | 初始化 MySQL 连接 | `*gorm.DB` |
|
||||
| `InitRedis()` | 初始化 Redis 连接 | `*redis.Client` |
|
||||
| `DB()` | 获取数据库实例 | `*gorm.DB` |
|
||||
| `Redis()` | 获取 Redis 实例 | `*redis.Client` |
|
||||
| `Logger()` | 获取日志实例 | `*logger.Logger` |
|
||||
| `Run(addr ...string)` | 启动服务 | `error` |
|
||||
|
||||
### Run 方法行为
|
||||
|
||||
- **非生产环境**:优先使用传入的地址,否则使用配置文件地址
|
||||
- **生产环境**:始终使用配置文件地址
|
||||
|
||||
```go
|
||||
// 使用配置文件地址
|
||||
app.Run()
|
||||
|
||||
// 非生产环境使用 :3000,生产环境仍用配置文件地址
|
||||
app.Run(":3000")
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `RUN_ENV` | 运行环境 (`LOCAL`, `DEVELOPMENT`, `PRODUCTION`) | `LOCAL` |
|
||||
| `CFG_PATH` | 配置文件路径 | `config/config.yml` |
|
||||
|
||||
## 日志使用
|
||||
|
||||
```go
|
||||
// 基本用法
|
||||
app.Logger().Info(ctx, "message")
|
||||
app.Logger().Error(ctx, "error message")
|
||||
app.Logger().Warn(ctx, "warning message")
|
||||
app.Logger().Debug(ctx, "debug message")
|
||||
|
||||
// 带键值对
|
||||
app.Logger().Info(ctx, "user logged in", "user_id", "123", "ip", "192.168.1.1")
|
||||
```
|
||||
|
||||
日志格式会根据环境自动切换:
|
||||
- **Local**: Console 格式,便于阅读
|
||||
- **Development/Production**: JSON 格式,便于日志收集
|
||||
|
||||
## 上下文用户信息
|
||||
|
||||
使用 `web` 包在上下文中传递用户信息:
|
||||
|
||||
```go
|
||||
import "git.hujye.com/infrastructure/go-web-gin/web"
|
||||
|
||||
// 设置用户信息
|
||||
ctx = web.SetUserID(ctx, "user-123")
|
||||
ctx = web.SetUserName(ctx, "john")
|
||||
ctx = web.SetTrace(ctx, "trace-456")
|
||||
ctx = web.SetFromIP(ctx, "192.168.1.1")
|
||||
|
||||
// 获取用户信息
|
||||
userID := web.GetUserID(ctx)
|
||||
userName := web.GetUserName(ctx)
|
||||
|
||||
// 或一次性构建
|
||||
userInfo := web.NewUserInfo().
|
||||
WithUserID("user-123").
|
||||
WithUserName("john").
|
||||
WithTrace("trace-456").
|
||||
WithFromIP("192.168.1.1")
|
||||
ctx = web.ToContext(ctx, userInfo)
|
||||
```
|
||||
|
||||
日志会自动从上下文中提取用户信息并记录。
|
||||
|
||||
## 使用 svr 包直接操作 Gin
|
||||
|
||||
如果需要直接使用 Gin 引擎单例:
|
||||
|
||||
```go
|
||||
import "git.hujye.com/infrastructure/go-web-gin/svr"
|
||||
|
||||
// 获取引擎
|
||||
engine := svr.GetEngine()
|
||||
|
||||
// 快捷方法
|
||||
svr.Use(middleware...)
|
||||
svr.GET("/ping", handler)
|
||||
svr.POST("/users", handler)
|
||||
svr.PUT("/users/:id", handler)
|
||||
svr.DELETE("/users/:id", handler)
|
||||
|
||||
// 路由组
|
||||
api := svr.Group("/api/v1")
|
||||
api.GET("/users", handler)
|
||||
|
||||
// 启动服务
|
||||
svr.Run(":8080")
|
||||
```
|
||||
|
||||
## 配置读取
|
||||
|
||||
```go
|
||||
import "git.hujye.com/infrastructure/go-web-gin/config"
|
||||
|
||||
// 获取完整配置
|
||||
cfg := config.Get()
|
||||
|
||||
// 访问配置项
|
||||
addr := cfg.GetAddr() // host:port
|
||||
isDebug := cfg.IsDebug() // 是否 debug 模式
|
||||
isRelease := cfg.IsRelease() // 是否 release 模式
|
||||
|
||||
// 动态读取配置(支持嵌套键)
|
||||
dbHost := config.GetString("mysql.host")
|
||||
dbPort := config.GetInt("mysql.port")
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
go-web-gin/
|
||||
├── app.go # 应用核心
|
||||
├── app_test.go # 测试套件
|
||||
├── config/
|
||||
│ └── config.go # 配置管理
|
||||
├── database/
|
||||
│ ├── mysql.go # MySQL 单例
|
||||
│ └── redis.go # Redis 单例
|
||||
├── env/
|
||||
│ └── env.go # 环境变量
|
||||
├── logger/
|
||||
│ ├── logger.go # 日志核心
|
||||
│ ├── encoder.go # 编码器
|
||||
│ └── struct.go # 结构定义
|
||||
├── server/
|
||||
│ └── server.go # 服务器封装
|
||||
├── svr/
|
||||
│ └── server.go # Gin 单例
|
||||
└── web/
|
||||
└── user.go # 上下文用户信息
|
||||
```
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
go test -v ./...
|
||||
|
||||
# 运行特定测试套件
|
||||
go test -v ./... -run TestAppRunSuite
|
||||
```
|
||||
|
||||
## 依赖
|
||||
|
||||
- [gin-gonic/gin](https://github.com/gin-gonic/gin) - Web 框架
|
||||
- [uber-go/zap](https://github.com/uber-go/zap) - 高性能日志
|
||||
- [go-gorm/gorm](https://github.com/go-gorm/gorm) - ORM
|
||||
- [redis/go-redis](https://github.com/redis/go-redis) - Redis 客户端
|
||||
- [spf13/viper](https://github.com/spf13/viper) - 配置管理
|
||||
- [stretchr/testify](https://github.com/stretchr/testify) - 测试框架
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
## Author
|
||||
|
||||
**hujye**
|
||||
|
||||
- Email: hujie@hujye.com
|
||||
55
app.go
55
app.go
@@ -4,13 +4,20 @@ import (
|
||||
"log"
|
||||
|
||||
"git.hujye.com/infrastructure/go-web-gin/config"
|
||||
"git.hujye.com/infrastructure/go-web-gin/server"
|
||||
"git.hujye.com/infrastructure/go-web-gin/database"
|
||||
"git.hujye.com/infrastructure/go-web-gin/logger"
|
||||
"git.hujye.com/infrastructure/go-web-gin/svr"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// App represents the application
|
||||
type App struct {
|
||||
server *server.Server
|
||||
engine *gin.Engine
|
||||
db *gorm.DB
|
||||
rdb *redis.Client
|
||||
logger *logger.Logger
|
||||
}
|
||||
|
||||
// New creates a new App instance and loads config file
|
||||
@@ -21,21 +28,57 @@ func New() *App {
|
||||
}
|
||||
|
||||
return &App{
|
||||
server: server.New(),
|
||||
engine: svr.GetEngine(),
|
||||
logger: logger.GetLogger(),
|
||||
}
|
||||
}
|
||||
|
||||
// UseMiddleware registers global middleware
|
||||
func (a *App) UseMiddleware(middleware ...gin.HandlerFunc) {
|
||||
a.server.Engine().Use(middleware...)
|
||||
a.engine.Use(middleware...)
|
||||
}
|
||||
|
||||
// RegisterRoutes registers routes with the given handler function
|
||||
func (a *App) RegisterRoutes(registerFunc func(*gin.Engine)) {
|
||||
registerFunc(a.server.Engine())
|
||||
registerFunc(a.engine)
|
||||
}
|
||||
|
||||
// InitMySQL initializes the MySQL database connection
|
||||
func (a *App) InitMySQL() *gorm.DB {
|
||||
a.db = database.GetDB()
|
||||
return a.db
|
||||
}
|
||||
|
||||
// DB returns the gorm.DB instance
|
||||
func (a *App) DB() *gorm.DB {
|
||||
return a.db
|
||||
}
|
||||
|
||||
// InitRedis initializes the Redis connection
|
||||
func (a *App) InitRedis() *redis.Client {
|
||||
a.rdb = database.GetRedis()
|
||||
return a.rdb
|
||||
}
|
||||
|
||||
// Redis returns the redis.Client instance
|
||||
func (a *App) Redis() *redis.Client {
|
||||
return a.rdb
|
||||
}
|
||||
|
||||
// Logger returns the logger.Logger instance
|
||||
func (a *App) Logger() *logger.Logger {
|
||||
return a.logger
|
||||
}
|
||||
|
||||
// Run starts the application server
|
||||
func (a *App) Run(addr ...string) error {
|
||||
return a.server.Run(addr...)
|
||||
listenAddr := config.Get().GetAddr()
|
||||
|
||||
// Non-production: use provided address if given, otherwise use config
|
||||
if !config.Get().IsRelease() && len(addr) > 0 && addr[0] != "" {
|
||||
listenAddr = addr[0]
|
||||
}
|
||||
|
||||
a.logger.Info(nil, "Server starting", "addr", listenAddr, "mode", config.Get().Server.Mode)
|
||||
return a.engine.Run(listenAddr)
|
||||
}
|
||||
|
||||
356
app_test.go
Normal file
356
app_test.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package go_web_gin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
// AppTestSuite is the test suite for App
|
||||
type AppTestSuite struct {
|
||||
suite.Suite
|
||||
app *App
|
||||
}
|
||||
|
||||
// SetupSuite runs once before all tests
|
||||
func (s *AppTestSuite) SetupSuite() {
|
||||
gin.SetMode(gin.TestMode)
|
||||
s.app = New()
|
||||
}
|
||||
|
||||
// TearDownSuite runs once after all tests
|
||||
func (s *AppTestSuite) TearDownSuite() {
|
||||
// Cleanup if needed
|
||||
}
|
||||
|
||||
// SetupTest runs before each test
|
||||
func (s *AppTestSuite) SetupTest() {
|
||||
// Reset state if needed
|
||||
}
|
||||
|
||||
// TearDownTest runs after each test
|
||||
func (s *AppTestSuite) TearDownTest() {
|
||||
// Cleanup after each test if needed
|
||||
}
|
||||
|
||||
// TestNew tests the New function
|
||||
func (s *AppTestSuite) TestNew() {
|
||||
app := New()
|
||||
s.NotNil(app, "New should return a non-nil App")
|
||||
s.NotNil(app.engine, "engine should be initialized")
|
||||
s.NotNil(app.logger, "logger should be initialized")
|
||||
}
|
||||
|
||||
// TestNewSingleton tests that svr.GetEngine returns the same instance
|
||||
func (s *AppTestSuite) TestNewSingleton() {
|
||||
app1 := New()
|
||||
app2 := New()
|
||||
s.Equal(app1.engine, app2.engine, "engine should be singleton")
|
||||
}
|
||||
|
||||
// TestUseMiddleware tests the UseMiddleware method
|
||||
func (s *AppTestSuite) TestUseMiddleware() {
|
||||
middlewareCalled := false
|
||||
middleware := func(c *gin.Context) {
|
||||
middlewareCalled = true
|
||||
c.Next()
|
||||
}
|
||||
|
||||
s.app.UseMiddleware(middleware)
|
||||
|
||||
// Create a test route to verify middleware
|
||||
s.app.engine.GET("/test-middleware", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test-middleware", nil)
|
||||
w := httptest.NewRecorder()
|
||||
s.app.engine.ServeHTTP(w, req)
|
||||
|
||||
s.True(middlewareCalled, "middleware should be called")
|
||||
}
|
||||
|
||||
// TestRegisterRoutes tests the RegisterRoutes method
|
||||
func (s *AppTestSuite) TestRegisterRoutes() {
|
||||
app := New()
|
||||
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.GET("/test-route", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
})
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test-route", nil)
|
||||
w := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
s.Contains(w.Body.String(), "ok")
|
||||
}
|
||||
|
||||
// TestRegisterRoutesMultiple tests registering multiple routes
|
||||
func (s *AppTestSuite) TestRegisterRoutesMultiple() {
|
||||
app := New()
|
||||
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.GET("/route1", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"route": 1})
|
||||
})
|
||||
e.POST("/route2", func(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, gin.H{"route": 2})
|
||||
})
|
||||
e.PUT("/route3", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"route": 3})
|
||||
})
|
||||
})
|
||||
|
||||
// Test GET
|
||||
req1 := httptest.NewRequest("GET", "/route1", nil)
|
||||
w1 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w1, req1)
|
||||
s.Equal(http.StatusOK, w1.Code)
|
||||
|
||||
// Test POST
|
||||
req2 := httptest.NewRequest("POST", "/route2", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w2, req2)
|
||||
s.Equal(http.StatusCreated, w2.Code)
|
||||
|
||||
// Test PUT
|
||||
req3 := httptest.NewRequest("PUT", "/route3", nil)
|
||||
w3 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w3, req3)
|
||||
s.Equal(http.StatusOK, w3.Code)
|
||||
}
|
||||
|
||||
// TestDB tests the DB method returns nil when not initialized
|
||||
func (s *AppTestSuite) TestDB() {
|
||||
app := New()
|
||||
s.Nil(app.DB(), "DB should be nil when not initialized")
|
||||
}
|
||||
|
||||
// TestRedis tests the Redis method returns nil when not initialized
|
||||
func (s *AppTestSuite) TestRedis() {
|
||||
app := New()
|
||||
s.Nil(app.Redis(), "Redis should be nil when not initialized")
|
||||
}
|
||||
|
||||
// TestLogger tests the Logger method
|
||||
func (s *AppTestSuite) TestLogger() {
|
||||
app := New()
|
||||
logger := app.Logger()
|
||||
s.NotNil(logger, "Logger should not be nil")
|
||||
}
|
||||
|
||||
// TestLoggerNotNil tests that logger is always initialized
|
||||
func (s *AppTestSuite) TestLoggerNotNil() {
|
||||
s.NotNil(s.app.Logger(), "Logger should always be initialized in New()")
|
||||
}
|
||||
|
||||
// TestLoggerMethods tests that logger methods work
|
||||
func (s *AppTestSuite) TestLoggerMethods() {
|
||||
s.NotPanics(func() {
|
||||
s.app.Logger().Info(nil, "Test info message")
|
||||
s.app.Logger().Debug(nil, "Test debug message")
|
||||
s.app.Logger().Warn(nil, "Test warn message")
|
||||
}, "Logger methods should not panic")
|
||||
}
|
||||
|
||||
// TestLoggerWithContext tests logger with context
|
||||
func (s *AppTestSuite) TestLoggerWithContext() {
|
||||
ctx := context.Background()
|
||||
s.NotPanics(func() {
|
||||
s.app.Logger().Info(ctx, "Test with context")
|
||||
}, "Logger should work with context")
|
||||
}
|
||||
|
||||
// TestRunInvalidAddress tests Run with invalid address
|
||||
func (s *AppTestSuite) TestRunInvalidAddress() {
|
||||
app := New()
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.GET("/ping", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"pong": true})
|
||||
})
|
||||
})
|
||||
|
||||
// Use a channel to handle the async nature of Run
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- app.Run("invalid:address:format")
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
s.Error(err, "Run should return error for invalid address")
|
||||
case <-time.After(2 * time.Second):
|
||||
// Server might start, which is fine for this test
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTPMethod tests various HTTP methods
|
||||
func (s *AppTestSuite) TestHTTPMethod() {
|
||||
app := New()
|
||||
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.DELETE("/delete", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"deleted": true})
|
||||
})
|
||||
e.PATCH("/patch", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"patched": true})
|
||||
})
|
||||
e.OPTIONS("/options", func(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
})
|
||||
e.HEAD("/head", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
})
|
||||
|
||||
// Test DELETE
|
||||
req := httptest.NewRequest("DELETE", "/delete", nil)
|
||||
w := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w, req)
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
// Test PATCH
|
||||
req2 := httptest.NewRequest("PATCH", "/patch", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w2, req2)
|
||||
s.Equal(http.StatusOK, w2.Code)
|
||||
|
||||
// Test OPTIONS
|
||||
req3 := httptest.NewRequest("OPTIONS", "/options", nil)
|
||||
w3 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w3, req3)
|
||||
s.Equal(http.StatusNoContent, w3.Code)
|
||||
|
||||
// Test HEAD
|
||||
req4 := httptest.NewRequest("HEAD", "/head", nil)
|
||||
w4 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w4, req4)
|
||||
s.Equal(http.StatusOK, w4.Code)
|
||||
}
|
||||
|
||||
// TestRouteGroup tests route groups
|
||||
func (s *AppTestSuite) TestRouteGroup() {
|
||||
app := New()
|
||||
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
api := e.Group("/api")
|
||||
{
|
||||
api.GET("/users", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"users": []string{}})
|
||||
})
|
||||
api.GET("/posts", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"posts": []string{}})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
req1 := httptest.NewRequest("GET", "/api/users", nil)
|
||||
w1 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w1, req1)
|
||||
s.Equal(http.StatusOK, w1.Code)
|
||||
|
||||
req2 := httptest.NewRequest("GET", "/api/posts", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w2, req2)
|
||||
s.Equal(http.StatusOK, w2.Code)
|
||||
}
|
||||
|
||||
// TestMiddlewareChain tests middleware chain
|
||||
func (s *AppTestSuite) TestMiddlewareChain() {
|
||||
app := New()
|
||||
order := []string{}
|
||||
|
||||
app.UseMiddleware(func(c *gin.Context) {
|
||||
order = append(order, "middleware1-before")
|
||||
c.Next()
|
||||
order = append(order, "middleware1-after")
|
||||
})
|
||||
|
||||
app.UseMiddleware(func(c *gin.Context) {
|
||||
order = append(order, "middleware2-before")
|
||||
c.Next()
|
||||
order = append(order, "middleware2-after")
|
||||
})
|
||||
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.GET("/chain", func(c *gin.Context) {
|
||||
order = append(order, "handler")
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/chain", nil)
|
||||
w := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w, req)
|
||||
|
||||
expected := []string{
|
||||
"middleware1-before",
|
||||
"middleware2-before",
|
||||
"handler",
|
||||
"middleware2-after",
|
||||
"middleware1-after",
|
||||
}
|
||||
s.Equal(expected, order, "middleware should execute in correct order")
|
||||
}
|
||||
|
||||
// TestQueryParam tests query parameters
|
||||
func (s *AppTestSuite) TestQueryParam() {
|
||||
app := New()
|
||||
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.GET("/search", func(c *gin.Context) {
|
||||
q := c.Query("q")
|
||||
c.JSON(http.StatusOK, gin.H{"query": q})
|
||||
})
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/search?q=test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
s.Contains(w.Body.String(), "test")
|
||||
}
|
||||
|
||||
// TestPathParam tests path parameters
|
||||
func (s *AppTestSuite) TestPathParam() {
|
||||
app := New()
|
||||
|
||||
app.RegisterRoutes(func(e *gin.Engine) {
|
||||
e.GET("/users/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
c.JSON(http.StatusOK, gin.H{"user_id": id})
|
||||
})
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/users/123", nil)
|
||||
w := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
s.Contains(w.Body.String(), "123")
|
||||
}
|
||||
|
||||
// TestNotFound tests 404 handling
|
||||
func (s *AppTestSuite) TestNotFound() {
|
||||
app := New()
|
||||
|
||||
req := httptest.NewRequest("GET", "/nonexistent", nil)
|
||||
w := httptest.NewRecorder()
|
||||
app.engine.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
// TestAppRunSuite runs the test suite
|
||||
func TestAppRunSuite(t *testing.T) {
|
||||
suite.Run(t, new(AppTestSuite))
|
||||
}
|
||||
@@ -18,6 +18,8 @@ type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
App AppConfig `mapstructure:"app"`
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
MySQL MySQLConfig `mapstructure:"mysql"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
}
|
||||
|
||||
// ServerConfig represents server configuration
|
||||
@@ -44,6 +46,33 @@ type LogConfig struct {
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// MySQLConfig represents mysql configuration
|
||||
type MySQLConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
DBName string `mapstructure:"db_name"`
|
||||
Charset string `mapstructure:"charset"`
|
||||
ParseTime bool `mapstructure:"parse_time"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||||
ConnMaxLifetime int `mapstructure:"conn_max_lifetime"` // seconds
|
||||
}
|
||||
|
||||
// RedisConfig represents redis configuration
|
||||
type RedisConfig struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
PoolSize int `mapstructure:"pool_size"`
|
||||
MinIdleConns int `mapstructure:"min_idle_conns"`
|
||||
MaxRetries int `mapstructure:"max_retries"`
|
||||
DialTimeout int `mapstructure:"dial_timeout"` // seconds
|
||||
ReadTimeout int `mapstructure:"read_timeout"` // seconds
|
||||
WriteTimeout int `mapstructure:"write_timeout"` // seconds
|
||||
}
|
||||
|
||||
// Load loads configuration from environment variable CFG_PATH
|
||||
// Default path is config/config.yml
|
||||
func Load() (*Config, error) {
|
||||
@@ -94,6 +123,29 @@ func Get() *Config {
|
||||
MaxAge: 28,
|
||||
Compress: true,
|
||||
},
|
||||
MySQL: MySQLConfig{
|
||||
Host: "127.0.0.1",
|
||||
Port: 3306,
|
||||
User: "root",
|
||||
Password: "",
|
||||
DBName: "test",
|
||||
Charset: "utf8mb4",
|
||||
ParseTime: true,
|
||||
MaxIdleConns: 10,
|
||||
MaxOpenConns: 100,
|
||||
ConnMaxLifetime: 3600,
|
||||
},
|
||||
Redis: RedisConfig{
|
||||
Addr: "127.0.0.1:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
PoolSize: 10,
|
||||
MinIdleConns: 5,
|
||||
MaxRetries: 3,
|
||||
DialTimeout: 5,
|
||||
ReadTimeout: 3,
|
||||
WriteTimeout: 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
return globalConfig
|
||||
|
||||
137
database/mysql.go
Normal file
137
database/mysql.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.hujye.com/infrastructure/go-web-gin/config"
|
||||
"git.hujye.com/infrastructure/go-web-gin/logger"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
gormlogger "gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// gormLogger implements gorm/logger.Interface using project's zap logger
|
||||
type gormLogger struct {
|
||||
logLevel gormlogger.LogLevel
|
||||
}
|
||||
|
||||
// newGormLogger creates a new gorm logger
|
||||
func newGormLogger(level gormlogger.LogLevel) *gormLogger {
|
||||
return &gormLogger{logLevel: level}
|
||||
}
|
||||
|
||||
// LogMode sets log level
|
||||
func (l *gormLogger) LogMode(level gormlogger.LogLevel) gormlogger.Interface {
|
||||
newLogger := *l
|
||||
newLogger.logLevel = level
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
// Info logs info messages
|
||||
func (l *gormLogger) Info(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= gormlogger.Info {
|
||||
logger.GetLogger().Info(ctx, fmt.Sprintf("gorm: %s", fmt.Sprintf(msg, data...)))
|
||||
}
|
||||
}
|
||||
|
||||
// Warn logs warn messages
|
||||
func (l *gormLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= gormlogger.Warn {
|
||||
logger.GetLogger().Warn(ctx, fmt.Sprintf("gorm: %s", fmt.Sprintf(msg, data...)))
|
||||
}
|
||||
}
|
||||
|
||||
// Error logs error messages
|
||||
func (l *gormLogger) Error(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= gormlogger.Error {
|
||||
logger.GetLogger().Error(ctx, fmt.Sprintf("gorm: %s", fmt.Sprintf(msg, data...)))
|
||||
}
|
||||
}
|
||||
|
||||
// Trace logs sql query with execution time
|
||||
func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
||||
if l.logLevel <= gormlogger.Silent {
|
||||
return
|
||||
}
|
||||
|
||||
elapsed := time.Since(begin)
|
||||
sql, rows := fc()
|
||||
|
||||
switch {
|
||||
case err != nil && l.logLevel >= gormlogger.Error:
|
||||
logger.GetLogger().Error(ctx, "gorm query error", "duration", elapsed.Milliseconds(), "sql", sql, "rows", rows, "error", err.Error())
|
||||
case elapsed > 200*time.Millisecond && l.logLevel >= gormlogger.Warn:
|
||||
logger.GetLogger().Warn(ctx, "gorm slow query", "duration", elapsed.Milliseconds(), "sql", sql, "rows", rows)
|
||||
case l.logLevel >= gormlogger.Info:
|
||||
logger.GetLogger().Debug(ctx, "gorm query", "duration", elapsed.Milliseconds(), "sql", sql, "rows", rows)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDB returns the gorm.DB singleton instance
|
||||
// It will initialize the connection on first call
|
||||
func GetDB() *gorm.DB {
|
||||
once.Do(func() {
|
||||
db = initDB()
|
||||
})
|
||||
return db
|
||||
}
|
||||
|
||||
// initDB initializes and returns a new gorm.DB connection
|
||||
func initDB() *gorm.DB {
|
||||
cfg := config.Get().MySQL
|
||||
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%t&loc=Local",
|
||||
cfg.User,
|
||||
cfg.Password,
|
||||
cfg.Host,
|
||||
cfg.Port,
|
||||
cfg.DBName,
|
||||
cfg.Charset,
|
||||
cfg.ParseTime,
|
||||
)
|
||||
|
||||
var logLevel gormlogger.LogLevel
|
||||
if config.Get().IsDebug() {
|
||||
logLevel = gormlogger.Info
|
||||
} else {
|
||||
logLevel = gormlogger.Silent
|
||||
}
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: newGormLogger(logLevel),
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to connect to database: %v", err))
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to get database instance: %v", err))
|
||||
}
|
||||
|
||||
sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(cfg.ConnMaxLifetime) * time.Second)
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func Close() error {
|
||||
if db != nil {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
69
database/redis.go
Normal file
69
database/redis.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.hujye.com/infrastructure/go-web-gin/config"
|
||||
"git.hujye.com/infrastructure/go-web-gin/logger"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var (
|
||||
rdb *redis.Client
|
||||
redisOnce sync.Once
|
||||
)
|
||||
|
||||
// GetRedis returns the redis.Client singleton instance.
|
||||
// It will initialize the connection on first call
|
||||
func GetRedis() *redis.Client {
|
||||
redisOnce.Do(func() {
|
||||
rdb = initRedis()
|
||||
})
|
||||
return rdb
|
||||
}
|
||||
|
||||
// initRedis initializes and returns a new redis.Client connection.
|
||||
// It will panic if connection fails
|
||||
func initRedis() *redis.Client {
|
||||
cfg := config.Get().Redis
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.Addr,
|
||||
Password: cfg.Password,
|
||||
DB: cfg.DB,
|
||||
PoolSize: cfg.PoolSize,
|
||||
MinIdleConns: cfg.MinIdleConns,
|
||||
MaxRetries: cfg.MaxRetries,
|
||||
DialTimeout: time.Duration(cfg.DialTimeout) * time.Second,
|
||||
ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
logger.GetLogger().Error(nil, "redis connect failed", "addr", cfg.Addr, "error", err.Error())
|
||||
panic(fmt.Sprintf("failed to connect to redis: %v", err))
|
||||
}
|
||||
|
||||
logger.GetLogger().Info(nil, "redis connected successfully", "addr", cfg.Addr, "db", cfg.DB)
|
||||
|
||||
return rdb
|
||||
}
|
||||
|
||||
// CloseRedis closes the redis connection.
|
||||
// Returns error if close fails, nil otherwise
|
||||
func CloseRedis() error {
|
||||
if rdb != nil {
|
||||
if err := rdb.Close(); err != nil {
|
||||
logger.GetLogger().Error(nil, "redis close failed", "error", err.Error())
|
||||
return err
|
||||
}
|
||||
logger.GetLogger().Info(nil, "redis connection closed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
12
go.mod
12
go.mod
@@ -4,26 +4,35 @@ go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
@@ -43,13 +52,14 @@ require (
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -2,7 +2,6 @@ package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -125,53 +124,68 @@ func getUserFields(ctx context.Context) []zap.Field {
|
||||
}
|
||||
|
||||
// Info logs an info message
|
||||
func (l *Logger) Info(ctx context.Context, msg string, values ...interface{}) {
|
||||
func (l *Logger) Info(ctx context.Context, msg string, keyValues ...interface{}) {
|
||||
allFields := getUserFields(ctx)
|
||||
formattedMsg := fmt.Sprintf(msg, values...)
|
||||
if len(values) > 0 {
|
||||
allFields = append(allFields, zap.String("msg", formattedMsg))
|
||||
for i := 0; i < len(keyValues); i += 2 {
|
||||
if i+1 < len(keyValues) {
|
||||
if key, ok := keyValues[i].(string); ok {
|
||||
allFields = append(allFields, zap.Any(key, keyValues[i+1]))
|
||||
}
|
||||
l.logger.Info(formattedMsg, allFields...)
|
||||
}
|
||||
}
|
||||
l.logger.Info(msg, allFields...)
|
||||
}
|
||||
|
||||
// Error logs an error message
|
||||
func (l *Logger) Error(ctx context.Context, msg string, values ...interface{}) {
|
||||
func (l *Logger) Error(ctx context.Context, msg string, keyValues ...interface{}) {
|
||||
allFields := getUserFields(ctx)
|
||||
formattedMsg := fmt.Sprintf(msg, values...)
|
||||
if len(values) > 0 {
|
||||
allFields = append(allFields, zap.String("msg", formattedMsg))
|
||||
for i := 0; i < len(keyValues); i += 2 {
|
||||
if i+1 < len(keyValues) {
|
||||
if key, ok := keyValues[i].(string); ok {
|
||||
allFields = append(allFields, zap.Any(key, keyValues[i+1]))
|
||||
}
|
||||
l.logger.Error(formattedMsg, allFields...)
|
||||
}
|
||||
}
|
||||
l.logger.Error(msg, allFields...)
|
||||
}
|
||||
|
||||
// Debug logs a debug message
|
||||
func (l *Logger) Debug(ctx context.Context, msg string, values ...interface{}) {
|
||||
func (l *Logger) Debug(ctx context.Context, msg string, keyValues ...interface{}) {
|
||||
allFields := getUserFields(ctx)
|
||||
formattedMsg := fmt.Sprintf(msg, values...)
|
||||
if len(values) > 0 {
|
||||
allFields = append(allFields, zap.String("msg", formattedMsg))
|
||||
for i := 0; i < len(keyValues); i += 2 {
|
||||
if i+1 < len(keyValues) {
|
||||
if key, ok := keyValues[i].(string); ok {
|
||||
allFields = append(allFields, zap.Any(key, keyValues[i+1]))
|
||||
}
|
||||
l.logger.Debug(formattedMsg, allFields...)
|
||||
}
|
||||
}
|
||||
l.logger.Debug(msg, allFields...)
|
||||
}
|
||||
|
||||
// Warn logs a warning message
|
||||
func (l *Logger) Warn(ctx context.Context, msg string, values ...interface{}) {
|
||||
func (l *Logger) Warn(ctx context.Context, msg string, keyValues ...interface{}) {
|
||||
allFields := getUserFields(ctx)
|
||||
formattedMsg := fmt.Sprintf(msg, values...)
|
||||
if len(values) > 0 {
|
||||
allFields = append(allFields, zap.String("msg", formattedMsg))
|
||||
for i := 0; i < len(keyValues); i += 2 {
|
||||
if i+1 < len(keyValues) {
|
||||
if key, ok := keyValues[i].(string); ok {
|
||||
allFields = append(allFields, zap.Any(key, keyValues[i+1]))
|
||||
}
|
||||
l.logger.Warn(formattedMsg, allFields...)
|
||||
}
|
||||
}
|
||||
l.logger.Warn(msg, allFields...)
|
||||
}
|
||||
|
||||
// Fatal logs a fatal message and exits
|
||||
func (l *Logger) Fatal(ctx context.Context, msg string, values ...interface{}) {
|
||||
func (l *Logger) Fatal(ctx context.Context, msg string, keyValues ...interface{}) {
|
||||
allFields := getUserFields(ctx)
|
||||
formattedMsg := fmt.Sprintf(msg, values...)
|
||||
if len(values) > 0 {
|
||||
allFields = append(allFields, zap.String("msg", formattedMsg))
|
||||
for i := 0; i < len(keyValues); i += 2 {
|
||||
if i+1 < len(keyValues) {
|
||||
if key, ok := keyValues[i].(string); ok {
|
||||
allFields = append(allFields, zap.Any(key, keyValues[i+1]))
|
||||
}
|
||||
l.logger.Fatal(formattedMsg, allFields...)
|
||||
}
|
||||
}
|
||||
l.logger.Fatal(msg, allFields...)
|
||||
}
|
||||
|
||||
// Sync flushes any buffered log entries
|
||||
|
||||
56
svr/server.go
Normal file
56
svr/server.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package svr
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
engine *gin.Engine
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// GetEngine returns the gin.Engine singleton instance.
|
||||
// It will initialize the engine on first call
|
||||
func GetEngine() *gin.Engine {
|
||||
once.Do(func() {
|
||||
engine = gin.New()
|
||||
})
|
||||
return engine
|
||||
}
|
||||
|
||||
// Use registers global middleware
|
||||
func Use(middleware ...gin.HandlerFunc) {
|
||||
GetEngine().Use(middleware...)
|
||||
}
|
||||
|
||||
// Group creates a new router group
|
||||
func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
|
||||
return GetEngine().Group(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// GET is a shortcut for router.Handle("GET", path, handlers)
|
||||
func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return GetEngine().GET(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// POST is a shortcut for router.Handle("POST", path, handlers)
|
||||
func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return GetEngine().POST(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// PUT is a shortcut for router.Handle("PUT", path, handlers)
|
||||
func PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return GetEngine().PUT(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// DELETE is a shortcut for router.Handle("DELETE", path, handlers)
|
||||
func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return GetEngine().DELETE(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// Run starts the server
|
||||
func Run(addr ...string) error {
|
||||
return GetEngine().Run(addr...)
|
||||
}
|
||||
Reference in New Issue
Block a user