Browse Source

update task and reward

tags/v1.22.9.2^2
chenyifan01 3 years ago
parent
commit
57a590de79
18 changed files with 347 additions and 69 deletions
  1. +12
    -0
      models/error.go
  2. +1
    -1
      models/point_account_log.go
  3. +10
    -4
      models/point_operate_record.go
  4. +0
    -12
      models/point_task_accomplish_log.go
  5. +0
    -25
      models/point_task_config.go
  6. +51
    -0
      models/task_accomplish_log.go
  7. +53
    -0
      models/task_config.go
  8. +3
    -5
      modules/auth/wechat/access_token.go
  9. +2
    -0
      modules/redis/redis_key/key_base.go
  10. +16
    -0
      modules/redis/redis_key/task_redis_key.go
  11. +1
    -1
      services/reward/callback.go
  12. +19
    -6
      services/reward/operator.go
  13. +7
    -4
      services/reward/point/point_operate.go
  14. +1
    -1
      services/reward/reward.go
  15. +39
    -0
      services/task/limiter.go
  16. +0
    -10
      services/task/point_task.go
  17. +104
    -0
      services/task/task.go
  18. +28
    -0
      services/task/task_config.go

+ 12
- 0
models/error.go View File

@@ -2012,3 +2012,15 @@ func IsErrTagNotExist(err error) bool {
_, ok := err.(ErrTagNotExist)
return ok
}

type ErrRecordNotExist struct {
}

func IsErrRecordNotExist(err error) bool {
_, ok := err.(ErrRecordNotExist)
return ok
}

func (err ErrRecordNotExist) Error() string {
return fmt.Sprintf("record not exist in database")
}

+ 1
- 1
models/point_account_log.go View File

@@ -7,7 +7,7 @@ type PointAccountLog struct {
AccountId int64 `xorm:"INDEX NOT NULL"`
UserId int64 `xorm:"INDEX NOT NULL"`
Type string `xorm:"NOT NULL"`
RelatedId string `xorm:"INDEX NOT NULL"`
SourceId string `xorm:"INDEX NOT NULL"`
PointsAmount int64 `xorm:"NOT NULL"`
AmountBefore int64 `xorm:"NOT NULL"`
AmountAfter int64 `xorm:"NOT NULL"`


+ 10
- 4
models/point_operate_record.go View File

@@ -5,9 +5,15 @@ import "code.gitea.io/gitea/modules/timeutil"
type RewardSourceType string

const (
SourceTypeAccomplishPointTask RewardSourceType = "ACCOMPLISH_POINT_TASK"
SourceTypeAdminOperate RewardSourceType = "ADMIN_OPERATE"
SourceTypeRunCloudbrainTask RewardSourceType = "RUN_CLOUBRAIN_TASK"
SourceTypeAccomplishTask RewardSourceType = "ACCOMPLISH_TASK"
SourceTypeAdminOperate RewardSourceType = "ADMIN_OPERATE"
SourceTypeRunCloudbrainTask RewardSourceType = "RUN_CLOUBRAIN_TASK"
)

type RewardType string

const (
RewardTypePoint RewardType = "POINT"
)

const (
@@ -26,7 +32,7 @@ type PointOperateRecord struct {
UserId int64 `xorm:"INDEX NOT NULL"`
PointsAmount int64 `xorm:"NOT NULL"`
RelatedType string `xorm:"NOT NULL"`
RelatedId string `xorm:"INDEX NOT NULL"`
SourceId string `xorm:"INDEX NOT NULL"`
OperateType string `xorm:"NOT NULL"`
OperateRate string `xorm:"NOT NULL default once"`
Status string `xorm:"NOT NULL"`


+ 0
- 12
models/point_task_accomplish_log.go View File

@@ -1,12 +0,0 @@
package models

import "code.gitea.io/gitea/modules/timeutil"

type PointTaskAccomplishLog struct {
ID int64 `xorm:"pk autoincr"`
ConfigId int64 `xorm:"NOT NULL"`
TaskCode int64 `xorm:"NOT NULL"`
UserId int64 `xorm:"INDEX NOT NULL"`
RelatedId int64 `xorm:"INDEX NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

+ 0
- 25
models/point_task_config.go View File

@@ -1,25 +0,0 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
)

const (
TaskConfigRefreshRateOnce = "ONCE"
TaskConfigRefreshRateDaily = "DAILY"
)

//PointTaskConfig Only add and delete are allowed, edit is not allowed
//so if you want to edit config for some task code,please delete first and add new one
type PointTaskConfig struct {
ID int64 `xorm:"pk autoincr"`
TaskCode string `xorm:"NOT NULL"`
Tittle string `xorm:"NOT NULL"`
RefreshRate string `xorm:"NOT NULL"`
Times int `xorm:"NOT NULL"`
AwardPoints int `xorm:"NOT NULL"`
Status int `xorm:"NOT NULL"`
Creator int64 `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
DeletedAt timeutil.TimeStamp `xorm:"deleted"`
}

+ 51
- 0
models/task_accomplish_log.go View File

@@ -0,0 +1,51 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"time"
)

type TaskAccomplishLog struct {
ID int64 `xorm:"pk autoincr"`
ConfigId int64 `xorm:"NOT NULL"`
TaskCode string `xorm:"NOT NULL"`
UserId int64 `xorm:"INDEX NOT NULL"`
SourceId string `xorm:"INDEX NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

type LimiterPeriod struct {
StartTime time.Time
EndTime time.Time
}

func getTaskAccomplishLog(tl *TaskAccomplishLog) (*TaskAccomplishLog, error) {
has, err := x.Get(tl)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return tl, nil
}

func GetTaskAccomplishLogBySourceIdAndTaskCode(sourceId, taskCode string) (*TaskAccomplishLog, error) {
t := &TaskAccomplishLog{
SourceId: sourceId,
TaskCode: taskCode,
}
return getTaskAccomplishLog(t)
}

func CountOnceTask(configId int64, userId int64, period *LimiterPeriod) (int64, error) {
if period == nil {
return x.Where("config_id = ? and user_id = ?", configId, userId).Count(&TaskAccomplishLog{})
} else {
return x.Where("config_id = ? and user_id = ? and created_unix >= ? and created_unix < ? ", configId, userId, period.StartTime.Unix(), period.EndTime.Unix()).Count(&TaskAccomplishLog{})
}

}

func InsertTaskAccomplishLog(tl *TaskAccomplishLog) (int64, error) {
return x.Insert(tl)
}

+ 53
- 0
models/task_config.go View File

@@ -0,0 +1,53 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"fmt"
)

type TaskType string

const (
TaskTypeComment TaskType = "COMMENT"
)

func (t *TaskType) String() string {
return fmt.Sprint(t)
}

const (
TaskConfigRefreshRateNotCycle = "NOT_CYCLE"
TaskConfigRefreshRateDaily = "DAILY"
)

//PointTaskConfig Only add and delete are allowed, edit is not allowed
//so if you want to edit config for some task code,please delete first and add new one
type TaskConfig struct {
ID int64 `xorm:"pk autoincr"`
TaskCode string `xorm:"NOT NULL"`
Tittle string `xorm:"NOT NULL"`
RefreshRate string `xorm:"NOT NULL"`
Times int64 `xorm:"NOT NULL"`
AwardType string `xorm:"NOT NULL"`
AwardAmount int64 `xorm:"NOT NULL"`
Creator int64 `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
DeletedAt timeutil.TimeStamp `xorm:"deleted"`
}

func getTaskConfig(t *TaskConfig) (*TaskConfig, error) {
has, err := x.Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return t, nil
}

func GetTaskConfigByTaskCode(taskCode string) (*TaskConfig, error) {
t := &TaskConfig{
TaskCode: taskCode,
}
return getTaskConfig(t)
}

+ 3
- 5
modules/auth/wechat/access_token.go View File

@@ -7,14 +7,12 @@ import (
"time"
)

const EMPTY_REDIS_VAL = "Nil"

var accessTokenLock = redis_lock.NewDistributeLock(redis_key.AccessTokenLockKey())

func GetWechatAccessToken() string {
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" {
if token == EMPTY_REDIS_VAL {
if token == redis_key.EMPTY_REDIS_VAL {
return ""
}
live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey())
@@ -39,7 +37,7 @@ func refreshAndGetAccessToken() string {
defer accessTokenLock.UnLock()
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" {
if token == EMPTY_REDIS_VAL {
if token == redis_key.EMPTY_REDIS_VAL {
return ""
}
return token
@@ -59,7 +57,7 @@ func callAccessTokenAndUpdateCache() string {
}

if token == "" {
redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second)
redis_client.Setex(redis_key.WechatAccessTokenKey(), redis_key.EMPTY_REDIS_VAL, 10*time.Second)
return ""
}
redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second)


+ 2
- 0
modules/redis/redis_key/key_base.go View File

@@ -4,6 +4,8 @@ import "strings"

const KEY_SEPARATE = ":"

const EMPTY_REDIS_VAL = "Nil"

func KeyJoin(keys ...string) string {
var build strings.Builder
for _, v := range keys {


+ 16
- 0
modules/redis/redis_key/task_redis_key.go View File

@@ -0,0 +1,16 @@
package redis_key

import (
"code.gitea.io/gitea/models"
"fmt"
)

const TASK_REDIS_PREFIX = "task"

func TaskAccomplishLock(userId int64, sourceId string, taskType models.TaskType) string {
return KeyJoin(TASK_REDIS_PREFIX, fmt.Sprint(userId), sourceId, taskType.String(), "accomplish")
}

func TaskConfig(taskType models.TaskType) string {
return KeyJoin(TASK_REDIS_PREFIX, "config", taskType.String())
}

services/reward/operate/callback.go → services/reward/callback.go View File

@@ -1,4 +1,4 @@
package operate
package reward

type CallbackHandler struct {
}

services/reward/operate/operator.go → services/reward/operator.go View File

@@ -1,17 +1,22 @@
package operate
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/services/reward"
"code.gitea.io/gitea/services/reward/point"
"errors"
"fmt"
)

var RewardOperatorMap = map[string]RewardOperator{
fmt.Sprint(models.RewardTypePoint): new(point.PointOperator),
}

type RewardOperateContext struct {
SourceType models.RewardSourceType
RelatedId string
SourceId string
Remark string
Reward reward.Reward
Reward Reward
TargetUserId int64
RequestId string
}

type RewardOperateResponse int
@@ -31,7 +36,11 @@ type RewardOperator interface {
Operate(ctx RewardOperateContext) error
}

func Operate(operator RewardOperator, ctx RewardOperateContext) error {
func Send(ctx RewardOperateContext) error {
operator := GetOperator(ctx.Reward.Type)
if operator == nil {
return errors.New("operator of reward type is not exist")
}
if operator.IsOperated(ctx) {
return nil
}
@@ -43,3 +52,7 @@ func Operate(operator RewardOperator, ctx RewardOperateContext) error {
}
return nil
}

func GetOperator(rewardType string) RewardOperator {
return RewardOperatorMap[rewardType]
}

+ 7
- 4
services/reward/point/point_operate.go View File

@@ -1,16 +1,19 @@
package point

import "code.gitea.io/gitea/services/reward/operate"
import (
"code.gitea.io/gitea/services/reward"
)

type PointOperator struct {
}

func (operator *PointOperator) IsOperated(ctx operate.RewardOperateContext) bool {
func (operator *PointOperator) IsOperated(ctx reward.RewardOperateContext) bool {
//todo
return true
}
func (operator *PointOperator) IsLimited(ctx operate.RewardOperateContext) bool {
func (operator *PointOperator) IsLimited(ctx reward.RewardOperateContext) bool {
return true
}
func (operator *PointOperator) Operate(ctx operate.RewardOperateContext) error {
func (operator *PointOperator) Operate(ctx reward.RewardOperateContext) error {
return nil
}

+ 1
- 1
services/reward/reward.go View File

@@ -1,6 +1,6 @@
package reward

type Reward struct {
Amount int
Amount int64
Type string
}

+ 39
- 0
services/task/limiter.go View File

@@ -0,0 +1,39 @@
package task

import (
"code.gitea.io/gitea/models"
"time"
)

var LimiterMap = map[string]Limiter{
models.TaskConfigRefreshRateNotCycle: new(NoCycleLimiter),
models.TaskConfigRefreshRateDaily: new(DailyLimiter),
}

type Limiter interface {
GetCurrentPeriod() *models.LimiterPeriod
}

type NoCycleLimiter struct {
}

func (l *NoCycleLimiter) GetCurrentPeriod() *models.LimiterPeriod {
return nil
}

type DailyLimiter struct {
}

func (l *DailyLimiter) GetCurrentPeriod() *models.LimiterPeriod {
t := time.Now()
startTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
endTime := startTime.Add(24 * time.Hour)
return &models.LimiterPeriod{
StartTime: startTime,
EndTime: endTime,
}
}

func GetLimiter(refreshRateype string) Limiter {
return LimiterMap[refreshRateype]
}

+ 0
- 10
services/task/point_task.go View File

@@ -1,10 +0,0 @@
package task

func Accomplish() error {
//1、幂等性判断
//2、获取任务配置
//3、判断任务是否可以完成
//4、生成任务记录
//5、触发奖励发放
return nil
}

+ 104
- 0
services/task/task.go View File

@@ -0,0 +1,104 @@
package task

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/services/reward"
"errors"
"time"
)

func Accomplish(userId int64, taskType models.TaskType, sourceId string) {
go accomplish(userId, taskType, sourceId)
}

func accomplish(userId int64, taskType models.TaskType, sourceId string) error {
//lock
var taskLock = redis_lock.NewDistributeLock(redis_key.TaskAccomplishLock(userId, sourceId, taskType))
if !taskLock.Lock(3 * time.Second) {
log.Info("duplicated task request,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId)
return nil
}
defer taskLock.UnLock()

//is handled before?
isHandled, err := isHandled(taskType, sourceId)
if err != nil {
log.Error("Get isHandled error,%v", err)
return err
}
if isHandled {
log.Info("task has been handled,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId)
return nil
}

//get task config
config, err := GetTaskConfig(taskType)
if err != nil {
log.Error("GetTaskConfig error,%v", err)
return err
}
if config == nil {
log.Info("task config not exist,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId)
return nil
}

//is limited?
isLimited, err := IsLimited(userId, config)
if err != nil {
log.Error("get limited error,%v", err)
return err
}
if isLimited {
log.Info("task accomplish maximum times are reached,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId)
return nil
}

//add log
models.InsertTaskAccomplishLog(&models.TaskAccomplishLog{
ConfigId: config.ID,
TaskCode: config.TaskCode,
UserId: userId,
SourceId: sourceId,
})

//reward
reward.Send(reward.RewardOperateContext{
SourceType: models.SourceTypeAccomplishTask,
SourceId: sourceId,
Reward: reward.Reward{
Amount: config.AwardAmount,
Type: config.AwardType,
},
TargetUserId: userId,
})

return nil
}

func isHandled(taskType models.TaskType, sourceId string) (bool, error) {
_, err := models.GetTaskAccomplishLogBySourceIdAndTaskCode(sourceId, taskType.String())
if err != nil {
if models.IsErrRecordNotExist(err) {
return false, nil
}
return false, err
}
return true, nil

}

func IsLimited(userId int64, config *models.TaskConfig) (bool, error) {
limiter := GetLimiter(config.RefreshRate)
if limiter == nil {
return false, errors.New("task config incorrect")
}
n, err := models.CountOnceTask(config.ID, userId, limiter.GetCurrentPeriod())
if err != nil {
return false, err
}
return n >= config.Times, nil

}

+ 28
- 0
services/task/task_config.go View File

@@ -0,0 +1,28 @@
package task

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"encoding/json"
)

func GetTaskConfig(taskType models.TaskType) (*models.TaskConfig, error) {
configStr, _ := redis_client.Get(redis_key.TaskConfig(taskType))
if configStr != "" {
if configStr == redis_key.EMPTY_REDIS_VAL {
return nil, nil
}
config := new(models.TaskConfig)
json.Unmarshal([]byte(configStr), config)
return config, nil
}
config, err := models.GetTaskConfigByTaskCode(taskType.String())
if err != nil {
if models.IsErrRecordNotExist(err) {
return nil, nil
}
return nil, err
}
return config, nil
}

Loading…
Cancel
Save