wip: oci
This commit is contained in:
358
example/redis-cache/main.go
Normal file
358
example/redis-cache/main.go
Normal file
@@ -0,0 +1,358 @@
|
||||
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)))
|
||||
}
|
||||
Reference in New Issue
Block a user