Commit c03c7cda authored by vicotor's avatar vicotor

add function for query

parent 99276f71
......@@ -3,5 +3,8 @@ package cachedata
import "errors"
var (
ErrNotExist = errors.New("not exist")
ErrNotExist = errors.New("not exist")
ErrUserDeleted = errors.New("user is deleted")
ErrWaitLockTimeout = errors.New("wait lock timeout")
ErrBalanceNotEnough = errors.New("balance is not enough")
)
package cachedata
import (
"fmt"
"github.com/odysseus/payment/model"
goredislib "github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
"strconv"
"time"
)
func (c *CacheData) Query(path string, uid int64) (*model.TaskType, error) {
locked, release, _ := c.getUserLockWithRetry(uid, USER_INFO_LOCK_DURATION*10)
if !locked {
return nil, ErrWaitLockTimeout
}
defer release()
// 1. get user info.
user, err := c.GetUserInfo(uid)
if err != nil {
log.WithError(err).Error("failed to get user info")
return nil, err
}
if user.Deleted == 1 {
log.WithError(err).Error("user is deleted")
return nil, ErrUserDeleted
}
// 2. get task info.
task, err := c.GetTaskWithPath(path)
if err != nil {
log.WithError(err).Error("failed to get task info")
return nil, err
}
// 3. get user level info.
userLevel, err := c.GetUserLevelInfoByLevelId(int64(user.Level))
if err != nil {
log.WithError(err).Error("failed to get user level info")
return nil, err
}
userLevelAndTaskType, err := c.GetUserLevelAndTaskTypeByLevelIdAndTaskTypeId(int64(user.Level), int64(task.ID))
if err != nil {
log.WithError(err).Error("failed to get user level and task type info")
}
// 4. check if user can do this task.
{
// a. check free times for this user and this task.
passed, err := c.checkQueryForFreeTimes(uid, userLevel, userLevelAndTaskType)
if err != nil {
log.WithError(err).Error("failed to check free times")
return nil, err
}
if passed {
// todo: cost a free time.
return task, nil
}
// b. continue check balance is enough for task.Fee
passed, err = c.checkQueryForCost(uid, user, userLevel, task)
if err != nil {
log.WithError(err).Error("failed to check cost")
return nil, err
}
if passed {
// todo: cost charge.
return task, nil
}
}
return nil, ErrBalanceNotEnough
}
func (c *CacheData) checkQueryForFreeTimes(uid int64, userLevel *model.UserLevel, taskAndUserLevel *model.UserLevelTaskType) (bool, error) {
layoutDay := "2006-01-02"
layoutMonth := "2006-01"
var (
userDayFreeMax = userLevel.FreeCallCountDay
userMonthFreeMax = userLevel.FreeCallCountMonth
taskFreeMax = 0
)
if taskAndUserLevel != nil {
taskFreeMax = int(taskAndUserLevel.FreeCallCount)
}
var (
userDayKey = fmt.Sprintf("k-u-%d:%s:", uid, time.Now().Format(layoutDay))
userMonthKey = fmt.Sprintf("k-u-%d:%s:", uid, time.Now().Format(layoutMonth))
taskUserDayKey = fmt.Sprintf("k-t-%d-u-%d:%s:", taskAndUserLevel.TaskTypeId, uid, time.Now().Format(layoutDay))
taskUserMonth = fmt.Sprintf("k-t-%d-u-%d:%s:", taskAndUserLevel.TaskTypeId, uid, time.Now().Format(layoutMonth))
)
pip := c.rdb.Pipeline()
userDayCmd := pip.Get(c.ctx, userDayKey)
userMonthCmd := pip.Get(c.ctx, userMonthKey)
taskUserDayCmd := pip.Get(c.ctx, taskUserDayKey)
taskUserMonthCmd := pip.Get(c.ctx, taskUserMonth)
_, err := pip.Exec(c.ctx)
if err != nil {
return false, err
}
var (
userDayUsed = int(userDayFreeMax)
userMonthUsed = 0
taskUserDayUsed = 0
taskUserMonthUsed = 0
)
if userDayCmd.Err() == nil {
userDayUsed, _ = strconv.Atoi(userDayCmd.Val())
} else if userDayCmd.Err() == goredislib.Nil {
userDayUsed = 0
}
if userMonthCmd.Err() == nil {
userMonthUsed, _ = strconv.Atoi(userMonthCmd.Val())
} else if userMonthCmd.Err() == goredislib.Nil {
userMonthUsed = 0
}
if taskUserDayCmd.Err() == nil {
taskUserDayUsed, _ = strconv.Atoi(taskUserDayCmd.Val())
} else if taskUserDayCmd.Err() == goredislib.Nil {
taskUserDayUsed = 0
}
if taskUserMonthCmd.Err() == nil {
taskUserMonthUsed, _ = strconv.Atoi(taskUserMonthCmd.Val())
} else if taskUserMonthCmd.Err() == goredislib.Nil {
taskUserMonthUsed = 0
}
// do count check.
if userDayUsed >= int(userDayFreeMax) {
return false, nil
}
if userMonthUsed >= int(userMonthFreeMax) {
return false, nil
}
if taskUserDayUsed >= taskFreeMax {
return false, nil
}
if taskUserMonthUsed >= taskFreeMax {
return false, nil
}
return true, nil
}
func (c *CacheData) checkQueryForCost(uid int64, user *UserInfo, userLevel *model.UserLevel, task *model.TaskType) (bool, error) {
chargeKey := fmt.Sprintf("charge-%d:", uid)
balKey := fmt.Sprintf("bal-%d:", uid)
creditKey := fmt.Sprintf("credit-%d:", uid)
pip := c.rdb.Pipeline()
chargeCmd := pip.Get(c.ctx, chargeKey)
balCmd := pip.Get(c.ctx, balKey)
creditCmd := pip.Get(c.ctx, creditKey)
_, err := pip.Exec(c.ctx)
if err != nil {
return false, err
}
var (
charge = int64(0)
bal = int64(0)
credit = int64(0)
)
if chargeCmd.Err() == nil {
charge, _ = strconv.ParseInt(chargeCmd.Val(), 10, 64)
} else if chargeCmd.Err() == goredislib.Nil {
charge = 0
}
if balCmd.Err() == nil {
bal, _ = strconv.ParseInt(balCmd.Val(), 10, 64)
} else if balCmd.Err() == goredislib.Nil {
bal = 0
}
if creditCmd.Err() == nil {
credit, _ = strconv.ParseInt(creditCmd.Val(), 10, 64)
} else if creditCmd.Err() == goredislib.Nil {
credit = 0
}
if (charge + task.Price) <= (bal + credit) {
return true, nil
}
return false, ErrBalanceNotEnough
}
func (c *CacheData) costFreeTime(uid int64, user *UserInfo, userLevel *model.UserLevel, task *model.TaskType, taskAndUserLevel *model.UserLevelTaskType) error {
layoutDay := "2006-01-02"
layoutMonth := "2006-01"
var (
userDayKey = fmt.Sprintf("k-u-%d:%s:", uid, time.Now().Format(layoutDay))
userMonthKey = fmt.Sprintf("k-u-%d:%s:", uid, time.Now().Format(layoutMonth))
taskUserDayKey = fmt.Sprintf("k-t-%d-u-%d:%s:", taskAndUserLevel.TaskTypeId, uid, time.Now().Format(layoutDay))
taskUserMonth = fmt.Sprintf("k-t-%d-u-%d:%s:", taskAndUserLevel.TaskTypeId, uid, time.Now().Format(layoutMonth))
)
pip := c.rdb.Pipeline()
userDayKeyCmd := pip.Incr(c.ctx, userDayKey)
userMonthCmd := pip.Incr(c.ctx, userMonthKey)
taskUserDayCmd := pip.Incr(c.ctx, taskUserDayKey)
taskUserMonthCmd := pip.Incr(c.ctx, taskUserMonth)
_, err := pip.Exec(c.ctx)
if err != nil {
return err
}
expip := c.rdb.Pipeline()
if userDayKeyCmd.Val() == 1 {
expip.Expire(c.ctx, userDayKey, time.Hour*24)
}
if userMonthCmd.Val() == 1 {
expip.Expire(c.ctx, userMonthKey, time.Hour*24*30)
}
if taskUserDayCmd.Val() == 1 {
expip.Expire(c.ctx, taskUserDayKey, time.Hour*24)
}
if taskUserMonthCmd.Val() == 1 {
expip.Expire(c.ctx, taskUserMonth, time.Hour*24*30)
}
_, err = expip.Exec(c.ctx)
if err != nil {
log.WithError(err).Error("failed to set expire")
}
return nil
}
......@@ -18,6 +18,9 @@ const (
USER_INFO_CHARGE_KEY = USER_INFO_KEY_PRE + "charge:"
USER_INFO_LOCK_DURATION = 10 * 1000 * 1000
LOCK_RETRY_MAX_TIMES = 5
LOCK_RETRY_INTERNAL = 10 * 1000 * 1000
)
type UserInfo struct {
......@@ -38,6 +41,28 @@ func (c *CacheData) checkExist(uid int64) (bool, error) {
}
}
func (c *CacheData) getUserLock(uid int64, timeout time.Duration) (bool, func(), error) {
lockKey := USER_INFO_LOCK_PRE + strconv.FormatInt(uid, 10)
return redisService.TryAcquire(c.ctx, c.rdb, lockKey, timeout)
}
func (c *CacheData) getUserLockWithRetry(uid int64, timeout time.Duration) (bool, func(), error) {
for i := 0; i < LOCK_RETRY_MAX_TIMES; i++ {
locked, release, err := c.getUserLock(uid, timeout)
if err != nil {
log.WithError(err).Error("failed to acquire user lock")
time.Sleep(LOCK_RETRY_INTERNAL)
continue
}
if !locked {
time.Sleep(LOCK_RETRY_INTERNAL)
continue
}
return true, release, nil
}
return false, nil, nil
}
// value: 0: not deleted, 1: deleted
func (c *CacheData) UpdateUserDeleted(uid int64, value int) error {
exist, err := c.checkExist(uid)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment