From 0c9e483348f8db53dec33a9d4fe839a109431ba2 Mon Sep 17 00:00:00 2001 From: hoangphuc552001 Date: Sun, 28 May 2023 13:40:36 +0700 Subject: [PATCH 1/2] test --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9fe5c51..8f33415 100644 --- a/README.md +++ b/README.md @@ -205,3 +205,5 @@ Don't confuse the project level `/src` directory with the `/src` directory Go us ## Notes A more opinionated project template with sample/reusable configs, scripts and code is a WIP. + +a \ No newline at end of file From 9dc4c627819fdeef70d655ba551d0d419a8331ad Mon Sep 17 00:00:00 2001 From: hoangphuc552001 Date: Tue, 13 Jun 2023 00:10:33 +0700 Subject: [PATCH 2/2] redis hw --- go.mod | 1 + main.go | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 main.go diff --git a/go.mod b/go.mod index bc7f763..f173ee1 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/main.go b/main.go new file mode 100644 index 0000000..8e02d6d --- /dev/null +++ b/main.go @@ -0,0 +1,216 @@ +package main + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + "github.com/google/uuid" + "log" + "net/http" + "time" +) + +var redisClient *redis.Client + +func init() { + redisClient = redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + }) + +} + +func main() { + _, err := redisClient.Ping(redisClient.Context()).Result() + if err != nil { + log.Fatal(err) + } + + router := gin.Default() + + router.POST("/login", loginHandler) + router.POST("/ping", pingHandler) + router.POST("/ping-count", pingCountHandler) + router.POST("/rate-limit", rateLimitHandler) + router.POST("/top", topHandler) + router.POST("/count", countHandler) + router.POST("/hyperloglog", hyperloglogHandler) + router.Run(":8080") + +} + +func hyperloglogHandler(c *gin.Context) { + count, err := redisClient.PFCount(redisClient.Context(), "hyperloglog").Result() + if err != nil { + log.Println(err) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"count": count}) +} + +func topHandler(c *gin.Context) { + top, err := redisClient.ZRevRangeWithScores(redisClient.Context(), "top", 0, 9).Result() + if err != nil { + log.Println(err) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, top) +} + +func countHandler(context *gin.Context) { + +} + +func rateLimitHandler(c *gin.Context) { + cookie, err := c.Request.Cookie("session_token") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + //check cookie exist in redis + username, err := redisClient.Get(redisClient.Context(), cookie.Value).Result() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + count, err := redisClient.Incr(redisClient.Context(), username).Result() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if count == 1 { + redisClient.Expire(redisClient.Context(), username, time.Minute) + } + + if count >= 3 { + c.JSON(http.StatusTooManyRequests, gin.H{"error": "too many requests"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "success"}) +} + +func generateSessionID() string { + sessionToken := uuid.NewString() + return sessionToken +} + +type LoginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func checkLogin(loginRequest LoginRequest) bool { + return loginRequest.Username == "admin" && loginRequest.Password == "admin" +} + +func loginHandler(c *gin.Context) { + var loginRequest LoginRequest + if err := c.BindJSON(&loginRequest); err != nil { + log.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if !checkLogin(loginRequest) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"}) + return + } + + sessionToken := generateSessionID() + fmt.Println(sessionToken) + + err := redisClient.Set(redisClient.Context(), sessionToken, loginRequest.Username, time.Minute).Err() + if err != nil { + log.Println(err) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + http.SetCookie(c.Writer, &http.Cookie{ + Name: "session_token", + Value: sessionToken, + Expires: time.Now().Add(time.Minute), + }) + c.JSON(http.StatusOK, gin.H{"message": "login success"}) +} + +func acquireLock(redisClient *redis.Client, lockKey string, value string, lockTimeout time.Duration) bool { + for { + result := redisClient.SetNX(redisClient.Context(), lockKey, value, lockTimeout) + if result.Err() != nil { + log.Fatal("Failed to acquire lock") + return false + } + if result.Val() { + return true + } + + // Lock not acquired, wait and retry + time.Sleep(time.Second) + } +} + +func releaseLock(redisClient *redis.Client, lockKey string) bool { + result := redisClient.Del(redisClient.Context(), lockKey) + if result.Err() != nil { + log.Fatal("Failed to release lock") + return false + } + return true +} + +func pingHandler(c *gin.Context) { + cookie, err := c.Request.Cookie("session_token") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + //check cookie exist in redis + username, err := redisClient.Get(redisClient.Context(), cookie.Value).Result() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + //thread + value := uuid.NewString() + fmt.Sprintf("thread: %s", value) + + lockKey := "lock" + if !acquireLock(redisClient, lockKey, value, 5*time.Minute) { + c.JSON(http.StatusBadRequest, gin.H{"error": "failed to acquire lock"}) + return + } + defer releaseLock(redisClient, lockKey) + time.Sleep(5 * time.Second) + redisClient.Incr(redisClient.Context(), username) //count + redisClient.ZIncrBy(redisClient.Context(), "top", 1, username) //top + redisClient.PFAdd(redisClient.Context(), "hyperloglog", username) //hyperloglog + c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Hello %s", username)}) +} + +func pingCountHandler(c *gin.Context) { + cookie, err := c.Request.Cookie("session_token") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + //check cookie exist in redis + username, err := redisClient.Get(redisClient.Context(), cookie.Value).Result() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + //count + count, err := redisClient.Get(redisClient.Context(), username).Int() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Count: %d", count)}) +}