将工厂改成黑盒模式,降低用户使用成本
This commit is contained in:
@@ -49,22 +49,29 @@ now := datetime.Now()
|
||||
str := datetime.FormatDateTime(now)
|
||||
```
|
||||
|
||||
#### HTTP响应
|
||||
#### HTTP响应(Handler黑盒模式)
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/http"
|
||||
import (
|
||||
"net/http"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
http.Success(w, data)
|
||||
http.SuccessPage(w, list, total, page, pageSize)
|
||||
http.Error(w, 1001, "业务错误")
|
||||
func GetUser(h *commonhttp.Handler) {
|
||||
id := h.GetQueryInt64("id", 0)
|
||||
h.Success(data)
|
||||
}
|
||||
|
||||
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||
```
|
||||
|
||||
#### 中间件
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
"git.toowon.com/jimmy/go-common/http"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
// CORS + 时区中间件
|
||||
@@ -72,10 +79,13 @@ chain := middleware.NewChain(
|
||||
middleware.CORS(),
|
||||
middleware.Timezone,
|
||||
)
|
||||
handler := chain.ThenFunc(yourHandler)
|
||||
|
||||
// 在处理器中获取时区
|
||||
timezone := http.GetTimezone(r)
|
||||
handler := chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
// 在Handler中获取时区
|
||||
timezone := h.GetTimezone()
|
||||
h.Success(data)
|
||||
})
|
||||
```
|
||||
|
||||
#### 配置管理
|
||||
|
||||
527
docs/factory.md
527
docs/factory.md
@@ -2,204 +2,249 @@
|
||||
|
||||
## 概述
|
||||
|
||||
工厂工具提供了从配置直接创建已初始化的客户端对象的功能,避免调用方重复实现创建逻辑。
|
||||
工厂工具提供了从配置直接创建已初始化客户端对象的功能,并提供了黑盒模式的便捷方法,让调用方无需关心底层实现细节,大大降低业务复杂度。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 从配置直接创建已初始化的客户端对象
|
||||
- 统一的工厂接口
|
||||
- 避免调用方重复实现创建逻辑
|
||||
- **黑盒模式**:提供直接调用的方法,无需获取客户端对象
|
||||
- **延迟初始化**:所有客户端在首次使用时才创建
|
||||
- **自动选择**:存储类型(OSS/MinIO)根据配置自动选择
|
||||
- **统一接口**:所有操作通过工厂方法调用
|
||||
- **向后兼容**:保留 `GetXXX()` 方法,需要时可获取对象
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 从配置文件直接创建工厂(推荐)
|
||||
### 1. 创建工厂(推荐)
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/factory"
|
||||
|
||||
// 直接传入配置文件路径,自动加载配置并创建工厂
|
||||
// 方式1:直接从配置文件创建(最推荐)
|
||||
fac, err := factory.NewFactoryFromFile("./config.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 直接获取已初始化的对象
|
||||
db, _ := fac.GetDatabase() // 直接获取数据库对象
|
||||
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)
|
||||
}
|
||||
|
||||
// 创建工厂实例
|
||||
// 方式2:从配置对象创建
|
||||
cfg, _ := config.LoadFromFile("./config.json")
|
||||
fac := factory.NewFactory(cfg)
|
||||
```
|
||||
|
||||
### 3. 获取数据库对象(已初始化,推荐)
|
||||
### 2. 日志记录(黑盒模式,推荐)
|
||||
|
||||
```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()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 直接使用,无需再创建连接
|
||||
// 直接使用GORM
|
||||
var users []User
|
||||
db.Find(&users)
|
||||
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
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/config"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载配置
|
||||
cfg, err := config.LoadFromFile("./config.json")
|
||||
// 创建工厂
|
||||
fac, err := factory.NewFactoryFromFile("./config.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 创建工厂
|
||||
fac := factory.NewFactory(cfg)
|
||||
ctx := context.Background()
|
||||
|
||||
// 获取邮件客户端(已初始化,可直接使用)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
fac.LogError("发送邮件失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取短信客户端(已初始化,可直接使用)
|
||||
smsClient, err := fac.GetSMSClient()
|
||||
// 短信发送(黑盒模式)
|
||||
resp, err := fac.SendSMS(
|
||||
[]string{"13800138000"},
|
||||
map[string]string{"code": "123456"},
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("SMS client not available: %v", err)
|
||||
fac.LogError("发送短信失败: %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 {
|
||||
log.Printf("SMS sent: %s", resp.RequestID)
|
||||
}
|
||||
fac.LogInfo("短信发送成功: %s", resp.RequestID)
|
||||
}
|
||||
|
||||
// 如果需要访问配置对象
|
||||
cfgObj := fac.GetConfig()
|
||||
dsn, _ := cfgObj.GetDatabaseDSN()
|
||||
log.Printf("Database DSN: %s", dsn)
|
||||
// 文件上传(黑盒模式,自动选择OSS或MinIO)
|
||||
file, _ := os.Open("test.jpg")
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 参考
|
||||
|
||||
### 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
|
||||
- 自动配置连接池参数
|
||||
- 数据库时间统一使用UTC时区
|
||||
- 延迟初始化,首次调用时创建连接
|
||||
|
||||
### (f *Factory) GetRedisClient() (*redis.Client, error)
|
||||
|
||||
获取Redis客户端对象(已初始化)。
|
||||
|
||||
**返回:** 已初始化的Redis客户端对象和错误信息
|
||||
|
||||
**说明:**
|
||||
- 自动处理所有配置检查和连接测试
|
||||
- 自动设置默认值(连接池大小、超时时间等)
|
||||
- 连接失败时会自动关闭客户端并返回错误
|
||||
- 返回的客户端已通过Ping测试,可直接使用
|
||||
|
||||
### (f *Factory) GetRedisConfig() *config.RedisConfig
|
||||
|
||||
获取Redis配置(用于创建Redis客户端)。
|
||||
|
||||
**返回:** Redis配置对象(可能为nil)
|
||||
|
||||
**说明:**
|
||||
- 推荐使用 `GetRedisClient()` 方法直接获取已初始化的客户端
|
||||
- 如果需要自定义创建Redis客户端,可以使用此方法获取配置
|
||||
|
||||
### (f *Factory) GetConfig() *config.Config
|
||||
#### GetConfig() *config.Config
|
||||
|
||||
获取配置对象。
|
||||
|
||||
**返回:** 配置对象
|
||||
|
||||
## 优势
|
||||
## 设计优势
|
||||
|
||||
### 之前的方式(需要调用方实现)
|
||||
### 优势总结
|
||||
|
||||
```go
|
||||
// 调用方需要自己实现创建逻辑
|
||||
cfg, _ := config.LoadFromFile("./config.json")
|
||||
dsn, _ := cfg.GetDatabaseDSN()
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
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. **降低复杂度**:调用方无需关心客户端对象的创建和管理
|
||||
2. **延迟初始化**:所有客户端在首次使用时才创建,提高性能
|
||||
3. **自动选择**:存储类型根据配置自动选择,无需手动指定
|
||||
4. **统一接口**:所有操作通过工厂方法调用,接口统一
|
||||
5. **容错处理**:日志初始化失败时自动回退到标准输出
|
||||
6. **代码简洁**:只提供黑盒模式方法,保持代码简洁清晰
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **配置检查**:工厂方法会自动检查配置是否存在,如果配置为nil会返回错误
|
||||
2. **错误处理**:所有Get方法都可能返回错误,需要正确处理
|
||||
3. **配置对象**:可以通过`GetConfig()`方法访问原始配置对象,获取其他配置信息
|
||||
2. **错误处理**:所有方法都可能返回错误,需要正确处理
|
||||
3. **延迟初始化**:所有客户端在首次使用时才创建,首次调用可能稍慢
|
||||
4. **存储选择**:存储类型根据配置自动选择(优先级:MinIO > OSS)
|
||||
5. **数据库对象**:数据库保持返回GORM对象,因为GORM已经提供了很好的抽象
|
||||
6. **黑盒模式**:所有功能都通过工厂方法直接调用,无需获取底层客户端对象
|
||||
|
||||
## 示例
|
||||
|
||||
完整示例请参考 `examples/factory_example.go`
|
||||
|
||||
|
||||
503
docs/http.md
503
docs/http.md
@@ -2,16 +2,16 @@
|
||||
|
||||
## 概述
|
||||
|
||||
HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,包含统一的响应结构、分页支持和HTTP状态码与业务状态码的分离。
|
||||
HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,采用Handler黑盒模式,封装了`ResponseWriter`和`Request`,提供简洁的API,无需每次都传递这两个参数。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 标准化的响应结构:`{code, message, timestamp, data}`
|
||||
- 分离HTTP状态码和业务状态码
|
||||
- 支持分页响应
|
||||
- 提供便捷的请求参数解析方法
|
||||
- 支持JSON请求体解析
|
||||
- 提供常用的HTTP错误响应方法
|
||||
- **黑盒模式**:封装`ResponseWriter`和`Request`,提供简洁的API
|
||||
- **标准化的响应结构**:`{code, message, timestamp, data}`
|
||||
- **分离HTTP状态码和业务状态码**
|
||||
- **支持分页响应**
|
||||
- **提供便捷的请求参数解析方法**
|
||||
- **支持JSON请求体解析**
|
||||
|
||||
## 响应结构
|
||||
|
||||
@@ -44,151 +44,224 @@ HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,包含
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 成功响应
|
||||
### 1. 创建Handler
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/http"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
// 简单成功响应(data为nil)
|
||||
http.Success(w, nil)
|
||||
|
||||
// 带数据的成功响应
|
||||
data := map[string]interface{}{
|
||||
"id": 1,
|
||||
"name": "test",
|
||||
// 方式1:使用HandleFunc包装器(推荐,最简洁)
|
||||
func GetUser(h *commonhttp.Handler) {
|
||||
id := h.GetQueryInt64("id", 0)
|
||||
h.Success(data)
|
||||
}
|
||||
http.Success(w, data)
|
||||
|
||||
// 带消息的成功响应
|
||||
http.SuccessWithMessage(w, "操作成功", data)
|
||||
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||
|
||||
// 方式2:手动创建Handler(需要更多控制时)
|
||||
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
GetUser(h)
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 错误响应
|
||||
### 2. 成功响应
|
||||
|
||||
```go
|
||||
// 业务错误(HTTP 200,业务code非0)
|
||||
http.Error(w, 1001, "用户不存在")
|
||||
|
||||
// 系统错误(HTTP 500)
|
||||
http.SystemError(w, "服务器内部错误")
|
||||
|
||||
// 请求错误(HTTP 400)
|
||||
http.BadRequest(w, "请求参数错误")
|
||||
|
||||
// 未授权(HTTP 401)
|
||||
http.Unauthorized(w, "未登录")
|
||||
|
||||
// 禁止访问(HTTP 403)
|
||||
http.Forbidden(w, "无权限访问")
|
||||
|
||||
// 未找到(HTTP 404)
|
||||
http.NotFound(w, "资源不存在")
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 简单成功响应(data为nil)
|
||||
h.Success(nil)
|
||||
|
||||
// 带数据的成功响应
|
||||
data := map[string]interface{}{
|
||||
"id": 1,
|
||||
"name": "test",
|
||||
}
|
||||
h.Success(data)
|
||||
|
||||
// 带消息的成功响应
|
||||
h.SuccessWithMessage("操作成功", data)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 分页响应
|
||||
### 3. 错误响应
|
||||
|
||||
```go
|
||||
// 获取分页参数
|
||||
page, pageSize := http.GetPaginationParams(r)
|
||||
|
||||
// 查询数据(示例)
|
||||
list, total := getDataList(page, pageSize)
|
||||
|
||||
// 返回分页响应
|
||||
http.SuccessPage(w, list, total, page, pageSize)
|
||||
|
||||
// 带消息的分页响应
|
||||
http.SuccessPageWithMessage(w, "查询成功", list, total, page, pageSize)
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 业务错误(HTTP 200,业务code非0)
|
||||
h.Error(1001, "用户不存在")
|
||||
|
||||
// 系统错误(HTTP 500)
|
||||
h.SystemError("服务器内部错误")
|
||||
|
||||
// 其他HTTP错误状态码,使用WriteJSON直接指定
|
||||
// 请求错误(HTTP 400)
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数错误", nil)
|
||||
|
||||
// 未授权(HTTP 401)
|
||||
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请求体
|
||||
|
||||
```go
|
||||
type CreateUserRequest struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
var req CreateUserRequest
|
||||
err := http.ParseJSON(r, &req)
|
||||
if err != nil {
|
||||
http.BadRequest(w, "请求参数解析失败")
|
||||
return
|
||||
func handler(h *commonhttp.Handler) {
|
||||
type CreateUserRequest struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
var req CreateUserRequest
|
||||
if err := h.ParseJSON(&req); err != nil {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用req...
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取查询参数
|
||||
|
||||
```go
|
||||
// 获取字符串参数
|
||||
name := http.GetQuery(r, "name", "")
|
||||
email := http.GetQuery(r, "email", "default@example.com")
|
||||
|
||||
// 获取整数参数
|
||||
id := http.GetQueryInt(r, "id", 0)
|
||||
age := http.GetQueryInt(r, "age", 18)
|
||||
|
||||
// 获取int64参数
|
||||
userId := http.GetQueryInt64(r, "userId", 0)
|
||||
|
||||
// 获取布尔参数
|
||||
isActive := http.GetQueryBool(r, "isActive", false)
|
||||
|
||||
// 获取浮点数参数
|
||||
price := http.GetQueryFloat64(r, "price", 0.0)
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 获取字符串参数
|
||||
name := h.GetQuery("name", "")
|
||||
email := h.GetQuery("email", "default@example.com")
|
||||
|
||||
// 获取整数参数
|
||||
id := h.GetQueryInt("id", 0)
|
||||
age := h.GetQueryInt("age", 18)
|
||||
|
||||
// 获取int64参数
|
||||
userId := h.GetQueryInt64("userId", 0)
|
||||
|
||||
// 获取布尔参数
|
||||
isActive := h.GetQueryBool("isActive", false)
|
||||
|
||||
// 获取浮点数参数
|
||||
price := h.GetQueryFloat64("price", 0.0)
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取表单参数
|
||||
|
||||
```go
|
||||
// 获取表单字符串
|
||||
name := http.GetFormValue(r, "name", "")
|
||||
|
||||
// 获取表单整数
|
||||
age := http.GetFormInt(r, "age", 0)
|
||||
|
||||
// 获取表单int64
|
||||
userId := http.GetFormInt64(r, "userId", 0)
|
||||
|
||||
// 获取表单布尔值
|
||||
isActive := http.GetFormBool(r, "isActive", false)
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 获取表单字符串
|
||||
name := h.GetFormValue("name", "")
|
||||
|
||||
// 获取表单整数
|
||||
age := h.GetFormInt("age", 0)
|
||||
|
||||
// 获取表单int64
|
||||
userId := h.GetFormInt64("userId", 0)
|
||||
|
||||
// 获取表单布尔值
|
||||
isActive := h.GetFormBool("isActive", false)
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取请求头
|
||||
|
||||
```go
|
||||
token := http.GetHeader(r, "Authorization", "")
|
||||
contentType := http.GetHeader(r, "Content-Type", "application/json")
|
||||
func handler(h *commonhttp.Handler) {
|
||||
token := h.GetHeader("Authorization", "")
|
||||
contentType := h.GetHeader("Content-Type", "application/json")
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取分页参数
|
||||
|
||||
```go
|
||||
// 自动解析page和pageSize参数
|
||||
// 默认: page=1, pageSize=10
|
||||
// 限制: pageSize最大1000
|
||||
page, pageSize := http.GetPaginationParams(r)
|
||||
**方式1:使用 PaginationRequest 结构(推荐)**
|
||||
|
||||
// 计算数据库查询偏移量
|
||||
offset := http.GetOffset(page, pageSize)
|
||||
```go
|
||||
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
|
||||
// 使用WriteJSON自定义响应
|
||||
http.WriteJSON(w, http.StatusOK, 0, "success", data)
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 从请求的context中获取时区
|
||||
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||
// 如果未设置,返回默认时区 AsiaShanghai
|
||||
timezone := h.GetTimezone()
|
||||
}
|
||||
```
|
||||
|
||||
// 参数说明:
|
||||
// - httpCode: HTTP状态码(200, 400, 500等)
|
||||
// - code: 业务状态码(0表示成功,非0表示业务错误)
|
||||
// - message: 响应消息
|
||||
// - data: 响应数据
|
||||
### 6. 访问原始对象
|
||||
|
||||
如果需要访问原始的`ResponseWriter`或`Request`:
|
||||
|
||||
```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
|
||||
|
||||
import (
|
||||
"log"
|
||||
"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)
|
||||
|
||||
// 返回分页响应
|
||||
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 {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
if err := http.ParseJSON(r, &req); err != nil {
|
||||
http.BadRequest(w, "请求参数解析失败")
|
||||
if err := h.ParseJSON(&req); err != nil {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if req.Name == "" {
|
||||
http.Error(w, 1001, "用户名不能为空")
|
||||
h.Error(1001, "用户名不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user, err := createUser(req.Name, req.Email)
|
||||
if err != nil {
|
||||
http.SystemError(w, "创建用户失败")
|
||||
h.SystemError("创建用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
http.SuccessWithMessage(w, "创建成功", user)
|
||||
h.SuccessWithMessage("创建成功", user)
|
||||
}
|
||||
|
||||
// 获取用户详情接口
|
||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取路径参数(需要配合路由框架使用)
|
||||
id := http.GetQueryInt64(r, "id", 0)
|
||||
func GetUser(h *commonhttp.Handler) {
|
||||
// 获取查询参数
|
||||
id := h.GetQueryInt64("id", 0)
|
||||
|
||||
if id == 0 {
|
||||
http.BadRequest(w, "用户ID不能为空")
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "用户ID不能为空", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
user, err := getUserByID(id)
|
||||
if err != nil {
|
||||
http.SystemError(w, "查询用户失败")
|
||||
h.SystemError("查询用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
http.Error(w, 1002, "用户不存在")
|
||||
h.Error(1002, "用户不存在")
|
||||
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 参考
|
||||
|
||||
### 响应方法
|
||||
### 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。
|
||||
|
||||
#### 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。
|
||||
|
||||
#### SystemError(w http.ResponseWriter, message string)
|
||||
#### (h *Handler) SystemError(message string)
|
||||
|
||||
系统错误响应,HTTP 500,业务code 500。
|
||||
|
||||
#### BadRequest(w http.ResponseWriter, message string)
|
||||
|
||||
请求错误响应,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{})
|
||||
#### (h *Handler) WriteJSON(httpCode, code int, message string, data interface{})
|
||||
|
||||
写入JSON响应(自定义)。
|
||||
|
||||
@@ -318,69 +432,113 @@ func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
- `message`: 响应消息
|
||||
- `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请求解析方法
|
||||
|
||||
### 请求方法
|
||||
|
||||
#### ParseJSON(r *http.Request, v interface{}) error
|
||||
#### (h *Handler) ParseJSON(v interface{}) error
|
||||
|
||||
解析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)。
|
||||
|
||||
#### 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)。
|
||||
|
||||
#### 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. **分页参数限制**:
|
||||
- page最小值为1
|
||||
- pageSize最小值为1,最大值为1000
|
||||
- pageSize最小值为1,最大值为100
|
||||
|
||||
3. **响应格式统一**:
|
||||
- 所有响应都遵循标准结构
|
||||
- timestamp为Unix时间戳(秒)
|
||||
|
||||
4. **错误处理**:
|
||||
- 使用Error方法返回业务错误
|
||||
- 使用SystemError返回系统错误
|
||||
- 使用BadRequest等返回HTTP级别的错误
|
||||
- 使用`Error`方法返回业务错误(HTTP 200,业务code非0)
|
||||
- 使用`SystemError`返回系统错误(HTTP 500)
|
||||
- 其他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` 读取时区
|
||||
- 如果未传递时区信息,使用默认时区 `AsiaShanghai`
|
||||
- 时区信息存储到context中,可通过 `http.GetTimezone()` 获取
|
||||
- 时区信息存储到context中,可通过Handler的`GetTimezone()`方法获取
|
||||
- 自动验证时区有效性,无效时区会回退到默认时区
|
||||
|
||||
### 使用方法
|
||||
@@ -132,19 +132,21 @@ corsHandler := middleware.CORS(corsConfig)(handler)
|
||||
import (
|
||||
"net/http"
|
||||
"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"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
// 从context获取时区
|
||||
timezone := http.GetTimezone(r)
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
|
||||
// 从Handler获取时区
|
||||
timezone := h.GetTimezone()
|
||||
|
||||
// 使用时区
|
||||
now := datetime.Now(timezone)
|
||||
datetime.FormatDateTime(now, timezone)
|
||||
|
||||
http.Success(w, map[string]interface{}{
|
||||
h.Success(map[string]interface{}{
|
||||
"timezone": timezone,
|
||||
"time": datetime.FormatDateTime(now),
|
||||
})
|
||||
@@ -183,13 +185,15 @@ func main() {
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/http"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
)
|
||||
|
||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
// 从请求context获取时区
|
||||
timezone := http.GetTimezone(r)
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
|
||||
// 从Handler获取时区
|
||||
timezone := h.GetTimezone()
|
||||
|
||||
// 使用时区进行时间处理
|
||||
now := datetime.Now(timezone)
|
||||
@@ -199,7 +203,7 @@ func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
endTime := datetime.EndOfDay(now, timezone)
|
||||
|
||||
// 返回数据
|
||||
http.Success(w, map[string]interface{}{
|
||||
h.Success(map[string]interface{}{
|
||||
"timezone": timezone,
|
||||
"startTime": datetime.FormatDateTime(startTime),
|
||||
"endTime": datetime.FormatDateTime(endTime),
|
||||
@@ -283,16 +287,18 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取时区
|
||||
timezone := http.GetTimezone(r)
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
|
||||
// 从Handler获取时区
|
||||
timezone := h.GetTimezone()
|
||||
now := datetime.Now(timezone)
|
||||
|
||||
http.Success(w, map[string]interface{}{
|
||||
h.Success(map[string]interface{}{
|
||||
"message": "Hello",
|
||||
"timezone": timezone,
|
||||
"time": datetime.FormatDateTime(now),
|
||||
@@ -332,7 +338,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
"git.toowon.com/jimmy/go-common/http"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -352,15 +358,17 @@ func main() {
|
||||
}
|
||||
|
||||
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) {
|
||||
timezone := http.GetTimezone(r)
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
timezone := h.GetTimezone()
|
||||
// 处理逻辑
|
||||
http.Success(w, nil)
|
||||
h.Success(nil)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user