package main import ( "context" "fmt" "log" "net/http" "os" "strconv" "time" "gitea.loveuer.com/loveuer/upkg/database/cache" "github.com/gin-gonic/gin" ) type App struct { cache cache.Cache } func NewApp() (*App, error) { // 从环境变量获取配置 redisAddr := getEnv("REDIS_ADDR", "redis-headless.default.svc.cluster.local:6379") redisPassword := getEnv("REDIS_PASSWORD", "") reconnect := getEnv("REDIS_RECONNECT", "true") == "true" reconnectIntervalStr := getEnv("REDIS_RECONNECT_INTERVAL", "10s") reconnectInterval, err := time.ParseDuration(reconnectIntervalStr) if err != nil { log.Printf("Invalid reconnect interval %s, using default 10s", reconnectIntervalStr) reconnectInterval = 10 * time.Second } var cacheInstance cache.Cache if redisAddr != "" { // 检查是否是 Headless Service if contains(redisAddr, "svc.cluster.local") { log.Printf("Using headless service mode: %s", redisAddr) cacheInstance, err = cache.NewRedisFromHeadlessService(redisAddr, redisPassword) if err != nil { return nil, fmt.Errorf("failed to connect to headless redis: %w", err) } } else { // 使用普通连接 config := cache.NewConfig("redis", redisAddr) config.Password = redisPassword config.Reconnect = reconnect config.ReconnectInterval = reconnectInterval cacheInstance, err = cache.Open(config) if err != nil { return nil, fmt.Errorf("failed to connect to redis: %w", err) } } } else { // 使用内存缓存 log.Println("Using memory cache (no redis addr specified)") cacheInstance = cache.NewMemoryCache() } return &App{cache: cacheInstance}, nil } func (a *App) setupRoutes() *gin.Engine { r := gin.Default() // 健康检查 r.GET("/health", a.healthCheck) // 缓存操作路由 api := r.Group("/api") { api.GET("/cache/:key", a.getCache) api.POST("/cache/:key", a.setCache) api.DELETE("/cache/:key", a.deleteCache) // Hash 操作 api.GET("/hash/:key/:field", a.getHash) api.POST("/hash/:key/:field", a.setHash) api.GET("/hash/:key", a.getAllHash) // 计数器 api.POST("/counter/:key/inc", a.incrementCounter) api.POST("/counter/:key/inc/:value", a.incrementCounterBy) // 测试重连功能 api.POST("/test/reconnect", a.testReconnect) } return r } func (a *App) healthCheck(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "timestamp": time.Now().Unix(), "service": "redis-cache-demo", }) } func (a *App) getCache(c *gin.Context) { key := c.Param("key") ctx := context.Background() val, err := a.cache.Get(ctx, key) if err != nil { if err == cache.ErrKeyNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "key not found"}) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{ "key": key, "value": val, }) } func (a *App) setCache(c *gin.Context) { key := c.Param("key") var req struct { Value string `json:"value"` ExpiresIn int `json:"expires_in,omitempty"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx := context.Background() var err error if req.ExpiresIn > 0 { err = a.cache.Set(ctx, key, req.Value, time.Duration(req.ExpiresIn)*time.Second) } else { err = a.cache.Set(ctx, key, req.Value) } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "message": "ok", "key": key, "value": req.Value, }) } func (a *App) deleteCache(c *gin.Context) { key := c.Param("key") ctx := context.Background() err := a.cache.Del(ctx, key) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "deleted", "key": key}) } func (a *App) getHash(c *gin.Context) { key := c.Param("key") field := c.Param("field") ctx := context.Background() val, err := a.cache.HGet(ctx, key, field) if err != nil { if err == cache.ErrKeyNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "key or field not found"}) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{ "key": key, "field": field, "value": val, }) } func (a *App) setHash(c *gin.Context) { key := c.Param("key") field := c.Param("field") var req struct { Value string `json:"value"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx := context.Background() err := a.cache.HSet(ctx, key, field, req.Value) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "message": "ok", "key": key, "field": field, "value": req.Value, }) } func (a *App) getAllHash(c *gin.Context) { key := c.Param("key") ctx := context.Background() hash, err := a.cache.HGetAll(ctx, key) if err != nil { if err == cache.ErrKeyNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "key not found"}) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{ "key": key, "data": hash, }) } func (a *App) incrementCounter(c *gin.Context) { key := c.Param("key") ctx := context.Background() val, err := a.cache.Inc(ctx, key) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "key": key, "value": val, }) } func (a *App) incrementCounterBy(c *gin.Context) { key := c.Param("key") valueStr := c.Param("value") value, err := strconv.ParseInt(valueStr, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid value"}) return } ctx := context.Background() val, err := a.cache.IncBy(ctx, key, value) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "key": key, "value": val, }) } func (a *App) testReconnect(c *gin.Context) { ctx := context.Background() // 测试读写操作 testKey := fmt.Sprintf("test_reconnect_%d", time.Now().Unix()) testValue := fmt.Sprintf("value_%d", time.Now().Unix()) // 写入测试 err := a.cache.Set(ctx, testKey, testValue) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "write failed", "detail": err.Error(), }) return } // 读取测试 val, err := a.cache.Get(ctx, testKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "read failed", "detail": err.Error(), }) return } // 清理 a.cache.Del(ctx, testKey) c.JSON(http.StatusOK, gin.H{ "message": "reconnect test successful", "write": testValue, "read": val, "match": val == testValue, }) } func (a *App) Close() error { return a.cache.Close() } func main() { app, err := NewApp() if err != nil { log.Fatalf("Failed to create app: %v", err) } defer app.Close() r := app.setupRoutes() port := getEnv("PORT", "8080") log.Printf("Starting server on port %s", port) log.Printf("Health check: http://localhost:%s/health", port) log.Printf("API endpoints:") log.Printf(" GET /api/cache/:key - Get value") log.Printf(" POST /api/cache/:key - Set value") log.Printf(" GET /api/hash/:key/:field - Get hash field") log.Printf(" POST /api/hash/:key/:field - Set hash field") log.Printf(" POST /api/counter/:key/inc - Increment counter") log.Printf(" POST /api/test/reconnect - Test reconnection") if err := r.Run(":" + port); err != nil { log.Fatalf("Server failed: %v", err) } } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || s[len(s)-len(substr):] == substr || (len(s) > len(substr) && (s[:len(substr)+1] == substr+"." || s[len(s)-len(substr)-1:] == "."+substr))) }