调整工厂模式的方法
This commit is contained in:
172
examples/factory_blackbox_example.go
Normal file
172
examples/factory_blackbox_example.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
// 示例:Factory黑盒模式 - 最简化的使用方式
|
||||
//
|
||||
// 核心理念:
|
||||
//
|
||||
// 外部项目只需要传递一个配置文件路径,
|
||||
// 直接使用 factory 的黑盒方法,无需获取内部对象
|
||||
func main() {
|
||||
// ====== 第1步:创建工厂(只需要配置文件路径)======
|
||||
fac, err := factory.NewFactoryFromFile("config.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// ====== 第2步:使用黑盒方法(推荐)======
|
||||
|
||||
// 1. 获取中间件链(自动配置所有基础中间件)
|
||||
chain := fac.GetMiddlewareChain()
|
||||
|
||||
// 2. 添加项目特定的自定义中间件
|
||||
chain.Append(authMiddleware, metricsMiddleware)
|
||||
|
||||
// 3. 注册路由
|
||||
http.Handle("/api/users", chain.ThenFunc(handleUsers))
|
||||
http.Handle("/api/upload", chain.ThenFunc(handleUpload))
|
||||
|
||||
// 4. 启动服务
|
||||
log.Println("Server started on :8080")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
// ====== API处理器 ======
|
||||
|
||||
// 用户列表
|
||||
func handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
|
||||
// 创建工厂(在处理器中也可以复用)
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
ctx := context.Background()
|
||||
|
||||
// 1. 使用数据库(需要获取对象,因为GORM很复杂)
|
||||
db, _ := fac.GetDatabase()
|
||||
var users []map[string]interface{}
|
||||
db.Table("users").Find(&users)
|
||||
|
||||
// 2. 使用Redis(黑盒方法,推荐)
|
||||
cacheKey := "users:list"
|
||||
cached, _ := fac.RedisGet(ctx, cacheKey)
|
||||
if cached != "" {
|
||||
h.Success(cached)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 记录日志(黑盒方法,推荐)
|
||||
fac.LogInfof(map[string]interface{}{
|
||||
"action": "list_users",
|
||||
"count": len(users),
|
||||
}, "查询用户列表")
|
||||
|
||||
// 4. 缓存结果
|
||||
fac.RedisSet(ctx, cacheKey, users, 5*time.Minute)
|
||||
|
||||
h.Success(users)
|
||||
}
|
||||
|
||||
// 文件上传
|
||||
func handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
ctx := context.Background()
|
||||
|
||||
// 解析上传的文件
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
fac.LogError("文件上传失败: %v", err)
|
||||
h.Error(400, "文件上传失败")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 上传文件(黑盒方法,自动选择OSS或MinIO)
|
||||
objectKey := "uploads/" + header.Filename
|
||||
url, err := fac.UploadFile(ctx, objectKey, file, header.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
fac.LogError("文件上传到存储失败: %v", err)
|
||||
h.Error(500, "文件上传失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 记录上传日志
|
||||
fac.LogInfof(map[string]interface{}{
|
||||
"filename": header.Filename,
|
||||
"size": header.Size,
|
||||
"url": url,
|
||||
}, "文件上传成功")
|
||||
|
||||
h.Success(map[string]interface{}{
|
||||
"url": url,
|
||||
})
|
||||
}
|
||||
|
||||
// ====== 自定义中间件 ======
|
||||
|
||||
// 认证中间件
|
||||
func authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
ctx := context.Background()
|
||||
|
||||
// 获取token
|
||||
token := r.Header.Get("Authorization")
|
||||
if token == "" {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
h.Error(401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
// 从Redis验证token(黑盒方法)
|
||||
userID, err := fac.RedisGet(ctx, "token:"+token)
|
||||
if err != nil || userID == "" {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
h.Error(401, "token无效")
|
||||
return
|
||||
}
|
||||
|
||||
// 记录日志(黑盒方法)
|
||||
fac.LogInfof(map[string]interface{}{
|
||||
"user_id": userID,
|
||||
"path": r.URL.Path,
|
||||
}, "用户请求")
|
||||
|
||||
// 将用户ID存入context(或header)
|
||||
r.Header.Set("X-User-ID", userID)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// 指标中间件
|
||||
func metricsMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
ctx := context.Background()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
// 继续处理请求
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
// 记录请求耗时到Redis(黑盒方法)
|
||||
latency := time.Since(start).Milliseconds()
|
||||
key := "metrics:" + r.URL.Path
|
||||
fac.RedisSet(ctx, key, latency, time.Minute)
|
||||
|
||||
// 记录指标日志(黑盒方法)
|
||||
fac.LogDebugf(map[string]interface{}{
|
||||
"path": r.URL.Path,
|
||||
"latency": latency,
|
||||
}, "请求指标")
|
||||
})
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 方式1:直接从配置文件创建工厂(推荐)
|
||||
fac, err := factory.NewFactoryFromFile("./config/example.json")
|
||||
if err != nil {
|
||||
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()
|
||||
if err != nil {
|
||||
fac.LogError("数据库连接失败: %v", err)
|
||||
} else {
|
||||
// 直接使用GORM,无需自己实现创建逻辑
|
||||
var count int64
|
||||
if err := db.Table("users").Count(&count).Error; err != nil {
|
||||
fac.LogError("查询用户数量失败: %v", err)
|
||||
} else {
|
||||
fac.LogInfo("用户数量: %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Redis操作(获取客户端对象,黑盒模式) ==========
|
||||
redisClient, err := fac.GetRedisClient()
|
||||
if err != nil {
|
||||
fac.LogError("Redis客户端不可用: %v", err)
|
||||
} else {
|
||||
// 直接使用Redis客户端,无需自己实现创建逻辑
|
||||
val, err := redisClient.Get(ctx, "test_key").Result()
|
||||
if err != nil && err != redis.Nil {
|
||||
fac.LogError("Redis错误: %v", err)
|
||||
} else if err == redis.Nil {
|
||||
fac.LogInfo("Redis键不存在")
|
||||
} else {
|
||||
fac.LogInfo("Redis值: %s", val)
|
||||
}
|
||||
|
||||
// 使用高级功能(如Hash操作)
|
||||
redisClient.HSet(ctx, "user:123", "name", "John")
|
||||
name, _ := redisClient.HGet(ctx, "user:123", "name").Result()
|
||||
fac.LogInfo("Redis Hash值: %s", name)
|
||||
}
|
||||
|
||||
|
||||
fac.LogInfo("示例执行完成")
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/config"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/logger"
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
)
|
||||
|
||||
// 示例:完整的中间件配置
|
||||
// 包括:Recovery、Logging、RateLimit、CORS、Timezone
|
||||
func main() {
|
||||
// 1. 配置logger(异步模式,输出到文件和stdout)
|
||||
loggerConfig := &logger.LoggerConfig{
|
||||
Level: "info",
|
||||
Output: "both", // 同时输出到stdout和文件
|
||||
FilePath: "./logs/app.log",
|
||||
Async: true, // 异步模式
|
||||
BufferSize: 1000, // 缓冲区大小
|
||||
Prefix: "[API]", // 日志前缀
|
||||
}
|
||||
myLogger, err := logger.NewLogger(loggerConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create logger:", err)
|
||||
}
|
||||
defer myLogger.Close() // 确保程序退出时关闭logger
|
||||
|
||||
// 2. 配置CORS
|
||||
corsConfig := &middleware.CORSConfig{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Content-Type", "Authorization", "X-Timezone"},
|
||||
ExposedHeaders: []string{"X-Total-Count"},
|
||||
AllowCredentials: false,
|
||||
MaxAge: 3600, // 1小时
|
||||
}
|
||||
|
||||
// 3. 配置日志中间件
|
||||
loggingConfig := &middleware.LoggingConfig{
|
||||
Logger: myLogger,
|
||||
SkipPaths: []string{"/health", "/metrics"}, // 跳过健康检查和监控接口
|
||||
}
|
||||
|
||||
// 4. 配置Recovery中间件
|
||||
recoveryConfig := &middleware.RecoveryConfig{
|
||||
Logger: myLogger,
|
||||
EnableStackTrace: true, // 启用堆栈跟踪
|
||||
CustomHandler: func(w http.ResponseWriter, r *http.Request, err interface{}) {
|
||||
// 使用统一的JSON响应格式
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
h.SystemError("服务器内部错误,请稍后重试")
|
||||
},
|
||||
}
|
||||
|
||||
// 5. 配置限流中间件(100请求/分钟)
|
||||
rateLimiter := middleware.NewTokenBucketLimiter(100, time.Minute)
|
||||
rateLimitConfig := &middleware.RateLimitConfig{
|
||||
Limiter: rateLimiter,
|
||||
OnRateLimitExceeded: func(w http.ResponseWriter, r *http.Request, key string) {
|
||||
// 记录限流事件
|
||||
myLogger.Warnf(map[string]interface{}{
|
||||
"key": key,
|
||||
"path": r.URL.Path,
|
||||
"ip": r.RemoteAddr,
|
||||
}, "Rate limit exceeded")
|
||||
},
|
||||
}
|
||||
|
||||
// 6. 创建中间件链(顺序很重要!)
|
||||
// 顺序:Recovery -> Logging -> RateLimit -> CORS -> Timezone
|
||||
chain := middleware.NewChain(
|
||||
middleware.Recovery(recoveryConfig), // 1. 最外层:捕获panic
|
||||
middleware.Logging(loggingConfig), // 2. 日志记录
|
||||
middleware.RateLimit(rateLimitConfig), // 3. 限流保护
|
||||
middleware.CORS(corsConfig), // 4. CORS处理
|
||||
middleware.Timezone, // 5. 时区处理
|
||||
)
|
||||
|
||||
// 7. 定义路由
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// API路由(应用所有中间件)
|
||||
mux.Handle("/api/hello", chain.ThenFunc(handleHello))
|
||||
mux.Handle("/api/panic", chain.ThenFunc(handlePanic)) // 测试panic恢复
|
||||
mux.Handle("/api/users", chain.ThenFunc(handleUsers))
|
||||
|
||||
// 健康检查(不应用中间件链,直接处理)
|
||||
mux.HandleFunc("/health", handleHealth)
|
||||
mux.HandleFunc("/metrics", handleMetrics)
|
||||
|
||||
// 8. 启动服务器
|
||||
addr := ":8080"
|
||||
log.Printf("Server starting on %s", addr)
|
||||
log.Printf("Try: http://localhost%s/api/hello", addr)
|
||||
log.Printf("Health: http://localhost%s/health", addr)
|
||||
|
||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||
log.Fatal("Server failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleHello 示例处理器:返回问候信息
|
||||
func handleHello(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
|
||||
// 从Handler获取时区
|
||||
timezone := h.GetTimezone()
|
||||
|
||||
h.Success(map[string]interface{}{
|
||||
"message": "Hello, World!",
|
||||
"timezone": timezone,
|
||||
"method": r.Method,
|
||||
"path": r.URL.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// handlePanic 示例处理器:测试panic恢复
|
||||
func handlePanic(w http.ResponseWriter, r *http.Request) {
|
||||
// 故意触发panic,测试Recovery中间件
|
||||
panic("This is a test panic!")
|
||||
}
|
||||
|
||||
// handleUsers 示例处理器:返回用户列表
|
||||
func handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
|
||||
// 模拟用户数据
|
||||
users := []map[string]interface{}{
|
||||
{"id": 1, "name": "Alice"},
|
||||
{"id": 2, "name": "Bob"},
|
||||
{"id": 3, "name": "Charlie"},
|
||||
}
|
||||
|
||||
h.Success(users)
|
||||
}
|
||||
|
||||
// handleHealth 健康检查处理器(不应用中间件)
|
||||
func handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
// handleMetrics 监控指标处理器(不应用中间件)
|
||||
func handleMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("metrics: ok"))
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
)
|
||||
|
||||
// 示例:限流中间件的使用
|
||||
// 展示不同的限流策略
|
||||
func main() {
|
||||
// 策略1:按IP限流(10请求/分钟)
|
||||
ipLimitChain := middleware.NewChain(
|
||||
middleware.RateLimitByIP(10, time.Minute),
|
||||
)
|
||||
|
||||
// 策略2:按用户ID限流(100请求/分钟)
|
||||
limiter := middleware.NewTokenBucketLimiter(100, time.Minute)
|
||||
userLimitConfig := &middleware.RateLimitConfig{
|
||||
Limiter: limiter,
|
||||
KeyFunc: func(r *http.Request) string {
|
||||
// 从请求头获取用户ID
|
||||
userID := r.Header.Get("X-User-ID")
|
||||
if userID != "" {
|
||||
return "user:" + userID
|
||||
}
|
||||
// 没有用户ID则使用IP
|
||||
return "ip:" + r.RemoteAddr
|
||||
},
|
||||
OnRateLimitExceeded: func(w http.ResponseWriter, r *http.Request, key string) {
|
||||
log.Printf("Rate limit exceeded for key: %s, path: %s", key, r.URL.Path)
|
||||
},
|
||||
}
|
||||
userLimitChain := middleware.NewChain(
|
||||
middleware.RateLimit(userLimitConfig),
|
||||
)
|
||||
|
||||
// 路由1:按IP限流的API(严格限制)
|
||||
http.Handle("/api/public", ipLimitChain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
h.Success(map[string]interface{}{
|
||||
"message": "Public API - IP rate limited (10/min)",
|
||||
"tip": "Try refreshing quickly to see rate limiting",
|
||||
})
|
||||
}))
|
||||
|
||||
// 路由2:按用户ID限流的API(宽松限制)
|
||||
http.Handle("/api/private", userLimitChain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
userID := r.Header.Get("X-User-ID")
|
||||
if userID == "" {
|
||||
h.Error(401, "Missing X-User-ID header")
|
||||
return
|
||||
}
|
||||
h.Success(map[string]interface{}{
|
||||
"message": "Private API - User rate limited (100/min)",
|
||||
"user_id": userID,
|
||||
})
|
||||
}))
|
||||
|
||||
// 路由3:无限流的API(用于测试对比)
|
||||
http.HandleFunc("/api/unlimited", func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
h.Success(map[string]interface{}{
|
||||
"message": "Unlimited API - No rate limiting",
|
||||
})
|
||||
})
|
||||
|
||||
// 启动服务器
|
||||
addr := ":8080"
|
||||
log.Printf("Server starting on %s", addr)
|
||||
log.Println("Test endpoints:")
|
||||
log.Printf(" - IP limited (10/min): http://localhost%s/api/public", addr)
|
||||
log.Printf(" - User limited (100/min): http://localhost%s/api/private (add X-User-ID header)", addr)
|
||||
log.Printf(" - No limit: http://localhost%s/api/unlimited", addr)
|
||||
log.Println("\nTest with curl:")
|
||||
log.Printf(" curl http://localhost%s/api/public", addr)
|
||||
log.Printf(" curl -H 'X-User-ID: user123' http://localhost%s/api/private", addr)
|
||||
log.Println("\nResponse headers:")
|
||||
log.Println(" X-RateLimit-Limit: Total allowed requests")
|
||||
log.Println(" X-RateLimit-Remaining: Remaining requests")
|
||||
log.Println(" X-RateLimit-Reset: Reset timestamp")
|
||||
|
||||
log.Fatal(http.ListenAndServe(addr, nil))
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"git.toowon.com/jimmy/go-common/migration"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 初始化数据库连接
|
||||
dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 创建迁移器
|
||||
migrator := migration.NewMigrator(db)
|
||||
|
||||
// 添加迁移
|
||||
migrator.AddMigration(migration.Migration{
|
||||
Version: "20240101000001",
|
||||
Description: "create_users_table",
|
||||
Up: func(db *gorm.DB) error {
|
||||
return db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`).Error
|
||||
},
|
||||
Down: func(db *gorm.DB) error {
|
||||
return db.Exec("DROP TABLE IF EXISTS users").Error
|
||||
},
|
||||
})
|
||||
|
||||
// 执行迁移
|
||||
if err := migrator.Up(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 查看迁移状态
|
||||
status, err := migrator.Status()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, s := range status {
|
||||
fmt.Printf("Version: %s, Description: %s, Applied: %v\n",
|
||||
s.Version, s.Description, s.Applied)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/migration"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 初始化数据库连接(使用SQLite作为示例)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to connect to database:", err)
|
||||
}
|
||||
|
||||
// 创建迁移器
|
||||
migrator := migration.NewMigrator(db)
|
||||
|
||||
// 添加一些迁移
|
||||
migrator.AddMigrations(
|
||||
migration.Migration{
|
||||
Version: "20240101000001",
|
||||
Description: "create_users_table",
|
||||
Up: func(db *gorm.DB) error {
|
||||
return db.Exec(`
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL
|
||||
)
|
||||
`).Error
|
||||
},
|
||||
Down: func(db *gorm.DB) error {
|
||||
return db.Exec("DROP TABLE IF EXISTS users").Error
|
||||
},
|
||||
},
|
||||
migration.Migration{
|
||||
Version: "20240101000002",
|
||||
Description: "add_created_at_to_users",
|
||||
Up: func(db *gorm.DB) error {
|
||||
return db.Exec("ALTER TABLE users ADD COLUMN created_at DATETIME").Error
|
||||
},
|
||||
Down: func(db *gorm.DB) error {
|
||||
return db.Exec("ALTER TABLE users DROP COLUMN created_at").Error
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// 执行迁移
|
||||
fmt.Println("=== Executing migrations ===")
|
||||
err = migrator.Up()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to run migrations:", err)
|
||||
}
|
||||
|
||||
// 查看状态
|
||||
fmt.Println("\n=== Migration status ===")
|
||||
status, err := migrator.Status()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to get status:", err)
|
||||
}
|
||||
for _, s := range status {
|
||||
fmt.Printf("Version: %s, Description: %s, Applied: %v\n",
|
||||
s.Version, s.Description, s.Applied)
|
||||
}
|
||||
|
||||
// 示例1:仅清空迁移记录(不回滚数据库变更)
|
||||
fmt.Println("\n=== Example 1: Reset migration records only ===")
|
||||
fmt.Println("Note: This only clears records, not database changes")
|
||||
// 直接调用(需要确认标志)
|
||||
// err = migrator.Reset(true)
|
||||
// if err != nil {
|
||||
// log.Fatal("Failed to reset:", err)
|
||||
// }
|
||||
|
||||
// 交互式确认(推荐)
|
||||
// 取消注释下面的代码来测试交互式重置
|
||||
// err = migrator.ResetWithConfirm()
|
||||
// if err != nil {
|
||||
// log.Fatal("Failed to reset with confirm:", err)
|
||||
// }
|
||||
|
||||
// 示例2:回滚所有迁移并清空记录
|
||||
fmt.Println("\n=== Example 2: Reset all migrations (rollback + clear records) ===")
|
||||
fmt.Println("Note: This will rollback all migrations and clear records")
|
||||
// 直接调用(需要确认标志)
|
||||
// err = migrator.ResetAll(true)
|
||||
// if err != nil {
|
||||
// log.Fatal("Failed to reset all:", err)
|
||||
// }
|
||||
|
||||
// 交互式确认(推荐)
|
||||
// 取消注释下面的代码来测试交互式重置
|
||||
// err = migrator.ResetAllWithConfirm()
|
||||
// if err != nil {
|
||||
// log.Fatal("Failed to reset all with confirm:", err)
|
||||
// }
|
||||
|
||||
fmt.Println("\nNote: Reset functions are commented out for safety.")
|
||||
fmt.Println("Uncomment the code above to test reset functionality.")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user