-
Notifications
You must be signed in to change notification settings - Fork 30
TuHo_Week2-DB-Redis #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: scott/sample-gorm
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,265 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "github.com/gin-gonic/gin" | ||
| "github.com/go-redis/redis/v8" | ||
| "gorm.io/driver/mysql" | ||
| "gorm.io/gorm" | ||
| "math/rand" | ||
| "net/http" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
| ) | ||
|
|
||
| var redisClient *redis.Client | ||
| var router *gin.Engine | ||
| var db *gorm.DB | ||
|
|
||
| var key_ping string | ||
|
|
||
| type User struct { | ||
| ID int `json:"-" gorm:"column:id;"` | ||
| Username string `json:"username" gorm:"column:name;"` | ||
| Password string `json:"password" gorm:"column:password;"` | ||
| } | ||
|
|
||
| func (User) TableName() string { | ||
| return "User" | ||
| } | ||
|
|
||
| func init() { | ||
| var err error | ||
| db, err = gorm.Open(mysql.New(mysql.Config{ | ||
| DSN: "root:12346@tcp(127.0.0.1:3307)/todo_list?charset=utf8mb4&parseTime=True&loc=Local", | ||
| DefaultStringSize: 256, | ||
| DisableDatetimePrecision: true, | ||
| DontSupportRenameIndex: true, | ||
| SkipInitializeWithVersion: false, | ||
| }), &gorm.Config{ | ||
| SkipDefaultTransaction: true, | ||
| }) | ||
| if err != nil { | ||
| fmt.Println("can not connect to db ", err) | ||
| return | ||
| } | ||
|
|
||
| // Khởi tạo Redis Client | ||
| redisClient = redis.NewClient(&redis.Options{ | ||
| Addr: "localhost:6379", | ||
| Password: "", // no password set | ||
| DB: 0, // use default DB | ||
| }) | ||
|
|
||
| router = gin.Default() | ||
|
|
||
| key_ping = "ping_ping" | ||
| } | ||
|
|
||
| func get_data(db *gorm.DB) func(*gin.Context) { | ||
| return func(c *gin.Context) { | ||
| var data User | ||
| id, err := strconv.Atoi(c.Param("id")) | ||
|
|
||
| if err != nil { | ||
| c.JSON(http.StatusBadRequest, gin.H{ | ||
| "error": err.Error(), | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| err = db.Where("id =? ", id).First(&data).Error | ||
| if err != nil { | ||
| c.JSON(http.StatusBadRequest, gin.H{ | ||
| "error": err.Error(), | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusOK, gin.H{ | ||
| "data": data, | ||
| }) | ||
|
|
||
| } | ||
|
|
||
| } | ||
|
|
||
| func login() { | ||
| // Đăng ký API /login | ||
| router.POST("/login", func(c *gin.Context) { | ||
|
|
||
| // Lấy thông tin đăng nhập từ request body | ||
| var loginInfo struct { | ||
| Username string `json:"username"` | ||
| Password string `json:"password"` | ||
| } | ||
| var data User | ||
| if err := c.BindJSON(&loginInfo); err != nil { | ||
| c.JSON(400, gin.H{"error": "Invalid request body"}) | ||
| return | ||
| } | ||
|
|
||
| // query from db | ||
| err := db.Where(&User{Username: loginInfo.Username, Password: loginInfo.Password}).First(&data).Error | ||
|
|
||
| if err != nil { | ||
| c.JSON(401, gin.H{"error": "Invalid username or password"}) | ||
| return | ||
| } | ||
|
|
||
| // Tạo session id ngẫu nhiên | ||
| rand.Seed(time.Now().UnixNano()) | ||
| sessionID := strconv.Itoa(rand.Intn(1000000000)) | ||
|
|
||
| // Lưu thông tin session vào Redis | ||
| value := fmt.Sprintf("%d+%s+%s", data.ID, data.Username, data.Password) | ||
| err_redis := redisClient.Set(redisClient.Context(), sessionID, value, time.Hour).Err() | ||
| if err_redis != nil { | ||
| c.JSON(500, gin.H{"error": "Internal server error"}) | ||
| return | ||
| } | ||
|
|
||
| // Trả về thông tin đăng nhập thành công và session id | ||
| c.JSON(200, gin.H{"message": "Login successful", "session_id": sessionID}) | ||
| }) | ||
| } | ||
|
|
||
| var flag bool | ||
|
|
||
| func ping() { | ||
| // Đăng ký API /ping | ||
|
|
||
| router.GET("/ping", func(c *gin.Context) { | ||
| if flag { | ||
hoanhtu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| c.AbortWithStatusJSON(429, gin.H{"error": "too many requests"}) | ||
| return | ||
| } | ||
| sessionID := c.GetHeader("sessionID") | ||
|
|
||
| value, err := redisClient.Get(redisClient.Context(), sessionID).Result() | ||
| if err != nil { | ||
| c.AbortWithStatusJSON(429, gin.H{"error": "cant get data user"}) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. return here ? |
||
| } | ||
| parts := strings.Split(value, "+") | ||
| fmt.Println(parts) | ||
| // Tăng biến đếm lên 1 và lưu trữ vào Redis | ||
|
|
||
| mapKey := fmt.Sprintf("user:%s:MapCount ", parts[0]) | ||
|
|
||
| mapValue, errPing := redisClient.HGetAll(redisClient.Context(), mapKey).Result() | ||
| now := time.Now().Unix() | ||
| timeCount, err := strconv.ParseInt(mapValue["timeCount"], 10, 64) | ||
| if err != nil { | ||
| c.AbortWithStatusJSON(500, gin.H{"error": "convert time false"}) | ||
| return | ||
| } | ||
|
|
||
| if errPing != nil || now-timeCount > 60 { | ||
| myMap := map[string]interface{}{ | ||
| "timeCount": now, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to implement like this, only need to store the count number (no need time) then set an expire time for it (60s in this case) then if it over 60s, this key will be deleted (which mean if someone increase or get will be 0) |
||
| "count": 0, | ||
| } | ||
| errMap := redisClient.HSet(redisClient.Context(), mapKey, myMap).Err() | ||
| if errMap != nil { | ||
| c.AbortWithStatusJSON(500, gin.H{"error": "set key map error"}) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| numPing, errNumPing := strconv.ParseInt(mapValue["timeCount"], 10, 64) | ||
|
|
||
| if errNumPing != nil || numPing >= 2 { | ||
| c.AbortWithStatusJSON(500, gin.H{"error": "user already ping 2 time"}) | ||
| return | ||
| } | ||
| // increase count | ||
| errIncreaseCount := redisClient.HIncrBy(redisClient.Context(), mapKey, "count", 1).Err() | ||
| if errIncreaseCount != nil { | ||
| c.AbortWithStatusJSON(500, gin.H{"error": "increase count in 60s"}) | ||
| return | ||
| } | ||
|
|
||
| // đếm số lần ping của user | ||
| keyUser := fmt.Sprintf("user:%s:request_count", parts[0]) | ||
| _, err1 := redisClient.Incr(redisClient.Context(), keyUser).Result() | ||
| if err1 != nil { | ||
| c.AbortWithStatusJSON(500, gin.H{"error": "internal server error"}) | ||
| return | ||
| } | ||
|
|
||
| // Truy vấn số lượng request của người dùng đó từ Redis | ||
| count, err := redisClient.Get(redisClient.Context(), keyUser).Int() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can get the count number from the response of incr command instead, no need to call one more time |
||
| if err != nil { | ||
| c.AbortWithStatusJSON(500, gin.H{"error": "get total count error"}) | ||
| return | ||
| } | ||
|
|
||
| count60, errCount60s := redisClient.HGet(redisClient.Context(), mapKey, "count").Result() | ||
|
|
||
| if errCount60s != nil { | ||
| c.AbortWithStatusJSON(500, gin.H{"error": "get count60 error"}) | ||
| return | ||
| } | ||
|
|
||
| // | ||
| numPings, err := redisClient.ZScore(redisClient.Context(), key_ping, keyUser).Result() | ||
|
|
||
| c.JSON(200, gin.H{"all count": count, "count in 60s": count60, "all count with key root": numPings}) | ||
|
|
||
| flag = true | ||
| // Thực hiện sleep trong 5 giây | ||
|
|
||
| time.Sleep(5 * time.Second) | ||
| flag = false | ||
| }) | ||
| } | ||
|
|
||
| func topPing() { | ||
| router.GET("/top", func(c *gin.Context) { | ||
| // Lấy top 10 người gọi API /ping nhiều nhất | ||
| result, err := redisClient.ZRevRangeWithScores(redisClient.Context(), key_ping, 0, 9).Result() | ||
| if err != nil { | ||
| c.JSON(500, gin.H{ | ||
| "error": "Internal server error", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| // Định dạng kết quả trảvề cho người dùng | ||
| var topUsers []gin.H | ||
| for _, z := range result { | ||
| topUsers = append(topUsers, gin.H{ | ||
| "username": z.Member, | ||
| "ping_calls": z.Score, | ||
| }) | ||
| } | ||
|
|
||
| // Trả về kết quả | ||
| c.JSON(200, topUsers) | ||
| }) | ||
| } | ||
|
|
||
| func hyperLogLog() { | ||
|
|
||
| router.GET("/count", func(c *gin.Context) { | ||
| // Get the unique API call count from Redis | ||
| count, err := redisClient.PFCount(redisClient.Context(), key_ping).Result() | ||
| if err != nil { | ||
| // Handle error | ||
| } | ||
|
|
||
| // Return the count to the user | ||
| c.JSON(200, gin.H{ | ||
| "count": count, | ||
| }) | ||
| }) | ||
| } | ||
| func main() { | ||
|
|
||
| login() | ||
| ping() | ||
| topPing() | ||
| hyperLogLog() | ||
| router.Run(":8080") | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
random like this can make collision rate high if number of user is big , you should use uuid, or hash from user_name