فهرست منبع

feat: implement concurrent top-up locking mechanism to prevent race conditions

CaIon 4 ماه پیش
والد
کامیت
f70cac54d1
1فایلهای تغییر یافته به همراه50 افزوده شده و 5 حذف شده
  1. 50 5
      controller/user.go

+ 50 - 5
controller/user.go

@@ -844,18 +844,64 @@ type topUpRequest struct {
 	Key string `json:"key"`
 }
 
-var topUpLock = sync.Mutex{}
+var topUpLocks sync.Map
+var topUpCreateLock sync.Mutex
+
+type topUpTryLock struct {
+	ch chan struct{}
+}
+
+func newTopUpTryLock() *topUpTryLock {
+	return &topUpTryLock{ch: make(chan struct{}, 1)}
+}
+
+func (l *topUpTryLock) TryLock() bool {
+	select {
+	case l.ch <- struct{}{}:
+		return true
+	default:
+		return false
+	}
+}
+
+func (l *topUpTryLock) Unlock() {
+	select {
+	case <-l.ch:
+	default:
+	}
+}
+
+func getTopUpLock(userID int) *topUpTryLock {
+	if v, ok := topUpLocks.Load(userID); ok {
+		return v.(*topUpTryLock)
+	}
+	topUpCreateLock.Lock()
+	defer topUpCreateLock.Unlock()
+	if v, ok := topUpLocks.Load(userID); ok {
+		return v.(*topUpTryLock)
+	}
+	l := newTopUpTryLock()
+	topUpLocks.Store(userID, l)
+	return l
+}
 
 func TopUp(c *gin.Context) {
-	topUpLock.Lock()
-	defer topUpLock.Unlock()
+	id := c.GetInt("id")
+	lock := getTopUpLock(id)
+	if !lock.TryLock() {
+		c.JSON(http.StatusOK, gin.H{
+			"success": false,
+			"message": "充值处理中,请稍后重试",
+		})
+		return
+	}
+	defer lock.Unlock()
 	req := topUpRequest{}
 	err := c.ShouldBindJSON(&req)
 	if err != nil {
 		common.ApiError(c, err)
 		return
 	}
-	id := c.GetInt("id")
 	quota, err := model.Redeem(req.Key, id)
 	if err != nil {
 		common.ApiError(c, err)
@@ -866,7 +912,6 @@ func TopUp(c *gin.Context) {
 		"message": "",
 		"data":    quota,
 	})
-	return
 }
 
 type UpdateUserSettingRequest struct {