| @@ -3,11 +3,14 @@ package wechat | |||||
| import ( | import ( | ||||
| "code.gitea.io/gitea/modules/redis/redis_client" | "code.gitea.io/gitea/modules/redis/redis_client" | ||||
| "code.gitea.io/gitea/modules/redis/redis_key" | "code.gitea.io/gitea/modules/redis/redis_key" | ||||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||||
| "time" | "time" | ||||
| ) | ) | ||||
| const EMPTY_REDIS_VAL = "Nil" | const EMPTY_REDIS_VAL = "Nil" | ||||
| var accessTokenLock = redis_lock.NewDistributeLock() | |||||
| func GetWechatAccessToken() string { | func GetWechatAccessToken() string { | ||||
| token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) | token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) | ||||
| if token != "" { | if token != "" { | ||||
| @@ -17,14 +20,37 @@ func GetWechatAccessToken() string { | |||||
| live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey()) | live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey()) | ||||
| //refresh wechat access token when expire time less than 5 minutes | //refresh wechat access token when expire time less than 5 minutes | ||||
| if live > 0 && live < 300 { | if live > 0 && live < 300 { | ||||
| refreshAccessTokenCache() | |||||
| refreshAccessToken() | |||||
| } | } | ||||
| return token | return token | ||||
| } | } | ||||
| return refreshAccessTokenCache() | |||||
| return refreshAndGetAccessToken() | |||||
| } | |||||
| func refreshAccessToken() { | |||||
| if ok := accessTokenLock.Lock(redis_key.AccessTokenLockKey(), 3*time.Second); ok { | |||||
| defer accessTokenLock.UnLock(redis_key.AccessTokenLockKey()) | |||||
| callAccessTokenAndUpdateCache() | |||||
| } | |||||
| } | |||||
| func refreshAndGetAccessToken() string { | |||||
| if ok := accessTokenLock.LockWithWait(redis_key.AccessTokenLockKey(), 3*time.Second, 3*time.Second); ok { | |||||
| defer accessTokenLock.UnLock(redis_key.AccessTokenLockKey()) | |||||
| token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) | |||||
| if token != "" { | |||||
| if token == EMPTY_REDIS_VAL { | |||||
| return "" | |||||
| } | |||||
| return token | |||||
| } | |||||
| return callAccessTokenAndUpdateCache() | |||||
| } | |||||
| return "" | |||||
| } | } | ||||
| func refreshAccessTokenCache() string { | |||||
| func callAccessTokenAndUpdateCache() string { | |||||
| r := callAccessToken() | r := callAccessToken() | ||||
| var token string | var token string | ||||
| @@ -36,9 +62,6 @@ func refreshAccessTokenCache() string { | |||||
| redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second) | redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second) | ||||
| return "" | return "" | ||||
| } | } | ||||
| redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second) | redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second) | ||||
| return token | return token | ||||
| } | } | ||||
| @@ -6,7 +6,7 @@ func GetWechatQRCode4Bind(sceneStr string) *QRCodeResponse { | |||||
| result, retryFlag := callQRCodeCreate(sceneStr) | result, retryFlag := callQRCodeCreate(sceneStr) | ||||
| if retryFlag { | if retryFlag { | ||||
| log.Info("retry wechat qr-code calling,sceneStr=%s", sceneStr) | log.Info("retry wechat qr-code calling,sceneStr=%s", sceneStr) | ||||
| refreshAccessTokenCache() | |||||
| refreshAccessToken() | |||||
| result, _ = callQRCodeCreate(sceneStr) | result, _ = callQRCodeCreate(sceneStr) | ||||
| } | } | ||||
| return result | return result | ||||
| @@ -9,7 +9,6 @@ import ( | |||||
| "time" | "time" | ||||
| ) | ) | ||||
| //todo redis连接池 | |||||
| func Setex(key, value string, timeout time.Duration) (bool, error) { | func Setex(key, value string, timeout time.Duration) (bool, error) { | ||||
| redisClient := labelmsg.Get() | redisClient := labelmsg.Get() | ||||
| defer redisClient.Close() | defer redisClient.Close() | ||||
| @@ -26,6 +25,22 @@ func Setex(key, value string, timeout time.Duration) (bool, error) { | |||||
| } | } | ||||
| func Setnx(key, value string, timeout time.Duration) (bool, error) { | |||||
| redisClient := labelmsg.Get() | |||||
| defer redisClient.Close() | |||||
| seconds := int(math.Floor(timeout.Seconds())) | |||||
| reply, err := redisClient.Do("SET", key, value, "NX", "EX", seconds) | |||||
| if err != nil { | |||||
| return false, err | |||||
| } | |||||
| if reply != "OK" { | |||||
| return false, nil | |||||
| } | |||||
| return true, nil | |||||
| } | |||||
| func Get(key string) (string, error) { | func Get(key string) (string, error) { | ||||
| redisClient := labelmsg.Get() | redisClient := labelmsg.Get() | ||||
| defer redisClient.Close() | defer redisClient.Close() | ||||
| @@ -9,3 +9,6 @@ func WechatBindingUserIdKey(sceneStr string) string { | |||||
| func WechatAccessTokenKey() string { | func WechatAccessTokenKey() string { | ||||
| return KeyJoin(PREFIX, "access_token") | return KeyJoin(PREFIX, "access_token") | ||||
| } | } | ||||
| func AccessTokenLockKey() string { | |||||
| return KeyJoin(PREFIX, "access_token_lock") | |||||
| } | |||||
| @@ -0,0 +1,40 @@ | |||||
| package redis_lock | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||||
| "time" | |||||
| ) | |||||
| type DistributeLock struct { | |||||
| } | |||||
| func NewDistributeLock() *DistributeLock { | |||||
| return &DistributeLock{} | |||||
| } | |||||
| func (lock *DistributeLock) Lock(lockKey string, expireTime time.Duration) bool { | |||||
| isOk, _ := redis_client.Setnx(lockKey, "", expireTime) | |||||
| return isOk | |||||
| } | |||||
| func (lock *DistributeLock) LockWithWait(lockKey string, expireTime time.Duration, waitTime time.Duration) bool { | |||||
| start := time.Now().UnixMilli() | |||||
| duration := waitTime.Milliseconds() | |||||
| for { | |||||
| isOk, _ := redis_client.Setnx(lockKey, "", expireTime) | |||||
| if isOk { | |||||
| return true | |||||
| } | |||||
| if time.Now().UnixMilli()-start > duration { | |||||
| return false | |||||
| } | |||||
| time.Sleep(50 * time.Millisecond) | |||||
| } | |||||
| return false | |||||
| } | |||||
| func (lock *DistributeLock) UnLock(lockKey string) error { | |||||
| _, err := redis_client.Del(lockKey) | |||||
| return err | |||||
| } | |||||