Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d454d8e143 |
157
README.md
157
README.md
@@ -104,20 +104,27 @@ now := datetime.Now()
|
|||||||
str := datetime.FormatDateTime(now)
|
str := datetime.FormatDateTime(now)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### HTTP响应
|
#### HTTP响应(Handler黑盒模式)
|
||||||
```go
|
```go
|
||||||
import "git.toowon.com/jimmy/go-common/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
|
)
|
||||||
|
|
||||||
http.Success(w, data)
|
// 使用Handler(黑盒模式)
|
||||||
http.SuccessPage(w, list, total, page, pageSize)
|
func GetUser(h *commonhttp.Handler) {
|
||||||
http.Error(w, 1001, "业务错误")
|
id := h.GetQueryInt64("id", 0) // 无需传递r
|
||||||
|
h.Success(data) // 无需传递w
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 中间件
|
#### 中间件
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"git.toowon.com/jimmy/go-common/middleware"
|
"git.toowon.com/jimmy/go-common/middleware"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CORS + 时区中间件
|
// CORS + 时区中间件
|
||||||
@@ -127,8 +134,10 @@ chain := middleware.NewChain(
|
|||||||
)
|
)
|
||||||
handler := chain.ThenFunc(yourHandler)
|
handler := chain.ThenFunc(yourHandler)
|
||||||
|
|
||||||
// 在处理器中获取时区
|
// 在Handler中获取时区
|
||||||
timezone := http.GetTimezone(r)
|
func handler(h *commonhttp.Handler) {
|
||||||
|
timezone := h.GetTimezone()
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 配置管理
|
#### 配置管理
|
||||||
@@ -144,77 +153,95 @@ redisAddr := cfg.GetRedisAddr()
|
|||||||
corsConfig := cfg.GetCORS()
|
corsConfig := cfg.GetCORS()
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 文件上传和查看
|
#### 文件上传和查看(推荐使用工厂黑盒模式)
|
||||||
```go
|
```go
|
||||||
import "git.toowon.com/jimmy/go-common/storage"
|
import (
|
||||||
|
"context"
|
||||||
|
"git.toowon.com/jimmy/go-common/factory"
|
||||||
|
)
|
||||||
|
|
||||||
// 创建存储实例
|
fac, _ := factory.NewFactoryFromFile("./config.json")
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 黑盒模式(推荐,自动选择OSS或MinIO)
|
||||||
|
file, _ := os.Open("test.jpg")
|
||||||
|
url, _ := fac.UploadFile(ctx, "images/test.jpg", file, "image/jpeg")
|
||||||
|
|
||||||
|
// 获取文件URL
|
||||||
|
url, _ := fac.GetFileURL("images/test.jpg", 0) // 永久有效
|
||||||
|
url, _ := fac.GetFileURL("images/test.jpg", 3600) // 1小时后过期
|
||||||
|
|
||||||
|
// 或使用存储处理器(需要HTTP处理器时)
|
||||||
storage, _ := storage.NewStorage(storage.StorageTypeOSS, cfg)
|
storage, _ := storage.NewStorage(storage.StorageTypeOSS, cfg)
|
||||||
|
uploadHandler := storage.NewUploadHandler(...)
|
||||||
// 创建上传处理器
|
|
||||||
uploadHandler := storage.NewUploadHandler(storage.UploadHandlerConfig{
|
|
||||||
Storage: storage,
|
|
||||||
MaxFileSize: 10 * 1024 * 1024,
|
|
||||||
AllowedExts: []string{".jpg", ".png"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// 创建代理查看处理器
|
|
||||||
proxyHandler := storage.NewProxyHandler(storage)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 邮件发送
|
#### 邮件发送(推荐使用工厂黑盒模式)
|
||||||
```go
|
|
||||||
import "git.toowon.com/jimmy/go-common/email"
|
|
||||||
|
|
||||||
// 从配置创建邮件发送器
|
|
||||||
mailer, _ := email.NewEmail(cfg.GetEmail())
|
|
||||||
|
|
||||||
// 发送邮件
|
|
||||||
mailer.SendSimple(
|
|
||||||
[]string{"recipient@example.com"},
|
|
||||||
"主题",
|
|
||||||
"正文",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 短信发送
|
|
||||||
```go
|
|
||||||
import "git.toowon.com/jimmy/go-common/sms"
|
|
||||||
|
|
||||||
// 从配置创建短信发送器
|
|
||||||
smsClient, _ := sms.NewSMS(cfg.GetSMS())
|
|
||||||
|
|
||||||
// 发送短信
|
|
||||||
smsClient.SendSimple(
|
|
||||||
[]string{"13800138000"},
|
|
||||||
map[string]string{"code": "123456"},
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 使用工厂直接获取客户端(推荐)
|
|
||||||
```go
|
```go
|
||||||
import "git.toowon.com/jimmy/go-common/factory"
|
import "git.toowon.com/jimmy/go-common/factory"
|
||||||
|
|
||||||
// 方式1:直接从配置文件创建工厂(最推荐)
|
|
||||||
fac, _ := factory.NewFactoryFromFile("./config.json")
|
fac, _ := factory.NewFactoryFromFile("./config.json")
|
||||||
|
|
||||||
// 直接获取数据库对象(已初始化,可直接使用)
|
// 黑盒模式(推荐)
|
||||||
db, _ := fac.GetDatabase()
|
fac.SendEmail([]string{"user@example.com"}, "主题", "正文")
|
||||||
db.Find(&users) // 直接使用,无需再创建连接
|
fac.SendEmail([]string{"user@example.com"}, "主题", "纯文本", "<h1>HTML内容</h1>")
|
||||||
|
|
||||||
// 直接获取Redis客户端(已初始化,可直接使用)
|
// 或获取客户端对象(需要高级功能时)
|
||||||
redisClient, _ := fac.GetRedisClient()
|
|
||||||
val, _ := redisClient.Get(ctx, "key").Result()
|
|
||||||
|
|
||||||
// 直接获取已初始化的客户端(无需重复实现创建逻辑)
|
|
||||||
emailClient, _ := fac.GetEmailClient()
|
emailClient, _ := fac.GetEmailClient()
|
||||||
smsClient, _ := fac.GetSMSClient()
|
|
||||||
logger, _ := fac.GetLogger()
|
|
||||||
|
|
||||||
// 直接使用
|
|
||||||
emailClient.SendSimple(...)
|
emailClient.SendSimple(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 短信发送(推荐使用工厂黑盒模式)
|
||||||
|
```go
|
||||||
|
import "git.toowon.com/jimmy/go-common/factory"
|
||||||
|
|
||||||
|
fac, _ := factory.NewFactoryFromFile("./config.json")
|
||||||
|
|
||||||
|
// 黑盒模式(推荐)
|
||||||
|
fac.SendSMS([]string{"13800138000"}, map[string]string{"code": "123456"})
|
||||||
|
|
||||||
|
// 或获取客户端对象(需要高级功能时)
|
||||||
|
smsClient, _ := fac.GetSMSClient()
|
||||||
smsClient.SendSimple(...)
|
smsClient.SendSimple(...)
|
||||||
logger.Info("Application started")
|
```
|
||||||
|
|
||||||
|
#### 使用工厂(黑盒模式,推荐)
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.toowon.com/jimmy/go-common/factory"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从配置文件创建工厂(最推荐)
|
||||||
|
fac, _ := factory.NewFactoryFromFile("./config.json")
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 日志(黑盒模式,直接调用)
|
||||||
|
fac.LogInfo("用户登录成功")
|
||||||
|
fac.LogError("登录失败: %v", err)
|
||||||
|
|
||||||
|
// 邮件发送(黑盒模式,直接调用)
|
||||||
|
fac.SendEmail([]string{"user@example.com"}, "验证码", "您的验证码是:123456")
|
||||||
|
|
||||||
|
// 短信发送(黑盒模式,直接调用)
|
||||||
|
fac.SendSMS([]string{"13800138000"}, map[string]string{"code": "123456"})
|
||||||
|
|
||||||
|
// 文件上传(黑盒模式,自动选择OSS或MinIO)
|
||||||
|
file, _ := os.Open("test.jpg")
|
||||||
|
url, _ := fac.UploadFile(ctx, "images/test.jpg", file, "image/jpeg")
|
||||||
|
|
||||||
|
// 获取文件URL
|
||||||
|
url, _ := fac.GetFileURL("images/test.jpg", 0) // 永久有效
|
||||||
|
url, _ := fac.GetFileURL("images/test.jpg", 3600) // 1小时后过期
|
||||||
|
|
||||||
|
// Redis操作(黑盒模式,直接调用)
|
||||||
|
fac.RedisSet(ctx, "key", "value", time.Hour)
|
||||||
|
value, _ := fac.RedisGet(ctx, "key")
|
||||||
|
fac.RedisDelete(ctx, "key")
|
||||||
|
|
||||||
|
// 数据库(GORM已经很灵活,直接返回对象)
|
||||||
|
db, _ := fac.GetDatabase()
|
||||||
|
db.Find(&users)
|
||||||
```
|
```
|
||||||
|
|
||||||
更多示例请查看 [examples](./examples/) 目录。
|
更多示例请查看 [examples](./examples/) 目录。
|
||||||
|
|||||||
@@ -49,22 +49,29 @@ now := datetime.Now()
|
|||||||
str := datetime.FormatDateTime(now)
|
str := datetime.FormatDateTime(now)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### HTTP响应
|
#### HTTP响应(Handler黑盒模式)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "git.toowon.com/jimmy/go-common/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
|
)
|
||||||
|
|
||||||
http.Success(w, data)
|
func GetUser(h *commonhttp.Handler) {
|
||||||
http.SuccessPage(w, list, total, page, pageSize)
|
id := h.GetQueryInt64("id", 0)
|
||||||
http.Error(w, 1001, "业务错误")
|
h.Success(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 中间件
|
#### 中间件
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"git.toowon.com/jimmy/go-common/middleware"
|
"git.toowon.com/jimmy/go-common/middleware"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CORS + 时区中间件
|
// CORS + 时区中间件
|
||||||
@@ -72,10 +79,13 @@ chain := middleware.NewChain(
|
|||||||
middleware.CORS(),
|
middleware.CORS(),
|
||||||
middleware.Timezone,
|
middleware.Timezone,
|
||||||
)
|
)
|
||||||
handler := chain.ThenFunc(yourHandler)
|
|
||||||
|
|
||||||
// 在处理器中获取时区
|
handler := chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
timezone := http.GetTimezone(r)
|
h := commonhttp.NewHandler(w, r)
|
||||||
|
// 在Handler中获取时区
|
||||||
|
timezone := h.GetTimezone()
|
||||||
|
h.Success(data)
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 配置管理
|
#### 配置管理
|
||||||
|
|||||||
525
docs/factory.md
525
docs/factory.md
@@ -2,204 +2,249 @@
|
|||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
工厂工具提供了从配置直接创建已初始化的客户端对象的功能,避免调用方重复实现创建逻辑。
|
工厂工具提供了从配置直接创建已初始化客户端对象的功能,并提供了黑盒模式的便捷方法,让调用方无需关心底层实现细节,大大降低业务复杂度。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- 从配置直接创建已初始化的客户端对象
|
- **黑盒模式**:提供直接调用的方法,无需获取客户端对象
|
||||||
- 统一的工厂接口
|
- **延迟初始化**:所有客户端在首次使用时才创建
|
||||||
- 避免调用方重复实现创建逻辑
|
- **自动选择**:存储类型(OSS/MinIO)根据配置自动选择
|
||||||
|
- **统一接口**:所有操作通过工厂方法调用
|
||||||
|
- **向后兼容**:保留 `GetXXX()` 方法,需要时可获取对象
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
### 1. 从配置文件直接创建工厂(推荐)
|
### 1. 创建工厂(推荐)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "git.toowon.com/jimmy/go-common/factory"
|
import "git.toowon.com/jimmy/go-common/factory"
|
||||||
|
|
||||||
// 直接传入配置文件路径,自动加载配置并创建工厂
|
// 方式1:直接从配置文件创建(最推荐)
|
||||||
fac, err := factory.NewFactoryFromFile("./config.json")
|
fac, err := factory.NewFactoryFromFile("./config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接获取已初始化的对象
|
// 方式2:从配置对象创建
|
||||||
db, _ := fac.GetDatabase() // 直接获取数据库对象
|
cfg, _ := config.LoadFromFile("./config.json")
|
||||||
logger, _ := fac.GetLogger() // 直接获取日志对象
|
|
||||||
emailClient, _ := fac.GetEmailClient() // 直接获取邮件客户端
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 从配置对象创建工厂
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"git.toowon.com/jimmy/go-common/config"
|
|
||||||
"git.toowon.com/jimmy/go-common/factory"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 加载配置
|
|
||||||
cfg, err := config.LoadFromFile("./config.json")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建工厂实例
|
|
||||||
fac := factory.NewFactory(cfg)
|
fac := factory.NewFactory(cfg)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 获取数据库对象(已初始化,推荐)
|
### 2. 日志记录(黑盒模式,推荐)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 直接获取已初始化的数据库对象(*gorm.DB)
|
// 简单日志
|
||||||
|
fac.LogDebug("调试信息: %s", "test")
|
||||||
|
fac.LogInfo("用户登录成功")
|
||||||
|
fac.LogWarn("警告信息")
|
||||||
|
fac.LogError("错误信息: %v", err)
|
||||||
|
|
||||||
|
// 带字段的日志
|
||||||
|
fac.LogInfof(map[string]interface{}{
|
||||||
|
"user_id": 123,
|
||||||
|
"ip": "192.168.1.1",
|
||||||
|
}, "用户登录成功")
|
||||||
|
|
||||||
|
fac.LogErrorf(map[string]interface{}{
|
||||||
|
"error_code": 1001,
|
||||||
|
}, "登录失败: %v", err)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 邮件发送(黑盒模式,推荐)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 简单邮件
|
||||||
|
err := fac.SendEmail(
|
||||||
|
[]string{"user@example.com"},
|
||||||
|
"验证码",
|
||||||
|
"您的验证码是:123456",
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTML邮件
|
||||||
|
err := fac.SendEmail(
|
||||||
|
[]string{"user@example.com"},
|
||||||
|
"验证码",
|
||||||
|
"纯文本内容",
|
||||||
|
"<h1>HTML内容</h1>",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 短信发送(黑盒模式,推荐)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 使用配置中的模板代码
|
||||||
|
resp, err := fac.SendSMS(
|
||||||
|
[]string{"13800138000"},
|
||||||
|
map[string]string{"code": "123456"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 指定模板代码
|
||||||
|
resp, err := fac.SendSMS(
|
||||||
|
[]string{"13800138000"},
|
||||||
|
map[string]string{"code": "123456"},
|
||||||
|
"SMS_123456789", // 模板代码
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 文件上传和查看(黑盒模式,推荐)
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 上传文件(自动选择OSS或MinIO)
|
||||||
|
file, _ := os.Open("test.jpg")
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
url, err := fac.UploadFile(ctx, "images/test.jpg", file, "image/jpeg")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("文件URL:", url)
|
||||||
|
|
||||||
|
// 获取文件URL(永久有效)
|
||||||
|
url, _ := fac.GetFileURL("images/test.jpg", 0)
|
||||||
|
|
||||||
|
// 获取临时访问URL(1小时后过期)
|
||||||
|
url, _ := fac.GetFileURL("images/test.jpg", 3600)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Redis操作(黑盒模式,推荐)
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 设置值(不过期)
|
||||||
|
err := fac.RedisSet(ctx, "user:123", "value")
|
||||||
|
|
||||||
|
// 设置值(带过期时间)
|
||||||
|
err := fac.RedisSet(ctx, "user:123", "value", time.Hour)
|
||||||
|
|
||||||
|
// 获取值
|
||||||
|
value, err := fac.RedisGet(ctx, "user:123")
|
||||||
|
|
||||||
|
// 删除键
|
||||||
|
err := fac.RedisDelete(ctx, "user:123", "user:456")
|
||||||
|
|
||||||
|
// 检查键是否存在
|
||||||
|
exists, err := fac.RedisExists(ctx, "user:123")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 数据库操作
|
||||||
|
|
||||||
|
数据库保持返回 GORM 对象,因为 GORM 已经提供了很好的抽象:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 获取数据库对象(延迟初始化)
|
||||||
db, err := fac.GetDatabase()
|
db, err := fac.GetDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接使用,无需再创建连接
|
// 直接使用GORM
|
||||||
var users []User
|
var users []User
|
||||||
db.Find(&users)
|
db.Find(&users)
|
||||||
db.Create(&user)
|
db.Create(&user)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 获取Redis客户端(已初始化,推荐)
|
|
||||||
|
|
||||||
```go
|
## 完整示例
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 直接获取已初始化的Redis客户端对象
|
|
||||||
redisClient, err := fac.GetRedisClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接使用,无需再创建连接
|
|
||||||
ctx := context.Background()
|
|
||||||
val, err := redisClient.Get(ctx, "key").Result()
|
|
||||||
if err != nil && err != redis.Nil {
|
|
||||||
log.Printf("Redis error: %v", err)
|
|
||||||
} else if err == redis.Nil {
|
|
||||||
fmt.Println("Key not found")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Value: %s\n", val)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 获取邮件客户端(已初始化)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 直接获取已初始化的邮件客户端
|
|
||||||
emailClient, err := fac.GetEmailClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接使用,无需再创建
|
|
||||||
err = emailClient.SendSimple(
|
|
||||||
[]string{"recipient@example.com"},
|
|
||||||
"主题",
|
|
||||||
"正文",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 获取短信客户端(已初始化)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 直接获取已初始化的短信客户端
|
|
||||||
smsClient, err := fac.GetSMSClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接使用,无需再创建
|
|
||||||
resp, err := smsClient.SendSimple(
|
|
||||||
[]string{"13800138000"},
|
|
||||||
map[string]string{"code": "123456"},
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. 获取日志记录器(已初始化)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 直接获取已初始化的日志记录器
|
|
||||||
logger, err := fac.GetLogger()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接使用,无需再创建
|
|
||||||
logger.Info("Application started")
|
|
||||||
logger.Error("Error occurred: %v", err)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8. 完整示例
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/config"
|
|
||||||
"git.toowon.com/jimmy/go-common/factory"
|
"git.toowon.com/jimmy/go-common/factory"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 加载配置
|
// 创建工厂
|
||||||
cfg, err := config.LoadFromFile("./config.json")
|
fac, err := factory.NewFactoryFromFile("./config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建工厂
|
ctx := context.Background()
|
||||||
fac := factory.NewFactory(cfg)
|
|
||||||
|
|
||||||
// 获取邮件客户端(已初始化,可直接使用)
|
// 日志记录(黑盒模式)
|
||||||
emailClient, err := fac.GetEmailClient()
|
fac.LogInfo("应用启动")
|
||||||
|
fac.LogInfof(map[string]interface{}{
|
||||||
|
"version": "1.0.0",
|
||||||
|
}, "应用启动成功")
|
||||||
|
|
||||||
|
// 邮件发送(黑盒模式)
|
||||||
|
err = fac.SendEmail(
|
||||||
|
[]string{"user@example.com"},
|
||||||
|
"欢迎",
|
||||||
|
"欢迎使用我们的服务",
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Email client not available: %v", err)
|
fac.LogError("发送邮件失败: %v", err)
|
||||||
} else {
|
|
||||||
// 直接使用
|
|
||||||
err = emailClient.SendSimple(
|
|
||||||
[]string{"recipient@example.com"},
|
|
||||||
"测试邮件",
|
|
||||||
"这是测试内容",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to send email: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取短信客户端(已初始化,可直接使用)
|
// 短信发送(黑盒模式)
|
||||||
smsClient, err := fac.GetSMSClient()
|
resp, err := fac.SendSMS(
|
||||||
|
[]string{"13800138000"},
|
||||||
|
map[string]string{"code": "123456"},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("SMS client not available: %v", err)
|
fac.LogError("发送短信失败: %v", err)
|
||||||
} else {
|
} else {
|
||||||
// 直接使用
|
fac.LogInfo("短信发送成功: %s", resp.RequestID)
|
||||||
resp, err := smsClient.SendSimple(
|
|
||||||
[]string{"13800138000"},
|
|
||||||
map[string]string{"code": "123456"},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to send SMS: %v", err)
|
|
||||||
} else {
|
|
||||||
log.Printf("SMS sent: %s", resp.RequestID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果需要访问配置对象
|
// 文件上传(黑盒模式,自动选择OSS或MinIO)
|
||||||
cfgObj := fac.GetConfig()
|
file, _ := os.Open("test.jpg")
|
||||||
dsn, _ := cfgObj.GetDatabaseDSN()
|
defer file.Close()
|
||||||
log.Printf("Database DSN: %s", dsn)
|
|
||||||
|
url, err := fac.UploadFile(ctx, "images/test.jpg", file, "image/jpeg")
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("上传文件失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("文件上传成功: %s", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis操作(黑盒模式)
|
||||||
|
err = fac.RedisSet(ctx, "user:123", "value", time.Hour)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("Redis设置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := fac.RedisGet(ctx, "user:123")
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("Redis获取失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("Redis值: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库操作
|
||||||
|
db, err := fac.GetDatabase()
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("数据库连接失败: %v", err)
|
||||||
|
} else {
|
||||||
|
var count int64
|
||||||
|
db.Table("users").Count(&count)
|
||||||
|
fac.LogInfo("用户数量: %d", count)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## API 参考
|
## API 参考
|
||||||
|
|
||||||
### NewFactory(cfg *config.Config) *Factory
|
### 工厂创建
|
||||||
|
|
||||||
|
#### NewFactory(cfg *config.Config) *Factory
|
||||||
|
|
||||||
创建工厂实例。
|
创建工厂实例。
|
||||||
|
|
||||||
@@ -208,7 +253,7 @@ func main() {
|
|||||||
|
|
||||||
**返回:** 工厂实例
|
**返回:** 工厂实例
|
||||||
|
|
||||||
### NewFactoryFromFile(filePath string) (*Factory, error)
|
#### NewFactoryFromFile(filePath string) (*Factory, error)
|
||||||
|
|
||||||
从配置文件直接创建工厂实例(便捷方法)。
|
从配置文件直接创建工厂实例(便捷方法)。
|
||||||
|
|
||||||
@@ -219,31 +264,132 @@ func main() {
|
|||||||
|
|
||||||
**说明:** 这是推荐的使用方式,一步完成配置加载和工厂创建。
|
**说明:** 这是推荐的使用方式,一步完成配置加载和工厂创建。
|
||||||
|
|
||||||
### (f *Factory) GetEmailClient() (*email.Email, error)
|
### 日志方法(黑盒模式)
|
||||||
|
|
||||||
获取邮件客户端(已初始化)。
|
#### LogDebug(message string, args ...interface{})
|
||||||
|
|
||||||
**返回:** 已初始化的邮件客户端对象和错误信息
|
记录调试日志。
|
||||||
|
|
||||||
**说明:** 如果邮件配置为nil,返回错误。
|
#### LogDebugf(fields map[string]interface{}, message string, args ...interface{})
|
||||||
|
|
||||||
### (f *Factory) GetSMSClient() (*sms.SMS, error)
|
记录调试日志(带字段)。
|
||||||
|
|
||||||
获取短信客户端(已初始化)。
|
#### LogInfo(message string, args ...interface{})
|
||||||
|
|
||||||
**返回:** 已初始化的短信客户端对象和错误信息
|
记录信息日志。
|
||||||
|
|
||||||
**说明:** 如果短信配置为nil,返回错误。
|
#### LogInfof(fields map[string]interface{}, message string, args ...interface{})
|
||||||
|
|
||||||
### (f *Factory) GetLogger() (*logger.Logger, error)
|
记录信息日志(带字段)。
|
||||||
|
|
||||||
获取日志记录器(已初始化)。
|
#### LogWarn(message string, args ...interface{})
|
||||||
|
|
||||||
**返回:** 已初始化的日志记录器对象和错误信息
|
记录警告日志。
|
||||||
|
|
||||||
**说明:** 如果日志配置为nil,会使用默认配置创建。
|
#### LogWarnf(fields map[string]interface{}, message string, args ...interface{})
|
||||||
|
|
||||||
### (f *Factory) GetDatabase() (*gorm.DB, error)
|
记录警告日志(带字段)。
|
||||||
|
|
||||||
|
#### LogError(message string, args ...interface{})
|
||||||
|
|
||||||
|
记录错误日志。
|
||||||
|
|
||||||
|
#### LogErrorf(fields map[string]interface{}, message string, args ...interface{})
|
||||||
|
|
||||||
|
记录错误日志(带字段)。
|
||||||
|
|
||||||
|
### 邮件方法(黑盒模式)
|
||||||
|
|
||||||
|
#### SendEmail(to []string, subject, body string, htmlBody ...string) error
|
||||||
|
|
||||||
|
发送邮件。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `to`: 收件人列表
|
||||||
|
- `subject`: 邮件主题
|
||||||
|
- `body`: 邮件正文(纯文本)
|
||||||
|
- `htmlBody`: HTML正文(可选,如果设置了会优先使用)
|
||||||
|
|
||||||
|
### 短信方法(黑盒模式)
|
||||||
|
|
||||||
|
#### SendSMS(phoneNumbers []string, templateParam interface{}, templateCode ...string) (*sms.SendResponse, error)
|
||||||
|
|
||||||
|
发送短信。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `phoneNumbers`: 手机号列表
|
||||||
|
- `templateParam`: 模板参数(map或JSON字符串)
|
||||||
|
- `templateCode`: 模板代码(可选,如果为空使用配置中的模板代码)
|
||||||
|
|
||||||
|
### 存储方法(黑盒模式)
|
||||||
|
|
||||||
|
#### UploadFile(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) (string, error)
|
||||||
|
|
||||||
|
上传文件。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `ctx`: 上下文
|
||||||
|
- `objectKey`: 对象键(文件路径)
|
||||||
|
- `reader`: 文件内容
|
||||||
|
- `contentType`: 文件类型(可选)
|
||||||
|
|
||||||
|
**返回:** 文件访问URL和错误信息
|
||||||
|
|
||||||
|
**说明:** 自动根据配置选择OSS或MinIO(优先级:MinIO > OSS)
|
||||||
|
|
||||||
|
#### GetFileURL(objectKey string, expires int64) (string, error)
|
||||||
|
|
||||||
|
获取文件访问URL。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `objectKey`: 对象键
|
||||||
|
- `expires`: 过期时间(秒),0表示永久有效
|
||||||
|
|
||||||
|
**返回:** 文件访问URL和错误信息
|
||||||
|
|
||||||
|
### Redis方法(黑盒模式)
|
||||||
|
|
||||||
|
#### RedisGet(ctx context.Context, key string) (string, error)
|
||||||
|
|
||||||
|
获取Redis值。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `ctx`: 上下文
|
||||||
|
- `key`: Redis键
|
||||||
|
|
||||||
|
**返回:** 值和错误信息(key不存在时返回空字符串)
|
||||||
|
|
||||||
|
#### RedisSet(ctx context.Context, key string, value interface{}, expiration ...time.Duration) error
|
||||||
|
|
||||||
|
设置Redis值。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `ctx`: 上下文
|
||||||
|
- `key`: Redis键
|
||||||
|
- `value`: Redis值
|
||||||
|
- `expiration`: 过期时间(可选,0表示不过期)
|
||||||
|
|
||||||
|
#### RedisDelete(ctx context.Context, keys ...string) error
|
||||||
|
|
||||||
|
删除Redis键。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `ctx`: 上下文
|
||||||
|
- `keys`: Redis键列表
|
||||||
|
|
||||||
|
#### RedisExists(ctx context.Context, key string) (bool, error)
|
||||||
|
|
||||||
|
检查Redis键是否存在。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `ctx`: 上下文
|
||||||
|
- `key`: Redis键
|
||||||
|
|
||||||
|
**返回:** 是否存在和错误信息
|
||||||
|
|
||||||
|
### 数据库方法
|
||||||
|
|
||||||
|
#### GetDatabase() (*gorm.DB, error)
|
||||||
|
|
||||||
获取数据库连接对象(已初始化)。
|
获取数据库连接对象(已初始化)。
|
||||||
|
|
||||||
@@ -253,78 +399,35 @@ func main() {
|
|||||||
- 支持MySQL、PostgreSQL、SQLite
|
- 支持MySQL、PostgreSQL、SQLite
|
||||||
- 自动配置连接池参数
|
- 自动配置连接池参数
|
||||||
- 数据库时间统一使用UTC时区
|
- 数据库时间统一使用UTC时区
|
||||||
|
- 延迟初始化,首次调用时创建连接
|
||||||
|
|
||||||
### (f *Factory) GetRedisClient() (*redis.Client, error)
|
|
||||||
|
|
||||||
获取Redis客户端对象(已初始化)。
|
#### GetConfig() *config.Config
|
||||||
|
|
||||||
**返回:** 已初始化的Redis客户端对象和错误信息
|
|
||||||
|
|
||||||
**说明:**
|
|
||||||
- 自动处理所有配置检查和连接测试
|
|
||||||
- 自动设置默认值(连接池大小、超时时间等)
|
|
||||||
- 连接失败时会自动关闭客户端并返回错误
|
|
||||||
- 返回的客户端已通过Ping测试,可直接使用
|
|
||||||
|
|
||||||
### (f *Factory) GetRedisConfig() *config.RedisConfig
|
|
||||||
|
|
||||||
获取Redis配置(用于创建Redis客户端)。
|
|
||||||
|
|
||||||
**返回:** Redis配置对象(可能为nil)
|
|
||||||
|
|
||||||
**说明:**
|
|
||||||
- 推荐使用 `GetRedisClient()` 方法直接获取已初始化的客户端
|
|
||||||
- 如果需要自定义创建Redis客户端,可以使用此方法获取配置
|
|
||||||
|
|
||||||
### (f *Factory) GetConfig() *config.Config
|
|
||||||
|
|
||||||
获取配置对象。
|
获取配置对象。
|
||||||
|
|
||||||
**返回:** 配置对象
|
**返回:** 配置对象
|
||||||
|
|
||||||
## 优势
|
## 设计优势
|
||||||
|
|
||||||
### 之前的方式(需要调用方实现)
|
### 优势总结
|
||||||
|
|
||||||
```go
|
1. **降低复杂度**:调用方无需关心客户端对象的创建和管理
|
||||||
// 调用方需要自己实现创建逻辑
|
2. **延迟初始化**:所有客户端在首次使用时才创建,提高性能
|
||||||
cfg, _ := config.LoadFromFile("./config.json")
|
3. **自动选择**:存储类型根据配置自动选择,无需手动指定
|
||||||
dsn, _ := cfg.GetDatabaseDSN()
|
4. **统一接口**:所有操作通过工厂方法调用,接口统一
|
||||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
5. **容错处理**:日志初始化失败时自动回退到标准输出
|
||||||
if err != nil {
|
6. **代码简洁**:只提供黑盒模式方法,保持代码简洁清晰
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// 配置连接池
|
|
||||||
sqlDB, _ := db.DB()
|
|
||||||
sqlDB.SetMaxOpenConns(100)
|
|
||||||
// 才能使用
|
|
||||||
db.Find(&users)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用工厂方式(直接获取已初始化对象)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 方式1:直接从配置文件创建(最推荐)
|
|
||||||
fac, _ := factory.NewFactoryFromFile("./config.json")
|
|
||||||
db, _ := fac.GetDatabase() // 直接获取已初始化的数据库对象
|
|
||||||
// 直接使用
|
|
||||||
db.Find(&users)
|
|
||||||
|
|
||||||
// 方式2:从配置对象创建
|
|
||||||
cfg, _ := config.LoadFromFile("./config.json")
|
|
||||||
fac := factory.NewFactory(cfg)
|
|
||||||
db, _ := fac.GetDatabase() // 直接获取已初始化的数据库对象
|
|
||||||
// 直接使用
|
|
||||||
db.Find(&users)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **配置检查**:工厂方法会自动检查配置是否存在,如果配置为nil会返回错误
|
1. **配置检查**:工厂方法会自动检查配置是否存在,如果配置为nil会返回错误
|
||||||
2. **错误处理**:所有Get方法都可能返回错误,需要正确处理
|
2. **错误处理**:所有方法都可能返回错误,需要正确处理
|
||||||
3. **配置对象**:可以通过`GetConfig()`方法访问原始配置对象,获取其他配置信息
|
3. **延迟初始化**:所有客户端在首次使用时才创建,首次调用可能稍慢
|
||||||
|
4. **存储选择**:存储类型根据配置自动选择(优先级:MinIO > OSS)
|
||||||
|
5. **数据库对象**:数据库保持返回GORM对象,因为GORM已经提供了很好的抽象
|
||||||
|
6. **黑盒模式**:所有功能都通过工厂方法直接调用,无需获取底层客户端对象
|
||||||
|
|
||||||
## 示例
|
## 示例
|
||||||
|
|
||||||
完整示例请参考 `examples/factory_example.go`
|
完整示例请参考 `examples/factory_example.go`
|
||||||
|
|
||||||
|
|||||||
477
docs/http.md
477
docs/http.md
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,包含统一的响应结构、分页支持和HTTP状态码与业务状态码的分离。
|
HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,采用Handler黑盒模式,封装了`ResponseWriter`和`Request`,提供简洁的API,无需每次都传递这两个参数。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- 标准化的响应结构:`{code, message, timestamp, data}`
|
- **黑盒模式**:封装`ResponseWriter`和`Request`,提供简洁的API
|
||||||
- 分离HTTP状态码和业务状态码
|
- **标准化的响应结构**:`{code, message, timestamp, data}`
|
||||||
- 支持分页响应
|
- **分离HTTP状态码和业务状态码**
|
||||||
- 提供便捷的请求参数解析方法
|
- **支持分页响应**
|
||||||
- 支持JSON请求体解析
|
- **提供便捷的请求参数解析方法**
|
||||||
- 提供常用的HTTP错误响应方法
|
- **支持JSON请求体解析**
|
||||||
|
|
||||||
## 响应结构
|
## 响应结构
|
||||||
|
|
||||||
@@ -44,151 +44,224 @@ HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,包含
|
|||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
### 1. 成功响应
|
### 1. 创建Handler
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 简单成功响应(data为nil)
|
// 方式1:使用HandleFunc包装器(推荐,最简洁)
|
||||||
http.Success(w, nil)
|
func GetUser(h *commonhttp.Handler) {
|
||||||
|
id := h.GetQueryInt64("id", 0)
|
||||||
// 带数据的成功响应
|
h.Success(data)
|
||||||
data := map[string]interface{}{
|
|
||||||
"id": 1,
|
|
||||||
"name": "test",
|
|
||||||
}
|
}
|
||||||
http.Success(w, data)
|
|
||||||
|
|
||||||
// 带消息的成功响应
|
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||||
http.SuccessWithMessage(w, "操作成功", data)
|
|
||||||
|
// 方式2:手动创建Handler(需要更多控制时)
|
||||||
|
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h := commonhttp.NewHandler(w, r)
|
||||||
|
GetUser(h)
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 错误响应
|
### 2. 成功响应
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 业务错误(HTTP 200,业务code非0)
|
func handler(h *commonhttp.Handler) {
|
||||||
http.Error(w, 1001, "用户不存在")
|
// 简单成功响应(data为nil)
|
||||||
|
h.Success(nil)
|
||||||
|
|
||||||
// 系统错误(HTTP 500)
|
// 带数据的成功响应
|
||||||
http.SystemError(w, "服务器内部错误")
|
data := map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"name": "test",
|
||||||
|
}
|
||||||
|
h.Success(data)
|
||||||
|
|
||||||
// 请求错误(HTTP 400)
|
// 带消息的成功响应
|
||||||
http.BadRequest(w, "请求参数错误")
|
h.SuccessWithMessage("操作成功", data)
|
||||||
|
}
|
||||||
// 未授权(HTTP 401)
|
|
||||||
http.Unauthorized(w, "未登录")
|
|
||||||
|
|
||||||
// 禁止访问(HTTP 403)
|
|
||||||
http.Forbidden(w, "无权限访问")
|
|
||||||
|
|
||||||
// 未找到(HTTP 404)
|
|
||||||
http.NotFound(w, "资源不存在")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 分页响应
|
### 3. 错误响应
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 获取分页参数
|
func handler(h *commonhttp.Handler) {
|
||||||
page, pageSize := http.GetPaginationParams(r)
|
// 业务错误(HTTP 200,业务code非0)
|
||||||
|
h.Error(1001, "用户不存在")
|
||||||
|
|
||||||
// 查询数据(示例)
|
// 系统错误(HTTP 500)
|
||||||
list, total := getDataList(page, pageSize)
|
h.SystemError("服务器内部错误")
|
||||||
|
|
||||||
// 返回分页响应
|
// 其他HTTP错误状态码,使用WriteJSON直接指定
|
||||||
http.SuccessPage(w, list, total, page, pageSize)
|
// 请求错误(HTTP 400)
|
||||||
|
h.WriteJSON(http.StatusBadRequest, 400, "请求参数错误", nil)
|
||||||
|
|
||||||
// 带消息的分页响应
|
// 未授权(HTTP 401)
|
||||||
http.SuccessPageWithMessage(w, "查询成功", list, total, page, pageSize)
|
h.WriteJSON(http.StatusUnauthorized, 401, "未登录", nil)
|
||||||
|
|
||||||
|
// 禁止访问(HTTP 403)
|
||||||
|
h.WriteJSON(http.StatusForbidden, 403, "无权限访问", nil)
|
||||||
|
|
||||||
|
// 未找到(HTTP 404)
|
||||||
|
h.WriteJSON(http.StatusNotFound, 404, "资源不存在", nil)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 解析请求
|
### 4. 分页响应
|
||||||
|
|
||||||
|
```go
|
||||||
|
func handler(h *commonhttp.Handler) {
|
||||||
|
// 获取分页参数
|
||||||
|
pagination := h.ParsePaginationRequest()
|
||||||
|
page := pagination.GetPage()
|
||||||
|
pageSize := pagination.GetSize()
|
||||||
|
|
||||||
|
// 查询数据(示例)
|
||||||
|
list, total := getDataList(page, pageSize)
|
||||||
|
|
||||||
|
// 返回分页响应(使用默认消息)
|
||||||
|
h.SuccessPage(list, total, page, pageSize)
|
||||||
|
|
||||||
|
// 返回分页响应(自定义消息)
|
||||||
|
h.SuccessPage(list, total, page, pageSize, "查询成功")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 解析请求
|
||||||
|
|
||||||
#### 解析JSON请求体
|
#### 解析JSON请求体
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type CreateUserRequest struct {
|
func handler(h *commonhttp.Handler) {
|
||||||
Name string `json:"name"`
|
type CreateUserRequest struct {
|
||||||
Email string `json:"email"`
|
Name string `json:"name"`
|
||||||
}
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
var req CreateUserRequest
|
var req CreateUserRequest
|
||||||
err := http.ParseJSON(r, &req)
|
if err := h.ParseJSON(&req); err != nil {
|
||||||
if err != nil {
|
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||||
http.BadRequest(w, "请求参数解析失败")
|
return
|
||||||
return
|
}
|
||||||
|
|
||||||
|
// 使用req...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 获取查询参数
|
#### 获取查询参数
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 获取字符串参数
|
func handler(h *commonhttp.Handler) {
|
||||||
name := http.GetQuery(r, "name", "")
|
// 获取字符串参数
|
||||||
email := http.GetQuery(r, "email", "default@example.com")
|
name := h.GetQuery("name", "")
|
||||||
|
email := h.GetQuery("email", "default@example.com")
|
||||||
|
|
||||||
// 获取整数参数
|
// 获取整数参数
|
||||||
id := http.GetQueryInt(r, "id", 0)
|
id := h.GetQueryInt("id", 0)
|
||||||
age := http.GetQueryInt(r, "age", 18)
|
age := h.GetQueryInt("age", 18)
|
||||||
|
|
||||||
// 获取int64参数
|
// 获取int64参数
|
||||||
userId := http.GetQueryInt64(r, "userId", 0)
|
userId := h.GetQueryInt64("userId", 0)
|
||||||
|
|
||||||
// 获取布尔参数
|
// 获取布尔参数
|
||||||
isActive := http.GetQueryBool(r, "isActive", false)
|
isActive := h.GetQueryBool("isActive", false)
|
||||||
|
|
||||||
// 获取浮点数参数
|
// 获取浮点数参数
|
||||||
price := http.GetQueryFloat64(r, "price", 0.0)
|
price := h.GetQueryFloat64("price", 0.0)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 获取表单参数
|
#### 获取表单参数
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 获取表单字符串
|
func handler(h *commonhttp.Handler) {
|
||||||
name := http.GetFormValue(r, "name", "")
|
// 获取表单字符串
|
||||||
|
name := h.GetFormValue("name", "")
|
||||||
|
|
||||||
// 获取表单整数
|
// 获取表单整数
|
||||||
age := http.GetFormInt(r, "age", 0)
|
age := h.GetFormInt("age", 0)
|
||||||
|
|
||||||
// 获取表单int64
|
// 获取表单int64
|
||||||
userId := http.GetFormInt64(r, "userId", 0)
|
userId := h.GetFormInt64("userId", 0)
|
||||||
|
|
||||||
// 获取表单布尔值
|
// 获取表单布尔值
|
||||||
isActive := http.GetFormBool(r, "isActive", false)
|
isActive := h.GetFormBool("isActive", false)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 获取请求头
|
#### 获取请求头
|
||||||
|
|
||||||
```go
|
```go
|
||||||
token := http.GetHeader(r, "Authorization", "")
|
func handler(h *commonhttp.Handler) {
|
||||||
contentType := http.GetHeader(r, "Content-Type", "application/json")
|
token := h.GetHeader("Authorization", "")
|
||||||
|
contentType := h.GetHeader("Content-Type", "application/json")
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 获取分页参数
|
#### 获取分页参数
|
||||||
|
|
||||||
```go
|
**方式1:使用 PaginationRequest 结构(推荐)**
|
||||||
// 自动解析page和pageSize参数
|
|
||||||
// 默认: page=1, pageSize=10
|
|
||||||
// 限制: pageSize最大1000
|
|
||||||
page, pageSize := http.GetPaginationParams(r)
|
|
||||||
|
|
||||||
// 计算数据库查询偏移量
|
```go
|
||||||
offset := http.GetOffset(page, pageSize)
|
func handler(h *commonhttp.Handler) {
|
||||||
|
// 定义请求结构(包含分页字段)
|
||||||
|
type ListUserRequest struct {
|
||||||
|
Keyword string `json:"keyword"`
|
||||||
|
commonhttp.PaginationRequest // 嵌入分页请求结构
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从JSON请求体解析(分页字段会自动解析)
|
||||||
|
var req ListUserRequest
|
||||||
|
if err := h.ParseJSON(&req); err != nil {
|
||||||
|
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用分页方法
|
||||||
|
page := req.GetPage() // 获取页码(默认1)
|
||||||
|
size := req.GetSize() // 获取每页数量(默认20,最大100,优先使用page_size)
|
||||||
|
offset := req.GetOffset() // 计算偏移量
|
||||||
|
}
|
||||||
|
|
||||||
|
// 或者从查询参数/form解析分页
|
||||||
|
func handler(h *commonhttp.Handler) {
|
||||||
|
pagination := h.ParsePaginationRequest()
|
||||||
|
page := pagination.GetPage()
|
||||||
|
size := pagination.GetSize()
|
||||||
|
offset := pagination.GetOffset()
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 自定义响应
|
#### 获取时区
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 使用WriteJSON自定义响应
|
func handler(h *commonhttp.Handler) {
|
||||||
http.WriteJSON(w, http.StatusOK, 0, "success", data)
|
// 从请求的context中获取时区
|
||||||
|
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||||
|
// 如果未设置,返回默认时区 AsiaShanghai
|
||||||
|
timezone := h.GetTimezone()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
// 参数说明:
|
### 6. 访问原始对象
|
||||||
// - httpCode: HTTP状态码(200, 400, 500等)
|
|
||||||
// - code: 业务状态码(0表示成功,非0表示业务错误)
|
如果需要访问原始的`ResponseWriter`或`Request`:
|
||||||
// - message: 响应消息
|
|
||||||
// - data: 响应数据
|
```go
|
||||||
|
func handler(h *commonhttp.Handler) {
|
||||||
|
// 获取原始ResponseWriter
|
||||||
|
w := h.ResponseWriter()
|
||||||
|
|
||||||
|
// 获取原始Request
|
||||||
|
r := h.Request()
|
||||||
|
|
||||||
|
// 获取Context
|
||||||
|
ctx := h.Context()
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 完整示例
|
## 完整示例
|
||||||
@@ -197,118 +270,159 @@ http.WriteJSON(w, http.StatusOK, 0, "success", data)
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 用户结构
|
||||||
|
type User struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
// 用户列表接口
|
// 用户列表接口
|
||||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
func GetUserList(h *commonhttp.Handler) {
|
||||||
// 获取分页参数
|
// 获取分页参数
|
||||||
page, pageSize := http.GetPaginationParams(r)
|
pagination := h.ParsePaginationRequest()
|
||||||
|
page := pagination.GetPage()
|
||||||
|
pageSize := pagination.GetSize()
|
||||||
|
|
||||||
// 获取查询参数
|
// 获取查询参数
|
||||||
keyword := http.GetQuery(r, "keyword", "")
|
keyword := h.GetQuery("keyword", "")
|
||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
users, total := queryUsers(keyword, page, pageSize)
|
users, total := queryUsers(keyword, page, pageSize)
|
||||||
|
|
||||||
// 返回分页响应
|
// 返回分页响应
|
||||||
http.SuccessPage(w, users, total, page, pageSize)
|
h.SuccessPage(users, total, page, pageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建用户接口
|
// 创建用户接口
|
||||||
func CreateUser(w http.ResponseWriter, r *http.Request) {
|
func CreateUser(h *commonhttp.Handler) {
|
||||||
// 解析请求体
|
// 解析请求体
|
||||||
var req struct {
|
var req struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := http.ParseJSON(r, &req); err != nil {
|
if err := h.ParseJSON(&req); err != nil {
|
||||||
http.BadRequest(w, "请求参数解析失败")
|
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 参数验证
|
// 参数验证
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
http.Error(w, 1001, "用户名不能为空")
|
h.Error(1001, "用户名不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建用户
|
// 创建用户
|
||||||
user, err := createUser(req.Name, req.Email)
|
user, err := createUser(req.Name, req.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.SystemError(w, "创建用户失败")
|
h.SystemError("创建用户失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回成功响应
|
// 返回成功响应
|
||||||
http.SuccessWithMessage(w, "创建成功", user)
|
h.SuccessWithMessage("创建成功", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户详情接口
|
// 获取用户详情接口
|
||||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
func GetUser(h *commonhttp.Handler) {
|
||||||
// 获取路径参数(需要配合路由框架使用)
|
// 获取查询参数
|
||||||
id := http.GetQueryInt64(r, "id", 0)
|
id := h.GetQueryInt64("id", 0)
|
||||||
|
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
http.BadRequest(w, "用户ID不能为空")
|
h.WriteJSON(http.StatusBadRequest, 400, "用户ID不能为空", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询用户
|
// 查询用户
|
||||||
user, err := getUserByID(id)
|
user, err := getUserByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.SystemError(w, "查询用户失败")
|
h.SystemError("查询用户失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
http.Error(w, 1002, "用户不存在")
|
h.Error(1002, "用户不存在")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Success(w, user)
|
h.Success(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 使用HandleFunc包装器(推荐)
|
||||||
|
http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||||
|
switch h.Request().Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
GetUserList(h)
|
||||||
|
case http.MethodPost:
|
||||||
|
CreateUser(h)
|
||||||
|
default:
|
||||||
|
h.WriteJSON(http.StatusMethodNotAllowed, 405, "方法不支持", nil)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||||
|
|
||||||
|
log.Println("Server started on :8080")
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## API 参考
|
## API 参考
|
||||||
|
|
||||||
### 响应方法
|
### Handler结构
|
||||||
|
|
||||||
#### Success(w http.ResponseWriter, data interface{})
|
Handler封装了`ResponseWriter`和`Request`,提供更简洁的API。
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Handler struct {
|
||||||
|
w http.ResponseWriter
|
||||||
|
r *http.Request
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建Handler
|
||||||
|
|
||||||
|
#### NewHandler(w http.ResponseWriter, r *http.Request) *Handler
|
||||||
|
|
||||||
|
创建Handler实例。
|
||||||
|
|
||||||
|
#### HandleFunc(fn func(*Handler)) http.HandlerFunc
|
||||||
|
|
||||||
|
将Handler函数转换为标准的http.HandlerFunc(便捷包装器)。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```go
|
||||||
|
http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||||
|
h.Success(data)
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handler响应方法
|
||||||
|
|
||||||
|
#### (h *Handler) Success(data interface{})
|
||||||
|
|
||||||
成功响应,HTTP 200,业务code 0。
|
成功响应,HTTP 200,业务code 0。
|
||||||
|
|
||||||
#### SuccessWithMessage(w http.ResponseWriter, message string, data interface{})
|
#### (h *Handler) SuccessWithMessage(message string, data interface{})
|
||||||
|
|
||||||
带消息的成功响应。
|
带消息的成功响应。
|
||||||
|
|
||||||
#### Error(w http.ResponseWriter, code int, message string)
|
#### (h *Handler) Error(code int, message string)
|
||||||
|
|
||||||
业务错误响应,HTTP 200,业务code非0。
|
业务错误响应,HTTP 200,业务code非0。
|
||||||
|
|
||||||
#### SystemError(w http.ResponseWriter, message string)
|
#### (h *Handler) SystemError(message string)
|
||||||
|
|
||||||
系统错误响应,HTTP 500,业务code 500。
|
系统错误响应,HTTP 500,业务code 500。
|
||||||
|
|
||||||
#### BadRequest(w http.ResponseWriter, message string)
|
#### (h *Handler) WriteJSON(httpCode, code int, message string, data interface{})
|
||||||
|
|
||||||
请求错误响应,HTTP 400。
|
|
||||||
|
|
||||||
#### Unauthorized(w http.ResponseWriter, message string)
|
|
||||||
|
|
||||||
未授权响应,HTTP 401。
|
|
||||||
|
|
||||||
#### Forbidden(w http.ResponseWriter, message string)
|
|
||||||
|
|
||||||
禁止访问响应,HTTP 403。
|
|
||||||
|
|
||||||
#### NotFound(w http.ResponseWriter, message string)
|
|
||||||
|
|
||||||
未找到响应,HTTP 404。
|
|
||||||
|
|
||||||
#### WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{})
|
|
||||||
|
|
||||||
写入JSON响应(自定义)。
|
写入JSON响应(自定义)。
|
||||||
|
|
||||||
@@ -318,69 +432,113 @@ func GetUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
- `message`: 响应消息
|
- `message`: 响应消息
|
||||||
- `data`: 响应数据
|
- `data`: 响应数据
|
||||||
|
|
||||||
#### SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int)
|
#### (h *Handler) SuccessPage(list interface{}, total int64, page, pageSize int, message ...string)
|
||||||
|
|
||||||
分页成功响应。
|
分页成功响应。
|
||||||
|
|
||||||
#### SuccessPageWithMessage(w http.ResponseWriter, message string, list interface{}, total int64, page, pageSize int)
|
**参数:**
|
||||||
|
- `list`: 数据列表
|
||||||
|
- `total`: 总记录数
|
||||||
|
- `page`: 当前页码
|
||||||
|
- `pageSize`: 每页大小
|
||||||
|
- `message`: 响应消息(可选,如果为空则使用默认消息 "success")
|
||||||
|
|
||||||
带消息的分页成功响应。
|
### Handler请求解析方法
|
||||||
|
|
||||||
### 请求方法
|
#### (h *Handler) ParseJSON(v interface{}) error
|
||||||
|
|
||||||
#### ParseJSON(r *http.Request, v interface{}) error
|
|
||||||
|
|
||||||
解析JSON请求体。
|
解析JSON请求体。
|
||||||
|
|
||||||
#### GetQuery(r *http.Request, key, defaultValue string) string
|
#### (h *Handler) GetQuery(key, defaultValue string) string
|
||||||
|
|
||||||
获取查询参数(字符串)。
|
获取查询参数(字符串)。
|
||||||
|
|
||||||
#### GetQueryInt(r *http.Request, key string, defaultValue int) int
|
#### (h *Handler) GetQueryInt(key string, defaultValue int) int
|
||||||
|
|
||||||
获取查询参数(整数)。
|
获取查询参数(整数)。
|
||||||
|
|
||||||
#### GetQueryInt64(r *http.Request, key string, defaultValue int64) int64
|
#### (h *Handler) GetQueryInt64(key string, defaultValue int64) int64
|
||||||
|
|
||||||
获取查询参数(int64)。
|
获取查询参数(int64)。
|
||||||
|
|
||||||
#### GetQueryBool(r *http.Request, key string, defaultValue bool) bool
|
#### (h *Handler) GetQueryBool(key string, defaultValue bool) bool
|
||||||
|
|
||||||
获取查询参数(布尔值)。
|
获取查询参数(布尔值)。
|
||||||
|
|
||||||
#### GetQueryFloat64(r *http.Request, key string, defaultValue float64) float64
|
#### (h *Handler) GetQueryFloat64(key string, defaultValue float64) float64
|
||||||
|
|
||||||
获取查询参数(浮点数)。
|
获取查询参数(浮点数)。
|
||||||
|
|
||||||
#### GetFormValue(r *http.Request, key, defaultValue string) string
|
#### (h *Handler) GetFormValue(key, defaultValue string) string
|
||||||
|
|
||||||
获取表单值(字符串)。
|
获取表单值(字符串)。
|
||||||
|
|
||||||
#### GetFormInt(r *http.Request, key string, defaultValue int) int
|
#### (h *Handler) GetFormInt(key string, defaultValue int) int
|
||||||
|
|
||||||
获取表单值(整数)。
|
获取表单值(整数)。
|
||||||
|
|
||||||
#### GetFormInt64(r *http.Request, key string, defaultValue int64) int64
|
#### (h *Handler) GetFormInt64(key string, defaultValue int64) int64
|
||||||
|
|
||||||
获取表单值(int64)。
|
获取表单值(int64)。
|
||||||
|
|
||||||
#### GetFormBool(r *http.Request, key string, defaultValue bool) bool
|
#### (h *Handler) GetFormBool(key string, defaultValue bool) bool
|
||||||
|
|
||||||
获取表单值(布尔值)。
|
获取表单值(布尔值)。
|
||||||
|
|
||||||
#### GetHeader(r *http.Request, key, defaultValue string) string
|
#### (h *Handler) GetHeader(key, defaultValue string) string
|
||||||
|
|
||||||
获取请求头。
|
获取请求头。
|
||||||
|
|
||||||
#### GetPaginationParams(r *http.Request) (page, pageSize int)
|
#### (h *Handler) ParsePaginationRequest() *PaginationRequest
|
||||||
|
|
||||||
获取分页参数。
|
从请求中解析分页参数。
|
||||||
|
|
||||||
**返回:** page(页码,最小1),pageSize(每页大小,最小1,最大1000)
|
**说明:**
|
||||||
|
- 支持从查询参数和form表单中解析
|
||||||
|
- 优先级:查询参数 > form表单
|
||||||
|
- 如果请求体是JSON格式且包含分页字段,建议先使用`ParseJSON`解析完整请求体到包含`PaginationRequest`的结构体中
|
||||||
|
|
||||||
#### GetOffset(page, pageSize int) int
|
#### (h *Handler) GetTimezone() string
|
||||||
|
|
||||||
根据页码和每页大小计算偏移量。
|
从请求的context中获取时区。
|
||||||
|
|
||||||
|
**说明:**
|
||||||
|
- 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||||
|
- 如果未设置,返回默认时区 AsiaShanghai
|
||||||
|
|
||||||
|
### Handler访问原始对象
|
||||||
|
|
||||||
|
#### (h *Handler) ResponseWriter() http.ResponseWriter
|
||||||
|
|
||||||
|
获取原始的ResponseWriter(需要时使用)。
|
||||||
|
|
||||||
|
#### (h *Handler) Request() *http.Request
|
||||||
|
|
||||||
|
获取原始的Request(需要时使用)。
|
||||||
|
|
||||||
|
#### (h *Handler) Context() context.Context
|
||||||
|
|
||||||
|
获取请求的Context。
|
||||||
|
|
||||||
|
### 分页请求结构
|
||||||
|
|
||||||
|
#### PaginationRequest
|
||||||
|
|
||||||
|
分页请求结构,支持从JSON和form中解析分页参数。
|
||||||
|
|
||||||
|
**字段:**
|
||||||
|
- `Page`: 页码(默认1)
|
||||||
|
- `Size`: 每页数量(兼容旧版本)
|
||||||
|
- `PageSize`: 每页数量(推荐使用,优先于Size)
|
||||||
|
|
||||||
|
**方法:**
|
||||||
|
- `GetPage() int`: 获取页码,如果未设置则返回默认值1
|
||||||
|
- `GetSize() int`: 获取每页数量,优先使用PageSize,如果未设置则使用Size,默认20,最大100
|
||||||
|
- `GetOffset() int`: 计算数据库查询的偏移量
|
||||||
|
|
||||||
|
#### ParsePaginationRequest(r *http.Request) *PaginationRequest
|
||||||
|
|
||||||
|
从请求中解析分页参数(内部函数,Handler内部使用)。
|
||||||
|
|
||||||
## 状态码说明
|
## 状态码说明
|
||||||
|
|
||||||
@@ -406,18 +564,21 @@ func GetUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
2. **分页参数限制**:
|
2. **分页参数限制**:
|
||||||
- page最小值为1
|
- page最小值为1
|
||||||
- pageSize最小值为1,最大值为1000
|
- pageSize最小值为1,最大值为100
|
||||||
|
|
||||||
3. **响应格式统一**:
|
3. **响应格式统一**:
|
||||||
- 所有响应都遵循标准结构
|
- 所有响应都遵循标准结构
|
||||||
- timestamp为Unix时间戳(秒)
|
- timestamp为Unix时间戳(秒)
|
||||||
|
|
||||||
4. **错误处理**:
|
4. **错误处理**:
|
||||||
- 使用Error方法返回业务错误
|
- 使用`Error`方法返回业务错误(HTTP 200,业务code非0)
|
||||||
- 使用SystemError返回系统错误
|
- 使用`SystemError`返回系统错误(HTTP 500)
|
||||||
- 使用BadRequest等返回HTTP级别的错误
|
- 其他HTTP错误状态码(400, 401, 403, 404等)使用`WriteJSON`方法直接指定
|
||||||
|
|
||||||
|
5. **黑盒模式**:
|
||||||
|
- 所有功能都通过Handler对象调用,无需传递`w`和`r`参数
|
||||||
|
- 代码更简洁,减少调用方工作量
|
||||||
|
|
||||||
## 示例
|
## 示例
|
||||||
|
|
||||||
完整示例请参考 `examples/http_example.go`
|
完整示例请参考 `examples/http_handler_example.go`
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ corsHandler := middleware.CORS(corsConfig)(handler)
|
|||||||
|
|
||||||
- 从请求头 `X-Timezone` 读取时区
|
- 从请求头 `X-Timezone` 读取时区
|
||||||
- 如果未传递时区信息,使用默认时区 `AsiaShanghai`
|
- 如果未传递时区信息,使用默认时区 `AsiaShanghai`
|
||||||
- 时区信息存储到context中,可通过 `http.GetTimezone()` 获取
|
- 时区信息存储到context中,可通过Handler的`GetTimezone()`方法获取
|
||||||
- 自动验证时区有效性,无效时区会回退到默认时区
|
- 自动验证时区有效性,无效时区会回退到默认时区
|
||||||
|
|
||||||
### 使用方法
|
### 使用方法
|
||||||
@@ -132,19 +132,21 @@ corsHandler := middleware.CORS(corsConfig)(handler)
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"git.toowon.com/jimmy/go-common/middleware"
|
"git.toowon.com/jimmy/go-common/middleware"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
"git.toowon.com/jimmy/go-common/datetime"
|
"git.toowon.com/jimmy/go-common/datetime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
// 从context获取时区
|
h := commonhttp.NewHandler(w, r)
|
||||||
timezone := http.GetTimezone(r)
|
|
||||||
|
// 从Handler获取时区
|
||||||
|
timezone := h.GetTimezone()
|
||||||
|
|
||||||
// 使用时区
|
// 使用时区
|
||||||
now := datetime.Now(timezone)
|
now := datetime.Now(timezone)
|
||||||
datetime.FormatDateTime(now, timezone)
|
datetime.FormatDateTime(now, timezone)
|
||||||
|
|
||||||
http.Success(w, map[string]interface{}{
|
h.Success(map[string]interface{}{
|
||||||
"timezone": timezone,
|
"timezone": timezone,
|
||||||
"time": datetime.FormatDateTime(now),
|
"time": datetime.FormatDateTime(now),
|
||||||
})
|
})
|
||||||
@@ -183,13 +185,15 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
"git.toowon.com/jimmy/go-common/datetime"
|
"git.toowon.com/jimmy/go-common/datetime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||||
// 从请求context获取时区
|
h := commonhttp.NewHandler(w, r)
|
||||||
timezone := http.GetTimezone(r)
|
|
||||||
|
// 从Handler获取时区
|
||||||
|
timezone := h.GetTimezone()
|
||||||
|
|
||||||
// 使用时区进行时间处理
|
// 使用时区进行时间处理
|
||||||
now := datetime.Now(timezone)
|
now := datetime.Now(timezone)
|
||||||
@@ -199,7 +203,7 @@ func GetUserList(w http.ResponseWriter, r *http.Request) {
|
|||||||
endTime := datetime.EndOfDay(now, timezone)
|
endTime := datetime.EndOfDay(now, timezone)
|
||||||
|
|
||||||
// 返回数据
|
// 返回数据
|
||||||
http.Success(w, map[string]interface{}{
|
h.Success(map[string]interface{}{
|
||||||
"timezone": timezone,
|
"timezone": timezone,
|
||||||
"startTime": datetime.FormatDateTime(startTime),
|
"startTime": datetime.FormatDateTime(startTime),
|
||||||
"endTime": datetime.FormatDateTime(endTime),
|
"endTime": datetime.FormatDateTime(endTime),
|
||||||
@@ -283,16 +287,18 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/middleware"
|
"git.toowon.com/jimmy/go-common/middleware"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
"git.toowon.com/jimmy/go-common/datetime"
|
"git.toowon.com/jimmy/go-common/datetime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// 获取时区
|
h := commonhttp.NewHandler(w, r)
|
||||||
timezone := http.GetTimezone(r)
|
|
||||||
|
// 从Handler获取时区
|
||||||
|
timezone := h.GetTimezone()
|
||||||
now := datetime.Now(timezone)
|
now := datetime.Now(timezone)
|
||||||
|
|
||||||
http.Success(w, map[string]interface{}{
|
h.Success(map[string]interface{}{
|
||||||
"message": "Hello",
|
"message": "Hello",
|
||||||
"timezone": timezone,
|
"timezone": timezone,
|
||||||
"time": datetime.FormatDateTime(now),
|
"time": datetime.FormatDateTime(now),
|
||||||
@@ -332,7 +338,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/middleware"
|
"git.toowon.com/jimmy/go-common/middleware"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -352,15 +358,17 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUsers(w http.ResponseWriter, r *http.Request) {
|
func getUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
timezone := http.GetTimezone(r)
|
h := commonhttp.NewHandler(w, r)
|
||||||
|
timezone := h.GetTimezone()
|
||||||
// 处理逻辑
|
// 处理逻辑
|
||||||
http.Success(w, nil)
|
h.Success(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPosts(w http.ResponseWriter, r *http.Request) {
|
func getPosts(w http.ResponseWriter, r *http.Request) {
|
||||||
timezone := http.GetTimezone(r)
|
h := commonhttp.NewHandler(w, r)
|
||||||
|
timezone := h.GetTimezone()
|
||||||
// 处理逻辑
|
// 处理逻辑
|
||||||
http.Success(w, nil)
|
h.Success(nil)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/factory"
|
"git.toowon.com/jimmy/go-common/factory"
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -16,80 +17,148 @@ func main() {
|
|||||||
log.Fatal("Failed to create factory:", err)
|
log.Fatal("Failed to create factory:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接获取数据库对象(已初始化,可直接使用)
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// ========== 日志记录(黑盒模式,推荐) ==========
|
||||||
|
fac.LogInfo("应用启动")
|
||||||
|
fac.LogDebug("调试信息: %s", "test")
|
||||||
|
fac.LogWarn("警告信息")
|
||||||
|
fac.LogError("错误信息: %v", fmt.Errorf("test error"))
|
||||||
|
|
||||||
|
// 带字段的日志
|
||||||
|
fac.LogInfof(map[string]interface{}{
|
||||||
|
"user_id": 123,
|
||||||
|
"ip": "192.168.1.1",
|
||||||
|
}, "用户登录成功")
|
||||||
|
|
||||||
|
fac.LogErrorf(map[string]interface{}{
|
||||||
|
"error_code": 1001,
|
||||||
|
"user_id": 123,
|
||||||
|
}, "登录失败: %v", fmt.Errorf("invalid password"))
|
||||||
|
|
||||||
|
// ========== 邮件发送(黑盒模式,推荐) ==========
|
||||||
|
err = fac.SendEmail(
|
||||||
|
[]string{"user@example.com"},
|
||||||
|
"验证码",
|
||||||
|
"您的验证码是:123456",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("发送邮件失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("邮件发送成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML邮件
|
||||||
|
err = fac.SendEmail(
|
||||||
|
[]string{"user@example.com"},
|
||||||
|
"欢迎",
|
||||||
|
"纯文本内容",
|
||||||
|
"<h1>HTML内容</h1>",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("发送HTML邮件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 短信发送(黑盒模式,推荐) ==========
|
||||||
|
resp, err := fac.SendSMS(
|
||||||
|
[]string{"13800138000"},
|
||||||
|
map[string]string{"code": "123456"},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("发送短信失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("短信发送成功: %s", resp.RequestID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定模板代码
|
||||||
|
resp, err = fac.SendSMS(
|
||||||
|
[]string{"13800138000"},
|
||||||
|
map[string]string{"code": "123456"},
|
||||||
|
"SMS_123456789", // 模板代码
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("发送短信失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 文件上传(黑盒模式,推荐,自动选择OSS或MinIO) ==========
|
||||||
|
file, err := os.Open("test.jpg")
|
||||||
|
if err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
url, err := fac.UploadFile(ctx, "images/test.jpg", file, "image/jpeg")
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("上传文件失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("文件上传成功: %s", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 获取文件URL(黑盒模式) ==========
|
||||||
|
// 永久有效
|
||||||
|
url, err := fac.GetFileURL("images/test.jpg", 0)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("获取文件URL失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("文件URL: %s", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 临时访问URL(1小时后过期)
|
||||||
|
url, err = fac.GetFileURL("images/test.jpg", 3600)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("获取临时URL失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("临时URL: %s", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Redis操作(黑盒模式,推荐) ==========
|
||||||
|
// 设置值(不过期)
|
||||||
|
err = fac.RedisSet(ctx, "user:123", "value")
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("Redis设置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置值(带过期时间)
|
||||||
|
err = fac.RedisSet(ctx, "user:123", "value", time.Hour)
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("Redis设置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取值
|
||||||
|
value, err := fac.RedisGet(ctx, "user:123")
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("Redis获取失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("Redis值: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除键
|
||||||
|
err = fac.RedisDelete(ctx, "user:123", "user:456")
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("Redis删除失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查键是否存在
|
||||||
|
exists, err := fac.RedisExists(ctx, "user:123")
|
||||||
|
if err != nil {
|
||||||
|
fac.LogError("Redis检查失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fac.LogInfo("键是否存在: %v", exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 数据库操作 ==========
|
||||||
db, err := fac.GetDatabase()
|
db, err := fac.GetDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Database not available: %v", err)
|
fac.LogError("数据库连接失败: %v", err)
|
||||||
} else {
|
} else {
|
||||||
// 直接使用数据库
|
// 直接使用GORM
|
||||||
var count int64
|
var count int64
|
||||||
if err := db.Table("users").Count(&count).Error; err != nil {
|
if err := db.Table("users").Count(&count).Error; err != nil {
|
||||||
log.Printf("Failed to count users: %v", err)
|
fac.LogError("查询用户数量失败: %v", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("User count: %d\n", count)
|
fac.LogInfo("用户数量: %d", count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接获取Redis客户端(已初始化,可直接使用)
|
|
||||||
redisClient, err := fac.GetRedisClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Redis not available: %v", err)
|
|
||||||
} else {
|
|
||||||
// 直接使用Redis客户端
|
|
||||||
ctx := context.Background()
|
|
||||||
val, err := redisClient.Get(ctx, "test_key").Result()
|
|
||||||
if err != nil && err != redis.Nil {
|
|
||||||
log.Printf("Redis error: %v", err)
|
|
||||||
} else if err == redis.Nil {
|
|
||||||
fmt.Println("Redis key not found")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Redis value: %s\n", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取邮件客户端(已初始化,可直接使用)
|
fac.LogInfo("示例执行完成")
|
||||||
emailClient, err := fac.GetEmailClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Email client not available: %v", err)
|
|
||||||
} else {
|
|
||||||
// 直接使用
|
|
||||||
err = emailClient.SendSimple(
|
|
||||||
[]string{"recipient@example.com"},
|
|
||||||
"测试邮件",
|
|
||||||
"这是测试内容",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to send email: %v", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Email sent successfully")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取短信客户端(已初始化,可直接使用)
|
|
||||||
smsClient, err := fac.GetSMSClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("SMS client not available: %v", err)
|
|
||||||
} else {
|
|
||||||
// 直接使用
|
|
||||||
resp, err := smsClient.SendSimple(
|
|
||||||
[]string{"13800138000"},
|
|
||||||
map[string]string{"code": "123456"},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to send SMS: %v", err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("SMS sent: %s\n", resp.RequestID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取日志记录器(已初始化,可直接使用)
|
|
||||||
logger, err := fac.GetLogger()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Logger not available: %v", err)
|
|
||||||
} else {
|
|
||||||
logger.Info("Application started")
|
|
||||||
logger.Debug("Debug message")
|
|
||||||
logger.Warn("Warning message")
|
|
||||||
logger.Error("Error message")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 用户结构
|
|
||||||
type User struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户列表
|
|
||||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// 获取分页参数
|
|
||||||
page, pageSize := http.GetPaginationParams(r)
|
|
||||||
|
|
||||||
// 获取查询参数
|
|
||||||
keyword := http.GetQuery(r, "keyword", "")
|
|
||||||
|
|
||||||
// 模拟查询数据
|
|
||||||
users := []User{
|
|
||||||
{ID: 1, Name: "User1", Email: "user1@example.com"},
|
|
||||||
{ID: 2, Name: "User2", Email: "user2@example.com"},
|
|
||||||
}
|
|
||||||
total := int64(100)
|
|
||||||
|
|
||||||
// 返回分页响应
|
|
||||||
http.SuccessPage(w, users, total, page, pageSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建用户
|
|
||||||
func CreateUser(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// 解析请求体
|
|
||||||
var req struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := http.ParseJSON(r, &req); err != nil {
|
|
||||||
http.BadRequest(w, "请求参数解析失败")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 参数验证
|
|
||||||
if req.Name == "" {
|
|
||||||
http.Error(w, 1001, "用户名不能为空")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟创建用户
|
|
||||||
user := User{
|
|
||||||
ID: 1,
|
|
||||||
Name: req.Name,
|
|
||||||
Email: req.Email,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回成功响应
|
|
||||||
http.SuccessWithMessage(w, "创建成功", user)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户详情
|
|
||||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// 获取查询参数
|
|
||||||
id := http.GetQueryInt64(r, "id", 0)
|
|
||||||
|
|
||||||
if id == 0 {
|
|
||||||
http.BadRequest(w, "用户ID不能为空")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟查询用户
|
|
||||||
if id == 1 {
|
|
||||||
user := User{ID: 1, Name: "User1", Email: "user1@example.com"}
|
|
||||||
http.Success(w, user)
|
|
||||||
} else {
|
|
||||||
http.Error(w, 1002, "用户不存在")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
GetUserList(w, r)
|
|
||||||
case http.MethodPost:
|
|
||||||
CreateUser(w, r)
|
|
||||||
default:
|
|
||||||
http.NotFound(w, "方法不支持")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("/user", GetUser)
|
|
||||||
|
|
||||||
log.Println("Server started on :8080")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
109
examples/http_handler_example.go
Normal file
109
examples/http_handler_example.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 用户结构
|
||||||
|
type User struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户列表(使用Handler黑盒模式)
|
||||||
|
func GetUserList(h *commonhttp.Handler) {
|
||||||
|
// 获取分页参数(简洁方式)
|
||||||
|
pagination := h.ParsePaginationRequest()
|
||||||
|
page := pagination.GetPage()
|
||||||
|
pageSize := pagination.GetSize()
|
||||||
|
|
||||||
|
// 获取查询参数(简洁方式)
|
||||||
|
_ = h.GetQuery("keyword", "") // 示例:获取查询参数
|
||||||
|
|
||||||
|
// 模拟查询数据
|
||||||
|
users := []User{
|
||||||
|
{ID: 1, Name: "User1", Email: "user1@example.com"},
|
||||||
|
{ID: 2, Name: "User2", Email: "user2@example.com"},
|
||||||
|
}
|
||||||
|
total := int64(100)
|
||||||
|
|
||||||
|
// 返回分页响应(简洁方式)
|
||||||
|
h.SuccessPage(users, total, page, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户(使用Handler黑盒模式)
|
||||||
|
func CreateUser(h *commonhttp.Handler) {
|
||||||
|
// 解析请求体(简洁方式)
|
||||||
|
var req struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.ParseJSON(&req); err != nil {
|
||||||
|
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数验证
|
||||||
|
if req.Name == "" {
|
||||||
|
h.Error(1001, "用户名不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟创建用户
|
||||||
|
user := User{
|
||||||
|
ID: 1,
|
||||||
|
Name: req.Name,
|
||||||
|
Email: req.Email,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回成功响应(简洁方式)
|
||||||
|
h.SuccessWithMessage("创建成功", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户详情(使用Handler黑盒模式)
|
||||||
|
func GetUser(h *commonhttp.Handler) {
|
||||||
|
// 获取查询参数(简洁方式)
|
||||||
|
id := h.GetQueryInt64("id", 0)
|
||||||
|
|
||||||
|
if id == 0 {
|
||||||
|
h.WriteJSON(http.StatusBadRequest, 400, "用户ID不能为空", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟查询用户
|
||||||
|
if id == 1 {
|
||||||
|
user := User{ID: 1, Name: "User1", Email: "user1@example.com"}
|
||||||
|
h.Success(user)
|
||||||
|
} else {
|
||||||
|
h.Error(1002, "用户不存在")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 方式1:使用HandleFunc包装器(推荐,最简洁)
|
||||||
|
http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||||
|
switch h.Request().Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
GetUserList(h)
|
||||||
|
case http.MethodPost:
|
||||||
|
CreateUser(h)
|
||||||
|
default:
|
||||||
|
h.WriteJSON(http.StatusMethodNotAllowed, 405, "方法不支持", nil)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 方式2:手动创建Handler(需要更多控制时)
|
||||||
|
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h := commonhttp.NewHandler(w, r)
|
||||||
|
GetUser(h)
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("Server started on :8080")
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
||||||
61
examples/http_pagination_example.go
Normal file
61
examples/http_pagination_example.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListUserRequest 用户列表请求(包含分页字段)
|
||||||
|
type ListUserRequest struct {
|
||||||
|
Keyword string `json:"keyword"`
|
||||||
|
commonhttp.PaginationRequest // 嵌入分页请求结构
|
||||||
|
}
|
||||||
|
|
||||||
|
// User 用户结构
|
||||||
|
type User struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户列表(使用Handler和PaginationRequest)
|
||||||
|
func GetUserList(h *commonhttp.Handler) {
|
||||||
|
var req ListUserRequest
|
||||||
|
|
||||||
|
// 方式1:从JSON请求体解析(分页字段会自动解析)
|
||||||
|
if h.Request().Method == http.MethodPost {
|
||||||
|
if err := h.ParseJSON(&req); err != nil {
|
||||||
|
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 方式2:从查询参数解析分页
|
||||||
|
pagination := h.ParsePaginationRequest()
|
||||||
|
req.PaginationRequest = *pagination
|
||||||
|
req.Keyword = h.GetQuery("keyword", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用分页方法
|
||||||
|
page := req.GetPage() // 获取页码(默认1)
|
||||||
|
size := req.GetSize() // 获取每页数量(默认20,最大100)
|
||||||
|
_ = req.GetOffset() // 计算偏移量
|
||||||
|
|
||||||
|
// 模拟查询数据
|
||||||
|
users := []User{
|
||||||
|
{ID: 1, Name: "User1", Email: "user1@example.com"},
|
||||||
|
{ID: 2, Name: "User2", Email: "user2@example.com"},
|
||||||
|
}
|
||||||
|
total := int64(100)
|
||||||
|
|
||||||
|
// 返回分页响应
|
||||||
|
h.SuccessPage(users, total, page, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/users", commonhttp.HandleFunc(GetUserList))
|
||||||
|
|
||||||
|
log.Println("Server started on :8080")
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/datetime"
|
"git.toowon.com/jimmy/go-common/datetime"
|
||||||
"git.toowon.com/jimmy/go-common/http"
|
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||||
"git.toowon.com/jimmy/go-common/middleware"
|
"git.toowon.com/jimmy/go-common/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,8 +31,11 @@ func main() {
|
|||||||
middleware.Timezone,
|
middleware.Timezone,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 定义处理器
|
// 定义处理器(使用Handler模式)
|
||||||
handler := chain.ThenFunc(apiHandler)
|
handler := chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h := commonhttp.NewHandler(w, r)
|
||||||
|
apiHandler(h)
|
||||||
|
})
|
||||||
|
|
||||||
// 注册路由
|
// 注册路由
|
||||||
http.Handle("/api", handler)
|
http.Handle("/api", handler)
|
||||||
@@ -42,10 +45,10 @@ func main() {
|
|||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiHandler 处理API请求
|
// apiHandler 处理API请求(使用Handler模式)
|
||||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
func apiHandler(h *commonhttp.Handler) {
|
||||||
// 从context获取时区
|
// 从Handler获取时区
|
||||||
timezone := http.GetTimezone(r)
|
timezone := h.GetTimezone()
|
||||||
|
|
||||||
// 使用时区进行时间处理
|
// 使用时区进行时间处理
|
||||||
now := datetime.Now(timezone)
|
now := datetime.Now(timezone)
|
||||||
@@ -53,7 +56,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
endOfDay := datetime.EndOfDay(now, timezone)
|
endOfDay := datetime.EndOfDay(now, timezone)
|
||||||
|
|
||||||
// 返回响应
|
// 返回响应
|
||||||
http.Success(w, map[string]interface{}{
|
h.Success(map[string]interface{}{
|
||||||
"message": "Hello from API",
|
"message": "Hello from API",
|
||||||
"timezone": timezone,
|
"timezone": timezone,
|
||||||
"currentTime": datetime.FormatDateTime(now),
|
"currentTime": datetime.FormatDateTime(now),
|
||||||
@@ -61,4 +64,3 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
"endOfDay": datetime.FormatDateTime(endOfDay),
|
"endOfDay": datetime.FormatDateTime(endOfDay),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package factory
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/config"
|
"git.toowon.com/jimmy/go-common/config"
|
||||||
"git.toowon.com/jimmy/go-common/email"
|
"git.toowon.com/jimmy/go-common/email"
|
||||||
"git.toowon.com/jimmy/go-common/logger"
|
"git.toowon.com/jimmy/go-common/logger"
|
||||||
"git.toowon.com/jimmy/go-common/sms"
|
"git.toowon.com/jimmy/go-common/sms"
|
||||||
|
"git.toowon.com/jimmy/go-common/storage"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
@@ -18,7 +20,13 @@ import (
|
|||||||
|
|
||||||
// Factory 工厂类,用于从配置创建各种客户端对象
|
// Factory 工厂类,用于从配置创建各种客户端对象
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
|
storage storage.Storage // 存储实例(延迟初始化)
|
||||||
|
logger *logger.Logger // 日志实例(延迟初始化)
|
||||||
|
email *email.Email // 邮件客户端(延迟初始化)
|
||||||
|
sms *sms.SMS // 短信客户端(延迟初始化)
|
||||||
|
db *gorm.DB // 数据库连接(延迟初始化)
|
||||||
|
redis *redis.Client // Redis客户端(延迟初始化)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFactory 创建工厂实例
|
// NewFactory 创建工厂实例
|
||||||
@@ -38,37 +46,275 @@ func NewFactoryFromFile(filePath string) (*Factory, error) {
|
|||||||
return NewFactory(cfg), nil
|
return NewFactory(cfg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEmailClient 获取邮件客户端(已初始化)
|
// getEmailClient 获取邮件客户端(内部方法,延迟初始化)
|
||||||
// 返回已初始化的邮件客户端对象,可直接使用
|
func (f *Factory) getEmailClient() (*email.Email, error) {
|
||||||
func (f *Factory) GetEmailClient() (*email.Email, error) {
|
if f.email != nil {
|
||||||
|
return f.email, nil
|
||||||
|
}
|
||||||
|
|
||||||
if f.cfg.Email == nil {
|
if f.cfg.Email == nil {
|
||||||
return nil, fmt.Errorf("email config is nil")
|
return nil, fmt.Errorf("email config is nil")
|
||||||
}
|
}
|
||||||
return email.NewEmail(f.cfg.Email)
|
|
||||||
|
e, err := email.NewEmail(f.cfg.Email)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create email client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.email = e
|
||||||
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSMSClient 获取短信客户端(已初始化)
|
// SendEmail 发送邮件(黑盒模式)
|
||||||
// 返回已初始化的短信客户端对象,可直接使用
|
// to: 收件人列表
|
||||||
func (f *Factory) GetSMSClient() (*sms.SMS, error) {
|
// subject: 邮件主题
|
||||||
|
// body: 邮件正文(纯文本)
|
||||||
|
// htmlBody: HTML正文(可选,如果设置了会优先使用)
|
||||||
|
func (f *Factory) SendEmail(to []string, subject, body string, htmlBody ...string) error {
|
||||||
|
e, err := f.getEmailClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &email.Message{
|
||||||
|
To: to,
|
||||||
|
Subject: subject,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(htmlBody) > 0 && htmlBody[0] != "" {
|
||||||
|
msg.HTMLBody = htmlBody[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSMSClient 获取短信客户端(内部方法,延迟初始化)
|
||||||
|
func (f *Factory) getSMSClient() (*sms.SMS, error) {
|
||||||
|
if f.sms != nil {
|
||||||
|
return f.sms, nil
|
||||||
|
}
|
||||||
|
|
||||||
if f.cfg.SMS == nil {
|
if f.cfg.SMS == nil {
|
||||||
return nil, fmt.Errorf("SMS config is nil")
|
return nil, fmt.Errorf("SMS config is nil")
|
||||||
}
|
}
|
||||||
return sms.NewSMS(f.cfg.SMS)
|
|
||||||
|
s, err := sms.NewSMS(f.cfg.SMS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create SMS client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.sms = s
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogger 获取日志记录器(已初始化)
|
// SendSMS 发送短信(黑盒模式)
|
||||||
// 返回已初始化的日志记录器对象,可直接使用
|
// phoneNumbers: 手机号列表
|
||||||
func (f *Factory) GetLogger() (*logger.Logger, error) {
|
// templateParam: 模板参数(map或JSON字符串)
|
||||||
|
// templateCode: 模板代码(可选,如果为空使用配置中的模板代码)
|
||||||
|
func (f *Factory) SendSMS(phoneNumbers []string, templateParam interface{}, templateCode ...string) (*sms.SendResponse, error) {
|
||||||
|
s, err := f.getSMSClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &sms.SendRequest{
|
||||||
|
PhoneNumbers: phoneNumbers,
|
||||||
|
TemplateParam: templateParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(templateCode) > 0 && templateCode[0] != "" {
|
||||||
|
req.TemplateCode = templateCode[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Send(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLogger 获取日志记录器(内部方法,延迟初始化)
|
||||||
|
func (f *Factory) getLogger() (*logger.Logger, error) {
|
||||||
|
if f.logger != nil {
|
||||||
|
return f.logger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l *logger.Logger
|
||||||
|
var err error
|
||||||
if f.cfg.Logger == nil {
|
if f.cfg.Logger == nil {
|
||||||
// 如果没有配置,使用默认配置创建
|
// 如果没有配置,使用默认配置创建
|
||||||
return logger.NewLogger(nil)
|
l, err = logger.NewLogger(nil)
|
||||||
|
} else {
|
||||||
|
l, err = logger.NewLogger(f.cfg.Logger)
|
||||||
}
|
}
|
||||||
return logger.NewLogger(f.cfg.Logger)
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create logger: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.logger = l
|
||||||
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDatabase 获取数据库连接对象(已初始化)
|
// LogDebug 记录调试日志
|
||||||
// 返回已初始化的GORM数据库对象,可直接使用
|
// message: 日志消息
|
||||||
func (f *Factory) GetDatabase() (*gorm.DB, error) {
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogDebug(message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[DEBUG] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[DEBUG] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
l.Debug(message, args...)
|
||||||
|
} else {
|
||||||
|
l.Debug(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogDebugf 记录调试日志(带字段)
|
||||||
|
// fields: 日志字段
|
||||||
|
// message: 日志消息
|
||||||
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogDebugf(fields map[string]interface{}, message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[DEBUG] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[DEBUG] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.Debugf(fields, message, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogInfo 记录信息日志
|
||||||
|
// message: 日志消息
|
||||||
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogInfo(message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[INFO] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[INFO] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
l.Info(message, args...)
|
||||||
|
} else {
|
||||||
|
l.Info(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogInfof 记录信息日志(带字段)
|
||||||
|
// fields: 日志字段
|
||||||
|
// message: 日志消息
|
||||||
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogInfof(fields map[string]interface{}, message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[INFO] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[INFO] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.Infof(fields, message, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogWarn 记录警告日志
|
||||||
|
// message: 日志消息
|
||||||
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogWarn(message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[WARN] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[WARN] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
l.Warn(message, args...)
|
||||||
|
} else {
|
||||||
|
l.Warn(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogWarnf 记录警告日志(带字段)
|
||||||
|
// fields: 日志字段
|
||||||
|
// message: 日志消息
|
||||||
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogWarnf(fields map[string]interface{}, message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[WARN] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[WARN] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.Warnf(fields, message, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogError 记录错误日志
|
||||||
|
// message: 日志消息
|
||||||
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogError(message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[ERROR] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[ERROR] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
l.Error(message, args...)
|
||||||
|
} else {
|
||||||
|
l.Error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogErrorf 记录错误日志(带字段)
|
||||||
|
// fields: 日志字段
|
||||||
|
// message: 日志消息
|
||||||
|
// args: 格式化参数(可选)
|
||||||
|
func (f *Factory) LogErrorf(fields map[string]interface{}, message string, args ...interface{}) {
|
||||||
|
l, err := f.getLogger()
|
||||||
|
if err != nil {
|
||||||
|
// 如果日志初始化失败,使用标准输出
|
||||||
|
if len(args) > 0 {
|
||||||
|
fmt.Printf("[ERROR] "+message+"\n", args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[ERROR] %s\n", message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.Errorf(fields, message, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDatabase 获取数据库连接对象(内部方法,延迟初始化)
|
||||||
|
func (f *Factory) getDatabase() (*gorm.DB, error) {
|
||||||
|
if f.db != nil {
|
||||||
|
return f.db, nil
|
||||||
|
}
|
||||||
|
|
||||||
if f.cfg.Database == nil {
|
if f.cfg.Database == nil {
|
||||||
return nil, fmt.Errorf("database config is nil")
|
return nil, fmt.Errorf("database config is nil")
|
||||||
}
|
}
|
||||||
@@ -112,12 +358,23 @@ func (f *Factory) GetDatabase() (*gorm.DB, error) {
|
|||||||
sqlDB.SetConnMaxLifetime(time.Duration(f.cfg.Database.ConnMaxLifetime) * time.Second)
|
sqlDB.SetConnMaxLifetime(time.Duration(f.cfg.Database.ConnMaxLifetime) * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.db = db
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRedisClient 获取Redis客户端对象(已初始化)
|
// GetDatabase 获取数据库连接对象(已初始化)
|
||||||
// 返回已初始化的Redis客户端对象,可直接使用
|
// 返回已初始化的GORM数据库对象,可直接使用
|
||||||
func (f *Factory) GetRedisClient() (*redis.Client, error) {
|
// 注意:数据库保持返回GORM对象,因为GORM已经提供了很好的抽象
|
||||||
|
func (f *Factory) GetDatabase() (*gorm.DB, error) {
|
||||||
|
return f.getDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRedisClient 获取Redis客户端对象(内部方法,延迟初始化)
|
||||||
|
func (f *Factory) getRedisClient() (*redis.Client, error) {
|
||||||
|
if f.redis != nil {
|
||||||
|
return f.redis, nil
|
||||||
|
}
|
||||||
|
|
||||||
if f.cfg.Redis == nil {
|
if f.cfg.Redis == nil {
|
||||||
return nil, fmt.Errorf("redis config is nil")
|
return nil, fmt.Errorf("redis config is nil")
|
||||||
}
|
}
|
||||||
@@ -169,17 +426,155 @@ func (f *Factory) GetRedisClient() (*redis.Client, error) {
|
|||||||
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.redis = client
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRedisConfig 获取Redis配置(用于创建Redis客户端)
|
// RedisGet 获取Redis值(黑盒模式)
|
||||||
// 返回Redis配置对象,调用方可以使用此配置创建Redis客户端
|
// key: Redis键
|
||||||
// 注意:推荐使用 GetRedisClient 方法直接获取已初始化的客户端
|
func (f *Factory) RedisGet(ctx context.Context, key string) (string, error) {
|
||||||
func (f *Factory) GetRedisConfig() *config.RedisConfig {
|
client, err := f.getRedisClient()
|
||||||
return f.cfg.Redis
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := client.Get(ctx, key).Result()
|
||||||
|
if err == redis.Nil {
|
||||||
|
return "", nil // key不存在,返回空字符串
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get redis key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisSet 设置Redis值(黑盒模式)
|
||||||
|
// key: Redis键
|
||||||
|
// value: Redis值
|
||||||
|
// expiration: 过期时间(可选,0表示不过期)
|
||||||
|
func (f *Factory) RedisSet(ctx context.Context, key string, value interface{}, expiration ...time.Duration) error {
|
||||||
|
client, err := f.getRedisClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var exp time.Duration
|
||||||
|
if len(expiration) > 0 {
|
||||||
|
exp = expiration[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Set(ctx, key, value, exp).Err()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set redis key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisDelete 删除Redis键(黑盒模式)
|
||||||
|
// keys: Redis键列表
|
||||||
|
func (f *Factory) RedisDelete(ctx context.Context, keys ...string) error {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := f.getRedisClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Del(ctx, keys...).Err()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete redis keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisExists 检查Redis键是否存在(黑盒模式)
|
||||||
|
// key: Redis键
|
||||||
|
func (f *Factory) RedisExists(ctx context.Context, key string) (bool, error) {
|
||||||
|
client, err := f.getRedisClient()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := client.Exists(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to check redis key existence: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig 获取配置对象
|
// GetConfig 获取配置对象
|
||||||
func (f *Factory) GetConfig() *config.Config {
|
func (f *Factory) GetConfig() *config.Config {
|
||||||
return f.cfg
|
return f.cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getStorage 获取存储实例(内部方法,延迟初始化)
|
||||||
|
func (f *Factory) getStorage() (storage.Storage, error) {
|
||||||
|
if f.storage != nil {
|
||||||
|
return f.storage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置自动选择存储类型
|
||||||
|
// 优先级:MinIO > OSS
|
||||||
|
var storageType storage.StorageType
|
||||||
|
if f.cfg.MinIO != nil {
|
||||||
|
storageType = storage.StorageTypeMinIO
|
||||||
|
} else if f.cfg.OSS != nil {
|
||||||
|
storageType = storage.StorageTypeOSS
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("no storage config found (OSS or MinIO)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建存储实例
|
||||||
|
s, err := storage.NewStorage(storageType, f.cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create storage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.storage = s
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadFile 上传文件
|
||||||
|
// ctx: 上下文
|
||||||
|
// objectKey: 对象键(文件路径)
|
||||||
|
// reader: 文件内容
|
||||||
|
// contentType: 文件类型(可选)
|
||||||
|
// 返回文件访问URL和错误
|
||||||
|
func (f *Factory) UploadFile(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) (string, error) {
|
||||||
|
s, err := f.getStorage()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
err = s.Upload(ctx, objectKey, reader, contentType...)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to upload file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件URL
|
||||||
|
url, err := s.GetURL(objectKey, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get file URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileURL 获取文件访问URL(Show方法)
|
||||||
|
// objectKey: 对象键
|
||||||
|
// expires: 过期时间(秒),0表示永久有效
|
||||||
|
func (f *Factory) GetFileURL(objectKey string, expires int64) (string, error) {
|
||||||
|
s, err := f.getStorage()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetURL(objectKey, expires)
|
||||||
|
}
|
||||||
|
|||||||
29
go.mod
29
go.mod
@@ -1,8 +1,12 @@
|
|||||||
module git.toowon.com/jimmy/go-common
|
module git.toowon.com/jimmy/go-common
|
||||||
|
|
||||||
go 1.21
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.10
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/minio/minio-go/v7 v7.0.97
|
||||||
|
github.com/redis/go-redis/v9 v9.17.1
|
||||||
gorm.io/driver/mysql v1.5.2
|
gorm.io/driver/mysql v1.5.2
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/driver/sqlite v1.6.0
|
gorm.io/driver/sqlite v1.6.0
|
||||||
@@ -12,16 +16,31 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||||
|
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.17.1 // indirect
|
github.com/minio/crc64nvme v1.1.0 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
|
github.com/tinylib/msgp v1.3.0 // indirect
|
||||||
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
|
golang.org/x/net v0.38.0 // indirect
|
||||||
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
|
golang.org/x/text v0.26.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
58
go.sum
58
go.sum
@@ -1,13 +1,24 @@
|
|||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -20,24 +31,55 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||||
|
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/minio/crc64nvme v1.1.0 h1:e/tAguZ+4cw32D+IO/8GSf5UVr9y+3eJcxZI2WOO/7Q=
|
||||||
|
github.com/minio/crc64nvme v1.1.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||||
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.97 h1:lqhREPyfgHTB/ciX8k2r8k0D93WaFqxbJX36UZq5occ=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.97/go.mod h1:re5VXuo0pwEtoNLsNuSr0RrLfT/MBtohwdaSmPPSRSk=
|
||||||
|
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.17.1 h1:7tl732FjYPRT9H9aNfyTwKg9iTETjWjGKEJ2t/5iWTs=
|
github.com/redis/go-redis/v9 v9.17.1 h1:7tl732FjYPRT9H9aNfyTwKg9iTETjWjGKEJ2t/5iWTs=
|
||||||
github.com/redis/go-redis/v9 v9.17.1/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.17.1/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
279
http/handler.go
Normal file
279
http/handler.go
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.toowon.com/jimmy/go-common/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler HTTP处理器包装器,封装ResponseWriter和Request,提供简洁的API
|
||||||
|
type Handler struct {
|
||||||
|
w http.ResponseWriter
|
||||||
|
r *http.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler 创建Handler实例
|
||||||
|
func NewHandler(w http.ResponseWriter, r *http.Request) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
w: w,
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseWriter 获取原始的ResponseWriter(需要时使用)
|
||||||
|
func (h *Handler) ResponseWriter() http.ResponseWriter {
|
||||||
|
return h.w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request 获取原始的Request(需要时使用)
|
||||||
|
func (h *Handler) Request() *http.Request {
|
||||||
|
return h.r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context 获取请求的Context
|
||||||
|
func (h *Handler) Context() context.Context {
|
||||||
|
return h.r.Context()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 响应方法(黑盒模式) ==========
|
||||||
|
|
||||||
|
// Success 成功响应
|
||||||
|
// data: 响应数据,可以为nil
|
||||||
|
func (h *Handler) Success(data interface{}) {
|
||||||
|
writeJSON(h.w, http.StatusOK, 0, "success", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuccessWithMessage 带消息的成功响应
|
||||||
|
func (h *Handler) SuccessWithMessage(message string, data interface{}) {
|
||||||
|
writeJSON(h.w, http.StatusOK, 0, message, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 错误响应
|
||||||
|
// code: 业务错误码,非0表示业务错误
|
||||||
|
// message: 错误消息
|
||||||
|
func (h *Handler) Error(code int, message string) {
|
||||||
|
writeJSON(h.w, http.StatusOK, code, message, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemError 系统错误响应(返回HTTP 500)
|
||||||
|
// message: 错误消息
|
||||||
|
func (h *Handler) SystemError(message string) {
|
||||||
|
writeJSON(h.w, http.StatusInternalServerError, 500, message, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON 写入JSON响应(自定义HTTP状态码和业务状态码)
|
||||||
|
// httpCode: HTTP状态码(200表示正常,500表示系统错误等)
|
||||||
|
// code: 业务状态码(0表示成功,非0表示业务错误)
|
||||||
|
// message: 响应消息
|
||||||
|
// data: 响应数据
|
||||||
|
func (h *Handler) WriteJSON(httpCode, code int, message string, data interface{}) {
|
||||||
|
writeJSON(h.w, httpCode, code, message, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuccessPage 分页成功响应
|
||||||
|
// list: 数据列表
|
||||||
|
// total: 总记录数
|
||||||
|
// page: 当前页码
|
||||||
|
// pageSize: 每页大小
|
||||||
|
// message: 响应消息(可选,如果为空则使用默认消息 "success")
|
||||||
|
func (h *Handler) SuccessPage(list interface{}, total int64, page, pageSize int, message ...string) {
|
||||||
|
msg := "success"
|
||||||
|
if len(message) > 0 && message[0] != "" {
|
||||||
|
msg = message[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
pageData := &PageData{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(h.w, http.StatusOK, 0, msg, pageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 请求解析方法(黑盒模式) ==========
|
||||||
|
|
||||||
|
// ParseJSON 解析JSON请求体
|
||||||
|
// v: 目标结构体指针
|
||||||
|
func (h *Handler) ParseJSON(v interface{}) error {
|
||||||
|
body, err := io.ReadAll(h.r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer h.r.Body.Close()
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(body, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQuery 获取查询参数
|
||||||
|
// key: 参数名
|
||||||
|
// defaultValue: 默认值
|
||||||
|
func (h *Handler) GetQuery(key, defaultValue string) string {
|
||||||
|
value := h.r.URL.Query().Get(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryInt 获取整数查询参数
|
||||||
|
// key: 参数名
|
||||||
|
// defaultValue: 默认值
|
||||||
|
func (h *Handler) GetQueryInt(key string, defaultValue int) int {
|
||||||
|
value := h.r.URL.Query().Get(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryInt64 获取int64查询参数
|
||||||
|
func (h *Handler) GetQueryInt64(key string, defaultValue int64) int64 {
|
||||||
|
value := h.r.URL.Query().Get(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryBool 获取布尔查询参数
|
||||||
|
func (h *Handler) GetQueryBool(key string, defaultValue bool) bool {
|
||||||
|
value := h.r.URL.Query().Get(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
boolValue, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return boolValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryFloat64 获取float64查询参数
|
||||||
|
func (h *Handler) GetQueryFloat64(key string, defaultValue float64) float64 {
|
||||||
|
value := h.r.URL.Query().Get(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
floatValue, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return floatValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormValue 获取表单值
|
||||||
|
func (h *Handler) GetFormValue(key, defaultValue string) string {
|
||||||
|
value := h.r.FormValue(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormInt 获取表单整数
|
||||||
|
func (h *Handler) GetFormInt(key string, defaultValue int) int {
|
||||||
|
value := h.r.FormValue(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormInt64 获取表单int64
|
||||||
|
func (h *Handler) GetFormInt64(key string, defaultValue int64) int64 {
|
||||||
|
value := h.r.FormValue(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormBool 获取表单布尔值
|
||||||
|
func (h *Handler) GetFormBool(key string, defaultValue bool) bool {
|
||||||
|
value := h.r.FormValue(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
boolValue, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return boolValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeader 获取请求头
|
||||||
|
func (h *Handler) GetHeader(key, defaultValue string) string {
|
||||||
|
value := h.r.Header.Get(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePaginationRequest 从请求中解析分页参数
|
||||||
|
// 支持从查询参数和form表单中解析
|
||||||
|
// 优先级:查询参数 > form表单
|
||||||
|
func (h *Handler) ParsePaginationRequest() *PaginationRequest {
|
||||||
|
return ParsePaginationRequest(h.r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTimezone 从请求的context中获取时区
|
||||||
|
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||||
|
// 如果未设置,返回默认时区 AsiaShanghai
|
||||||
|
func (h *Handler) GetTimezone() string {
|
||||||
|
return middleware.GetTimezoneFromContext(h.r.Context())
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFunc 将Handler函数转换为标准的http.HandlerFunc
|
||||||
|
// 这样可以将Handler函数直接用于http.HandleFunc
|
||||||
|
// 示例:
|
||||||
|
//
|
||||||
|
// http.HandleFunc("/users", http.HandleFunc(func(h *http.Handler) {
|
||||||
|
// h.Success(data)
|
||||||
|
// }))
|
||||||
|
func HandleFunc(fn func(*Handler)) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h := NewHandler(w, r)
|
||||||
|
fn(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
231
http/request.go
231
http/request.go
@@ -1,49 +1,12 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseJSON 解析JSON请求体
|
// getQueryInt 获取整数查询参数(内部方法,供ParsePaginationRequest使用)
|
||||||
// r: HTTP请求
|
func getQueryInt(r *http.Request, key string, defaultValue int) int {
|
||||||
// v: 目标结构体指针
|
|
||||||
func ParseJSON(r *http.Request, v interface{}) error {
|
|
||||||
body, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
if len(body) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(body, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQuery 获取查询参数
|
|
||||||
// r: HTTP请求
|
|
||||||
// key: 参数名
|
|
||||||
// defaultValue: 默认值
|
|
||||||
func GetQuery(r *http.Request, key, defaultValue string) string {
|
|
||||||
value := r.URL.Query().Get(key)
|
|
||||||
if value == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueryInt 获取整数查询参数
|
|
||||||
// r: HTTP请求
|
|
||||||
// key: 参数名
|
|
||||||
// defaultValue: 默认值
|
|
||||||
func GetQueryInt(r *http.Request, key string, defaultValue int) int {
|
|
||||||
value := r.URL.Query().Get(key)
|
value := r.URL.Query().Get(key)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
@@ -57,62 +20,8 @@ func GetQueryInt(r *http.Request, key string, defaultValue int) int {
|
|||||||
return intValue
|
return intValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQueryInt64 获取int64查询参数
|
// getFormInt 获取表单整数(内部方法,供ParsePaginationRequest使用)
|
||||||
func GetQueryInt64(r *http.Request, key string, defaultValue int64) int64 {
|
func getFormInt(r *http.Request, key string, defaultValue int) int {
|
||||||
value := r.URL.Query().Get(key)
|
|
||||||
if value == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
intValue, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return intValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueryBool 获取布尔查询参数
|
|
||||||
func GetQueryBool(r *http.Request, key string, defaultValue bool) bool {
|
|
||||||
value := r.URL.Query().Get(key)
|
|
||||||
if value == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
boolValue, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return boolValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueryFloat64 获取float64查询参数
|
|
||||||
func GetQueryFloat64(r *http.Request, key string, defaultValue float64) float64 {
|
|
||||||
value := r.URL.Query().Get(key)
|
|
||||||
if value == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
floatValue, err := strconv.ParseFloat(value, 64)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return floatValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFormValue 获取表单值
|
|
||||||
func GetFormValue(r *http.Request, key, defaultValue string) string {
|
|
||||||
value := r.FormValue(key)
|
|
||||||
if value == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFormInt 获取表单整数
|
|
||||||
func GetFormInt(r *http.Request, key string, defaultValue int) int {
|
|
||||||
value := r.FormValue(key)
|
value := r.FormValue(key)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
@@ -126,82 +35,82 @@ func GetFormInt(r *http.Request, key string, defaultValue int) int {
|
|||||||
return intValue
|
return intValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFormInt64 获取表单int64
|
// PaginationRequest 分页请求结构
|
||||||
func GetFormInt64(r *http.Request, key string, defaultValue int64) int64 {
|
// 支持从JSON和form中解析分页参数
|
||||||
value := r.FormValue(key)
|
type PaginationRequest struct {
|
||||||
if value == "" {
|
Page int `json:"page" form:"page"` // 页码,默认1
|
||||||
return defaultValue
|
Size int `json:"size" form:"size"` // 每页数量(兼容旧版本)
|
||||||
}
|
PageSize int `json:"page_size" form:"page_size"` // 每页数量(推荐使用)
|
||||||
|
|
||||||
intValue, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return intValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFormBool 获取表单布尔值
|
// GetPage 获取页码,如果未设置则返回默认值1
|
||||||
func GetFormBool(r *http.Request, key string, defaultValue bool) bool {
|
func (p *PaginationRequest) GetPage() int {
|
||||||
value := r.FormValue(key)
|
if p.Page <= 0 {
|
||||||
if value == "" {
|
return 1
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
|
return p.Page
|
||||||
boolValue, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return boolValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHeader 获取请求头
|
// GetSize 获取每页数量,如果未设置则返回默认值20,最大限制100
|
||||||
func GetHeader(r *http.Request, key, defaultValue string) string {
|
// 优先使用 PageSize 字段,如果未设置则使用 Size 字段(兼容旧版本)
|
||||||
value := r.Header.Get(key)
|
func (p *PaginationRequest) GetSize() int {
|
||||||
if value == "" {
|
size := p.PageSize
|
||||||
return defaultValue
|
if size <= 0 {
|
||||||
|
size = p.Size // 兼容旧版本的 Size 字段
|
||||||
}
|
}
|
||||||
return value
|
if size <= 0 {
|
||||||
|
return 20 // 默认20条
|
||||||
|
}
|
||||||
|
if size > 100 {
|
||||||
|
return 100 // 最大100条
|
||||||
|
}
|
||||||
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPaginationParams 获取分页参数
|
// GetOffset 计算数据库查询的偏移量
|
||||||
// 返回 page, pageSize
|
func (p *PaginationRequest) GetOffset() int {
|
||||||
// 默认 page=1, pageSize=10
|
return (p.GetPage() - 1) * p.GetSize()
|
||||||
func GetPaginationParams(r *http.Request) (page, pageSize int) {
|
|
||||||
page = GetQueryInt(r, "page", 1)
|
|
||||||
pageSize = GetQueryInt(r, "pageSize", 10)
|
|
||||||
|
|
||||||
// 参数校验
|
|
||||||
if page < 1 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
if pageSize < 1 {
|
|
||||||
pageSize = 10
|
|
||||||
}
|
|
||||||
if pageSize > 1000 {
|
|
||||||
pageSize = 1000 // 限制最大页面大小
|
|
||||||
}
|
|
||||||
|
|
||||||
return page, pageSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOffset 根据页码和每页大小计算偏移量
|
// getPaginationFromQuery 从查询参数获取分页参数(内部辅助方法)
|
||||||
func GetOffset(page, pageSize int) int {
|
func getPaginationFromQuery(r *http.Request) (page, size, pageSize int) {
|
||||||
if page < 1 {
|
page = getQueryInt(r, "page", 0)
|
||||||
page = 1
|
size = getQueryInt(r, "size", 0)
|
||||||
|
pageSize = getQueryInt(r, "page_size", 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPaginationFromForm 从form表单获取分页参数(内部辅助方法)
|
||||||
|
func getPaginationFromForm(r *http.Request) (page, size, pageSize int) {
|
||||||
|
page = getFormInt(r, "page", 0)
|
||||||
|
size = getFormInt(r, "size", 0)
|
||||||
|
pageSize = getFormInt(r, "page_size", 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePaginationRequest 从请求中解析分页参数
|
||||||
|
// 支持从查询参数和form表单中解析
|
||||||
|
// 优先级:查询参数 > form表单
|
||||||
|
// 注意:如果请求体是JSON格式且包含分页字段,建议先使用ParseJSON解析完整请求体到包含PaginationRequest的结构体中
|
||||||
|
func ParsePaginationRequest(r *http.Request) *PaginationRequest {
|
||||||
|
req := &PaginationRequest{}
|
||||||
|
|
||||||
|
// 1. 从查询参数解析(优先级最高)
|
||||||
|
req.Page, req.Size, req.PageSize = getPaginationFromQuery(r)
|
||||||
|
|
||||||
|
// 2. 如果查询参数中没有,尝试从form表单解析
|
||||||
|
if req.Page == 0 || (req.Size == 0 && req.PageSize == 0) {
|
||||||
|
page, size, pageSize := getPaginationFromForm(r)
|
||||||
|
if req.Page == 0 && page != 0 {
|
||||||
|
req.Page = page
|
||||||
|
}
|
||||||
|
if req.Size == 0 && size != 0 {
|
||||||
|
req.Size = size
|
||||||
|
}
|
||||||
|
if req.PageSize == 0 && pageSize != 0 {
|
||||||
|
req.PageSize = pageSize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (page - 1) * pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimezone 从请求的context中获取时区
|
return req
|
||||||
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
|
||||||
// 如果未设置,返回默认时区 AsiaShanghai
|
|
||||||
func GetTimezone(r *http.Request) string {
|
|
||||||
return middleware.GetTimezoneFromContext(r.Context())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimezoneFromContext 从context中获取时区
|
|
||||||
func GetTimezoneFromContext(ctx context.Context) string {
|
|
||||||
return middleware.GetTimezoneFromContext(ctx)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,56 +30,12 @@ type PageData struct {
|
|||||||
PageSize int `json:"pageSize"` // 每页大小
|
PageSize int `json:"pageSize"` // 每页大小
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success 成功响应
|
// writeJSON 写入JSON响应(内部方法)
|
||||||
// data: 响应数据,可以为nil
|
|
||||||
func Success(w http.ResponseWriter, data interface{}) {
|
|
||||||
WriteJSON(w, http.StatusOK, 0, "success", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuccessWithMessage 带消息的成功响应
|
|
||||||
func SuccessWithMessage(w http.ResponseWriter, message string, data interface{}) {
|
|
||||||
WriteJSON(w, http.StatusOK, 0, message, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error 错误响应
|
|
||||||
// code: 业务错误码,非0表示业务错误
|
|
||||||
// message: 错误消息
|
|
||||||
func Error(w http.ResponseWriter, code int, message string) {
|
|
||||||
WriteJSON(w, http.StatusOK, code, message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemError 系统错误响应(返回HTTP 500)
|
|
||||||
// message: 错误消息
|
|
||||||
func SystemError(w http.ResponseWriter, message string) {
|
|
||||||
WriteJSON(w, http.StatusInternalServerError, 500, message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BadRequest 请求错误响应(HTTP 400)
|
|
||||||
func BadRequest(w http.ResponseWriter, message string) {
|
|
||||||
WriteJSON(w, http.StatusBadRequest, 400, message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unauthorized 未授权响应(HTTP 401)
|
|
||||||
func Unauthorized(w http.ResponseWriter, message string) {
|
|
||||||
WriteJSON(w, http.StatusUnauthorized, 401, message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forbidden 禁止访问响应(HTTP 403)
|
|
||||||
func Forbidden(w http.ResponseWriter, message string) {
|
|
||||||
WriteJSON(w, http.StatusForbidden, 403, message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotFound 未找到响应(HTTP 404)
|
|
||||||
func NotFound(w http.ResponseWriter, message string) {
|
|
||||||
WriteJSON(w, http.StatusNotFound, 404, message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteJSON 写入JSON响应
|
|
||||||
// httpCode: HTTP状态码(200表示正常,500表示系统错误等)
|
// httpCode: HTTP状态码(200表示正常,500表示系统错误等)
|
||||||
// code: 业务状态码(0表示成功,非0表示业务错误)
|
// code: 业务状态码(0表示成功,非0表示业务错误)
|
||||||
// message: 响应消息
|
// message: 响应消息
|
||||||
// data: 响应数据
|
// data: 响应数据
|
||||||
func WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{}) {
|
func writeJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{}) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.WriteHeader(httpCode)
|
w.WriteHeader(httpCode)
|
||||||
|
|
||||||
@@ -92,47 +48,3 @@ func WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data i
|
|||||||
|
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuccessPage 分页成功响应
|
|
||||||
// list: 数据列表
|
|
||||||
// total: 总记录数
|
|
||||||
// page: 当前页码
|
|
||||||
// pageSize: 每页大小
|
|
||||||
func SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int) {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
response := PageResponse{
|
|
||||||
Code: 0,
|
|
||||||
Message: "success",
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
Data: &PageData{
|
|
||||||
List: list,
|
|
||||||
Total: total,
|
|
||||||
Page: page,
|
|
||||||
PageSize: pageSize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuccessPageWithMessage 带消息的分页成功响应
|
|
||||||
func SuccessPageWithMessage(w http.ResponseWriter, message string, list interface{}, total int64, page, pageSize int) {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
response := PageResponse{
|
|
||||||
Code: 0,
|
|
||||||
Message: message,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
Data: &PageData{
|
|
||||||
List: list,
|
|
||||||
Total: total,
|
|
||||||
Page: page,
|
|
||||||
PageSize: pageSize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(response)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -43,29 +43,31 @@ func NewUploadHandler(cfg UploadHandlerConfig) *UploadHandler {
|
|||||||
// 表单字段: file (文件)
|
// 表单字段: file (文件)
|
||||||
// 可选字段: prefix (对象键前缀,会覆盖配置中的前缀)
|
// 可选字段: prefix (对象键前缀,会覆盖配置中的前缀)
|
||||||
func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handler := commonhttp.NewHandler(w, r)
|
||||||
|
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
commonhttp.NotFound(w, "Method not allowed")
|
handler.Error(4001, "Method not allowed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析multipart表单
|
// 解析multipart表单
|
||||||
err := r.ParseMultipartForm(h.maxFileSize)
|
err := r.ParseMultipartForm(h.maxFileSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
commonhttp.BadRequest(w, fmt.Sprintf("Failed to parse form: %v", err))
|
handler.Error(4002, fmt.Sprintf("Failed to parse form: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件
|
// 获取文件
|
||||||
file, header, err := r.FormFile("file")
|
file, header, err := r.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
commonhttp.BadRequest(w, fmt.Sprintf("Failed to get file: %v", err))
|
handler.Error(4003, fmt.Sprintf("Failed to get file: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// 检查文件大小
|
// 检查文件大小
|
||||||
if h.maxFileSize > 0 && header.Size > h.maxFileSize {
|
if h.maxFileSize > 0 && header.Size > h.maxFileSize {
|
||||||
commonhttp.Error(w, 1001, fmt.Sprintf("File size exceeds limit: %d bytes", h.maxFileSize))
|
handler.Error(1001, fmt.Sprintf("File size exceeds limit: %d bytes", h.maxFileSize))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !allowed {
|
if !allowed {
|
||||||
commonhttp.Error(w, 1002, fmt.Sprintf("File extension not allowed. Allowed: %v", h.allowedExts))
|
handler.Error(1002, fmt.Sprintf("File extension not allowed. Allowed: %v", h.allowedExts))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,14 +110,14 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
err = h.storage.Upload(ctx, objectKey, file, contentType)
|
err = h.storage.Upload(ctx, objectKey, file, contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to upload file: %v", err))
|
handler.SystemError(fmt.Sprintf("Failed to upload file: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件URL
|
// 获取文件URL
|
||||||
fileURL, err := h.storage.GetURL(objectKey, 0)
|
fileURL, err := h.storage.GetURL(objectKey, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to get file URL: %v", err))
|
handler.SystemError(fmt.Sprintf("Failed to get file URL: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +130,7 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
UploadTime: time.Now(),
|
UploadTime: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
commonhttp.SuccessWithMessage(w, "Upload successful", result)
|
handler.SuccessWithMessage("Upload successful", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateUniqueFilename 生成唯一文件名
|
// generateUniqueFilename 生成唯一文件名
|
||||||
@@ -154,15 +156,17 @@ func NewProxyHandler(storage Storage) *ProxyHandler {
|
|||||||
// ServeHTTP 处理文件查看请求
|
// ServeHTTP 处理文件查看请求
|
||||||
// URL参数: key (对象键)
|
// URL参数: key (对象键)
|
||||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handler := commonhttp.NewHandler(w, r)
|
||||||
|
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
commonhttp.NotFound(w, "Method not allowed")
|
handler.Error(4001, "Method not allowed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取对象键
|
// 获取对象键
|
||||||
objectKey := r.URL.Query().Get("key")
|
objectKey := handler.GetQuery("key", "")
|
||||||
if objectKey == "" {
|
if objectKey == "" {
|
||||||
commonhttp.BadRequest(w, "Missing parameter: key")
|
handler.Error(4004, "Missing parameter: key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,19 +174,19 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
exists, err := h.storage.Exists(ctx, objectKey)
|
exists, err := h.storage.Exists(ctx, objectKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to check file existence: %v", err))
|
handler.SystemError(fmt.Sprintf("Failed to check file existence: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
commonhttp.NotFound(w, "File not found")
|
handler.Error(4005, "File not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件内容
|
// 获取文件内容
|
||||||
reader, err := h.storage.GetObject(ctx, objectKey)
|
reader, err := h.storage.GetObject(ctx, objectKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to get file: %v", err))
|
handler.SystemError(fmt.Sprintf("Failed to get file: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
@@ -206,7 +210,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 复制文件内容到响应
|
// 复制文件内容到响应
|
||||||
_, err = io.Copy(w, reader)
|
_, err = io.Copy(w, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to write response: %v", err))
|
handler.SystemError(fmt.Sprintf("Failed to write response: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
200
storage/minio.go
200
storage/minio.go
@@ -5,16 +5,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.toowon.com/jimmy/go-common/config"
|
"git.toowon.com/jimmy/go-common/config"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MinIOStorage MinIO存储实现
|
// MinIOStorage MinIO存储实现
|
||||||
type MinIOStorage struct {
|
type MinIOStorage struct {
|
||||||
config *config.MinIOConfig
|
config *config.MinIOConfig
|
||||||
// client 存储MinIO客户端(实际使用时需要根据具体的MinIO SDK实现)
|
client *minio.Client
|
||||||
// 这里使用interface{},实际使用时需要替换为具体的客户端类型
|
bucket string
|
||||||
client interface{}
|
domain string
|
||||||
|
protocol string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMinIOStorage 创建MinIO存储实例
|
// NewMinIOStorage 创建MinIO存储实例
|
||||||
@@ -23,122 +27,138 @@ func NewMinIOStorage(cfg *config.MinIOConfig) (*MinIOStorage, error) {
|
|||||||
return nil, fmt.Errorf("MinIO config is nil")
|
return nil, fmt.Errorf("MinIO config is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
storage := &MinIOStorage{
|
// 创建MinIO客户端
|
||||||
config: cfg,
|
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(cfg.AccessKeyID, cfg.SecretAccessKey, ""),
|
||||||
|
Secure: cfg.UseSSL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create MinIO client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化MinIO客户端
|
// 检查bucket是否存在,不存在则创建
|
||||||
// 注意:这里需要根据实际的MinIO SDK实现
|
ctx := context.Background()
|
||||||
// 例如使用MinIO Go SDK:
|
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
||||||
// client, err := minio.New(cfg.Endpoint, &minio.Options{
|
if err != nil {
|
||||||
// Creds: credentials.NewStaticV4(cfg.AccessKeyID, cfg.SecretAccessKey, ""),
|
// 如果检查失败,可能是网络问题,但不阻止客户端创建
|
||||||
// Secure: cfg.UseSSL,
|
// 继续创建客户端,后续上传时会再次检查
|
||||||
// })
|
} else if !exists {
|
||||||
// if err != nil {
|
err = client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{})
|
||||||
// return nil, fmt.Errorf("failed to create MinIO client: %w", err)
|
if err != nil {
|
||||||
// }
|
// 不阻止客户端创建,后续上传时会再次尝试
|
||||||
// storage.client = client
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return storage, nil
|
protocol := "http"
|
||||||
|
if cfg.UseSSL {
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MinIOStorage{
|
||||||
|
config: cfg,
|
||||||
|
client: client,
|
||||||
|
bucket: cfg.Bucket,
|
||||||
|
domain: cfg.Domain,
|
||||||
|
protocol: protocol,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload 上传文件到MinIO
|
// Upload 上传文件到MinIO
|
||||||
func (s *MinIOStorage) Upload(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) error {
|
func (s *MinIOStorage) Upload(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) error {
|
||||||
// 实现MinIO上传逻辑
|
if s.client == nil {
|
||||||
// 注意:这里需要根据实际的MinIO SDK实现
|
return fmt.Errorf("MinIO client is not initialized")
|
||||||
// 示例(使用MinIO Go SDK):
|
}
|
||||||
// ct := "application/octet-stream"
|
|
||||||
// if len(contentType) > 0 && contentType[0] != "" {
|
|
||||||
// ct = contentType[0]
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// _, err := s.client.PutObject(ctx, s.config.Bucket, objectKey, reader, -1, minio.PutObjectOptions{
|
|
||||||
// ContentType: ct,
|
|
||||||
// })
|
|
||||||
// if err != nil {
|
|
||||||
// return fmt.Errorf("failed to upload object: %w", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 当前实现返回错误,提示需要实现具体的MinIO SDK
|
ct := "application/octet-stream"
|
||||||
return fmt.Errorf("MinIO upload not implemented, please implement with actual MinIO SDK")
|
if len(contentType) > 0 && contentType[0] != "" {
|
||||||
|
ct = contentType[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := minio.PutObjectOptions{
|
||||||
|
ContentType: ct,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.client.PutObject(ctx, s.bucket, objectKey, reader, -1, opts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to upload object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetURL 获取MinIO文件访问URL
|
// GetURL 获取MinIO文件访问URL
|
||||||
func (s *MinIOStorage) GetURL(objectKey string, expires int64) (string, error) {
|
func (s *MinIOStorage) GetURL(objectKey string, expires int64) (string, error) {
|
||||||
if s.config.Domain != "" {
|
if s.client == nil {
|
||||||
// 使用自定义域名
|
return "", fmt.Errorf("MinIO client is not initialized")
|
||||||
if strings.HasSuffix(s.config.Domain, "/") {
|
}
|
||||||
return s.config.Domain + objectKey, nil
|
|
||||||
|
// 如果设置了过期时间,生成预签名URL
|
||||||
|
if expires > 0 {
|
||||||
|
ctx := context.Background()
|
||||||
|
expiry := time.Duration(expires) * time.Second
|
||||||
|
presignedURL, err := s.client.PresignedGetObject(ctx, s.bucket, objectKey, expiry, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate presigned URL: %w", err)
|
||||||
}
|
}
|
||||||
return s.config.Domain + "/" + objectKey, nil
|
return presignedURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用自定义域名或默认域名
|
||||||
|
if s.domain != "" {
|
||||||
|
// 使用自定义域名
|
||||||
|
if strings.HasSuffix(s.domain, "/") {
|
||||||
|
return fmt.Sprintf("%s://%s%s/%s", s.protocol, s.domain, s.bucket, objectKey), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s://%s/%s/%s", s.protocol, s.domain, s.bucket, objectKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用MinIO默认域名
|
// 使用MinIO默认域名
|
||||||
protocol := "http"
|
|
||||||
if s.config.UseSSL {
|
|
||||||
protocol = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建MinIO URL
|
|
||||||
// 格式: http://endpoint/bucket/objectKey
|
// 格式: http://endpoint/bucket/objectKey
|
||||||
url := fmt.Sprintf("%s://%s/%s/%s", protocol, s.config.Endpoint, s.config.Bucket, objectKey)
|
return fmt.Sprintf("%s://%s/%s/%s", s.protocol, s.config.Endpoint, s.bucket, objectKey), nil
|
||||||
|
|
||||||
// 如果设置了过期时间,需要生成预签名URL
|
|
||||||
// 注意:这里需要根据实际的MinIO SDK实现
|
|
||||||
// 示例(使用MinIO Go SDK):
|
|
||||||
// if expires > 0 {
|
|
||||||
// expiry := time.Duration(expires) * time.Second
|
|
||||||
// presignedURL, err := s.client.PresignedGetObject(ctx, s.config.Bucket, objectKey, expiry, nil)
|
|
||||||
// if err != nil {
|
|
||||||
// return "", err
|
|
||||||
// }
|
|
||||||
// return presignedURL.String(), nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
return url, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete 删除MinIO文件
|
// Delete 删除MinIO文件
|
||||||
func (s *MinIOStorage) Delete(ctx context.Context, objectKey string) error {
|
func (s *MinIOStorage) Delete(ctx context.Context, objectKey string) error {
|
||||||
// 实现MinIO删除逻辑
|
if s.client == nil {
|
||||||
// 注意:这里需要根据实际的MinIO SDK实现
|
return fmt.Errorf("MinIO client is not initialized")
|
||||||
// 示例(使用MinIO Go SDK):
|
}
|
||||||
// err := s.client.RemoveObject(ctx, s.config.Bucket, objectKey, minio.RemoveObjectOptions{})
|
|
||||||
// if err != nil {
|
|
||||||
// return fmt.Errorf("failed to delete object: %w", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
return fmt.Errorf("MinIO delete not implemented, please implement with actual MinIO SDK")
|
err := s.client.RemoveObject(ctx, s.bucket, objectKey, minio.RemoveObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exists 检查MinIO文件是否存在
|
// Exists 检查MinIO文件是否存在
|
||||||
func (s *MinIOStorage) Exists(ctx context.Context, objectKey string) (bool, error) {
|
func (s *MinIOStorage) Exists(ctx context.Context, objectKey string) (bool, error) {
|
||||||
// 实现MinIO存在性检查逻辑
|
if s.client == nil {
|
||||||
// 注意:这里需要根据实际的MinIO SDK实现
|
return false, fmt.Errorf("MinIO client is not initialized")
|
||||||
// 示例(使用MinIO Go SDK):
|
}
|
||||||
// _, err := s.client.StatObject(ctx, s.config.Bucket, objectKey, minio.StatObjectOptions{})
|
|
||||||
// if err != nil {
|
|
||||||
// if minio.ToErrorResponse(err).Code == "NoSuchKey" {
|
|
||||||
// return false, nil
|
|
||||||
// }
|
|
||||||
// return false, fmt.Errorf("failed to check object existence: %w", err)
|
|
||||||
// }
|
|
||||||
// return true, nil
|
|
||||||
|
|
||||||
return false, fmt.Errorf("MinIO exists check not implemented, please implement with actual MinIO SDK")
|
_, err := s.client.StatObject(ctx, s.bucket, objectKey, minio.StatObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
errResp := minio.ToErrorResponse(err)
|
||||||
|
if errResp.Code == "NoSuchKey" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("failed to check object existence: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObject 获取MinIO文件内容
|
// GetObject 获取MinIO文件内容
|
||||||
func (s *MinIOStorage) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) {
|
func (s *MinIOStorage) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) {
|
||||||
// 实现MinIO获取对象逻辑
|
if s.client == nil {
|
||||||
// 注意:这里需要根据实际的MinIO SDK实现
|
return nil, fmt.Errorf("MinIO client is not initialized")
|
||||||
// 示例(使用MinIO Go SDK):
|
}
|
||||||
// obj, err := s.client.GetObject(ctx, s.config.Bucket, objectKey, minio.GetObjectOptions{})
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, fmt.Errorf("failed to get object: %w", err)
|
|
||||||
// }
|
|
||||||
// return obj, nil
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("MinIO get object not implemented, please implement with actual MinIO SDK")
|
obj, err := s.client.GetObject(ctx, s.bucket, objectKey, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user