Browse Source

update

tags/v1.22.9.2^2
chenyifan01 3 years ago
parent
commit
0a4ab4dcaf
21 changed files with 292 additions and 147 deletions
  1. +21
    -9
      models/limit_config.go
  2. +7
    -0
      models/models.go
  3. +4
    -2
      models/point_account.go
  4. +1
    -1
      models/repo_watch.go
  5. +21
    -10
      models/reward_operate_record.go
  6. +5
    -13
      models/task_config.go
  7. +6
    -2
      modules/auth/wechat/access_token.go
  8. +2
    -0
      modules/notification/notification.go
  9. +85
    -0
      modules/notification/task/task.go
  10. +1
    -1
      modules/redis/redis_client/client.go
  11. +2
    -2
      modules/redis/redis_key/account_redis_key.go
  12. +2
    -2
      modules/redis/redis_key/limit_redis_key.go
  13. +4
    -8
      modules/redis/redis_key/task_redis_key.go
  14. +14
    -8
      modules/redis/redis_lock/lock.go
  15. +32
    -18
      services/reward/limiter/limiter.go
  16. +33
    -28
      services/reward/operator.go
  17. +6
    -2
      services/reward/point/account/point_account.go
  18. +17
    -9
      services/reward/point/point_operate.go
  19. +0
    -6
      services/reward/reward.go
  20. +27
    -24
      services/task/task.go
  21. +2
    -2
      services/task/task_config.go

+ 21
- 9
models/limit_config.go View File

@@ -2,6 +2,24 @@ package models


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


type LimitType string

const (
LimitTypeTask LimitType = "TASK"
LimitTypeReward LimitType = "REWARD"
)

func (l LimitType) Name() string {
switch l {
case LimitTypeTask:
return "TASK"
case LimitTypeReward:
return "REWARD"
default:
return ""
}
}

type LimitConfig struct { type LimitConfig struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Tittle string Tittle string
@@ -9,14 +27,15 @@ type LimitConfig struct {
Scope string `xorm:"NOT NULL"` Scope string `xorm:"NOT NULL"`
LimitNum int64 `xorm:"NOT NULL"` LimitNum int64 `xorm:"NOT NULL"`
LimitCode string `xorm:"NOT NULL"` LimitCode string `xorm:"NOT NULL"`
LimitType string `xorm:"NOT NULL"`
Creator int64 `xorm:"NOT NULL"` Creator int64 `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
DeletedAt timeutil.TimeStamp `xorm:"deleted"` DeletedAt timeutil.TimeStamp `xorm:"deleted"`
} }


func findLimitConfig(tl *LimitConfig) ([]LimitConfig, error) {
func GetLimitConfigByLimitCode(limitCode string, limitType LimitType) ([]LimitConfig, error) {
r := make([]LimitConfig, 0) r := make([]LimitConfig, 0)
err := x.Find(r, tl)
err := x.Where("limit_code = ? and limit_type = ?", limitCode, limitType.Name()).Find(&r)
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(r) == 0 { } else if len(r) == 0 {
@@ -24,10 +43,3 @@ func findLimitConfig(tl *LimitConfig) ([]LimitConfig, error) {
} }
return r, nil return r, nil
} }

func GetLimitConfigByLimitCode(limitCode string) ([]LimitConfig, error) {
t := &LimitConfig{
LimitCode: limitCode,
}
return findLimitConfig(t)
}

+ 7
- 0
models/models.go View File

@@ -144,6 +144,13 @@ func init() {
new(WechatBindLog), new(WechatBindLog),
new(OrgStatistic), new(OrgStatistic),
new(SearchRecord), new(SearchRecord),
new(TaskConfig),
new(TaskAccomplishLog),
new(RewardOperateRecord),
new(LimitConfig),
new(PeriodicTask),
new(PointAccountLog),
new(PointAccount),
) )


tablesStatistic = append(tablesStatistic, tablesStatistic = append(tablesStatistic,


+ 4
- 2
models/point_account.go View File

@@ -1,6 +1,8 @@
package models package models


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


type PointAccountStatus int type PointAccountStatus int


@@ -87,7 +89,7 @@ func GetAccountByUserId(userId int64) (*PointAccount, error) {
return nil, err return nil, err
} }
if !has { if !has {
return nil, nil
return nil, ErrRecordNotExist{}
} }
return p, nil return p, nil
} }


+ 1
- 1
models/repo_watch.go View File

@@ -287,7 +287,7 @@ func NotifyWatchers(actions ...*Action) error {


func producer(actions ...*Action) { func producer(actions ...*Action) {
for _, action := range actions { for _, action := range actions {
if !action.IsPrivate{
if !action.IsPrivate {
ActionChan <- action ActionChan <- action
} }
} }


+ 21
- 10
models/reward_operate_record.go View File

@@ -5,18 +5,12 @@ import (
"fmt" "fmt"
) )


type RewardSourceType string

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


func (r *RewardSourceType) String() string {
return fmt.Sprint(r)
}

type RewardType string type RewardType string


const ( const (
@@ -40,6 +34,7 @@ const (


type RewardOperateRecord struct { type RewardOperateRecord struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
RecordId string `xorm:"INDEX NOT NULL"`
UserId int64 `xorm:"INDEX NOT NULL"` UserId int64 `xorm:"INDEX NOT NULL"`
Amount int64 `xorm:"NOT NULL"` Amount int64 `xorm:"NOT NULL"`
RewardType string `xorm:"NOT NULL"` RewardType string `xorm:"NOT NULL"`
@@ -80,5 +75,21 @@ func UpdateAwardOperateRecordStatus(sourceType, requestId, oldStatus, newStatus
r := &RewardOperateRecord{ r := &RewardOperateRecord{
Status: newStatus, Status: newStatus,
} }
return x.Cols("status").Where("source_type=? and requestId=? and status=?", sourceType, requestId, oldStatus).Update(r)
return x.Cols("status").Where("source_type=? and request_id=? and status=?", sourceType, requestId, oldStatus).Update(r)
}

type RewardOperateContext struct {
SourceType string
SourceId string
Remark string
Reward Reward
TargetUserId int64
RequestId string
OperateType string
CycleIntervalSeconds int64
}

type Reward struct {
Amount int64
Type string
} }

+ 5
- 13
models/task_config.go View File

@@ -2,19 +2,13 @@ package models


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


type TaskType string

const ( const (
TaskTypeComment TaskType = "COMMENT"
TaskTypeCreateIssueComment string = "CREATE_IS"
TaskTypeNewIssue = "NEW_ISSUE"
) )


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

const ( const (
PeriodNotCycle = "NOT_CYCLE" PeriodNotCycle = "NOT_CYCLE"
PeriodDaily = "DAILY" PeriodDaily = "DAILY"
@@ -23,11 +17,9 @@ const (
//PointTaskConfig Only add and delete are allowed, edit is not allowed //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 //so if you want to edit config for some task code,please delete first and add new one
type TaskConfig struct { 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"`
ID int64 `xorm:"pk autoincr"`
TaskCode string `xorm:"NOT NULL"`
Tittle string
AwardType string `xorm:"NOT NULL"` AwardType string `xorm:"NOT NULL"`
AwardAmount int64 `xorm:"NOT NULL"` AwardAmount int64 `xorm:"NOT NULL"`
Creator int64 `xorm:"NOT NULL"` Creator int64 `xorm:"NOT NULL"`


+ 6
- 2
modules/auth/wechat/access_token.go View File

@@ -26,14 +26,18 @@ func GetWechatAccessToken() string {
} }


func refreshAccessToken() { func refreshAccessToken() {
if ok := accessTokenLock.Lock(3 * time.Second); ok {
if ok, _ := accessTokenLock.Lock(3 * time.Second); ok {
defer accessTokenLock.UnLock() defer accessTokenLock.UnLock()
callAccessTokenAndUpdateCache() callAccessTokenAndUpdateCache()
} }
} }


func refreshAndGetAccessToken() string { func refreshAndGetAccessToken() string {
if ok := accessTokenLock.LockWithWait(3*time.Second, 3*time.Second); ok {
isOk, err := accessTokenLock.LockWithWait(3*time.Second, 3*time.Second)
if err != nil {
return ""
}
if isOk {
defer accessTokenLock.UnLock() defer accessTokenLock.UnLock()
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" { if token != "" {


+ 2
- 0
modules/notification/notification.go View File

@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/modules/notification/base" "code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/notification/indexer" "code.gitea.io/gitea/modules/notification/indexer"
"code.gitea.io/gitea/modules/notification/mail" "code.gitea.io/gitea/modules/notification/mail"
"code.gitea.io/gitea/modules/notification/task"
"code.gitea.io/gitea/modules/notification/ui" "code.gitea.io/gitea/modules/notification/ui"
"code.gitea.io/gitea/modules/notification/webhook" "code.gitea.io/gitea/modules/notification/webhook"
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
@@ -35,6 +36,7 @@ func NewContext() {
RegisterNotifier(indexer.NewNotifier()) RegisterNotifier(indexer.NewNotifier())
RegisterNotifier(webhook.NewNotifier()) RegisterNotifier(webhook.NewNotifier())
RegisterNotifier(action.NewNotifier()) RegisterNotifier(action.NewNotifier())
RegisterNotifier(task.NewNotifier())
} }


// NotifyUploadAttachment notifies attachment upload message to notifiers // NotifyUploadAttachment notifies attachment upload message to notifiers


+ 85
- 0
modules/notification/task/task.go View File

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

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/services/task"
"fmt"
)

type taskNotifier struct {
base.NullNotifier
}

var (
_ base.Notifier = &taskNotifier{}
)

// NewNotifier create a new actionNotifier notifier
func NewNotifier() base.Notifier {
return &taskNotifier{}
}

func (t *taskNotifier) NotifyNewIssue(issue *models.Issue) {
task.Accomplish(issue.Poster.ID, models.TaskTypeNewIssue, fmt.Sprint(issue.ID))
}

// NotifyIssueChangeStatus notifies close or reopen issue to notifiers
func (t *taskNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, actionComment *models.Comment, closeOrReopen bool) {
return
}

// NotifyCreateIssueComment notifies comment on an issue to notifiers
func (t *taskNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
task.Accomplish(doer.ID, models.TaskTypeCreateIssueComment, fmt.Sprint(comment.ID))
}

func (t *taskNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
task.Accomplish(pull.Issue.Poster.ID, models.TaskTypeCreateIssueComment, fmt.Sprint(pull.ID))
}

func (t *taskNotifier) NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string) {
return
}

func (t *taskNotifier) NotifyAliasRepository(doer *models.User, repo *models.Repository, oldAlias string) {
return
}

func (t *taskNotifier) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) {
return
}

func (t *taskNotifier) NotifyCreateRepository(doer *models.User, u *models.User, repo *models.Repository) {
return
}

func (t *taskNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo *models.Repository) {
return
}

func (t *taskNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
return
}

func (t *taskNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *models.User) {
return
}

func (t *taskNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
return
}

func (t *taskNotifier) NotifySyncCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string) {
return
}

func (t *taskNotifier) NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) {
return
}

func (t *taskNotifier) NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType) {
return
}

+ 1
- 1
modules/redis/redis_client/client.go View File

@@ -103,7 +103,7 @@ func Expire(key string, expireSeconds int64) error {
redisClient := labelmsg.Get() redisClient := labelmsg.Get()
defer redisClient.Close() defer redisClient.Close()


_, err := redisClient.Do("EXPIRE ", key, expireSeconds)
_, err := redisClient.Do("EXPIRE", key, expireSeconds)
if err != nil { if err != nil {
return err return err
} }


+ 2
- 2
modules/redis/redis_key/account_redis_key.go View File

@@ -8,8 +8,8 @@ func PointAccountOperateLock(accountCode string) string {
return KeyJoin(ACCOUNT_REDIS_PREFIX, accountCode, "operate", "lock") return KeyJoin(ACCOUNT_REDIS_PREFIX, accountCode, "operate", "lock")
} }


func PointAccountDetail(userId int64) string {
return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "detail")
func PointAccountInfo(userId int64) string {
return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "info")
} }


func PointAccountInitLock(userId int64) string { func PointAccountInitLock(userId int64) string {


+ 2
- 2
modules/redis/redis_key/limit_redis_key.go View File

@@ -21,6 +21,6 @@ func LimitCount(userId int64, limitCode string, period *models.PeriodResult) str


} }


func LimitConfig(limitCode string) string {
return KeyJoin(LIMIT_REDIS_PREFIX, limitCode, "config")
func LimitConfig(limitCode string, limitType models.LimitType) string {
return KeyJoin(LIMIT_REDIS_PREFIX, limitCode, limitType.Name(), "config")
} }

+ 4
- 8
modules/redis/redis_key/task_redis_key.go View File

@@ -1,15 +1,11 @@
package redis_key package redis_key


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

const TASK_REDIS_PREFIX = "task" const TASK_REDIS_PREFIX = "task"


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


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

+ 14
- 8
modules/redis/redis_lock/lock.go View File

@@ -13,26 +13,32 @@ func NewDistributeLock(lockKey string) *DistributeLock {
return &DistributeLock{lockKey: lockKey} return &DistributeLock{lockKey: lockKey}
} }


func (lock *DistributeLock) Lock(expireTime time.Duration) bool {
isOk, _ := redis_client.Setnx(lock.lockKey, "", expireTime)
return isOk
func (lock *DistributeLock) Lock(expireTime time.Duration) (bool, error) {
isOk, err := redis_client.Setnx(lock.lockKey, "", expireTime)
if err != nil {
return false, err
}
return isOk, nil
} }


func (lock *DistributeLock) LockWithWait(expireTime time.Duration, waitTime time.Duration) bool {
func (lock *DistributeLock) LockWithWait(expireTime time.Duration, waitTime time.Duration) (bool, error) {
start := time.Now().Unix() * 1000 start := time.Now().Unix() * 1000
duration := waitTime.Milliseconds() duration := waitTime.Milliseconds()
for { for {
isOk, _ := redis_client.Setnx(lock.lockKey, "", expireTime)
isOk, err := redis_client.Setnx(lock.lockKey, "", expireTime)
if err != nil {
return false, err
}
if isOk { if isOk {
return true
return true, nil
} }
if time.Now().Unix()*1000-start > duration { if time.Now().Unix()*1000-start > duration {
return false
return false, nil
} }
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
} }


return false
return false, nil
} }


func (lock *DistributeLock) UnLock() error { func (lock *DistributeLock) UnLock() error {


+ 32
- 18
services/reward/limiter/limiter.go View File

@@ -2,6 +2,7 @@ package limiter


import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"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/services/task/period" "code.gitea.io/gitea/services/task/period"
@@ -17,25 +18,27 @@ type limiterRunner struct {
userId int64 userId int64
amount int64 amount int64
limitCode string limitCode string
limitType models.LimitType
} }


func newLimiterRunner(limitCode string, userId, amount int64) *limiterRunner {
func newLimiterRunner(limitCode string, limitType models.LimitType, userId, amount int64) *limiterRunner {
return &limiterRunner{ return &limiterRunner{
userId: userId, userId: userId,
amount: amount, amount: amount,
limitCode: limitCode, limitCode: limitCode,
limitType: limitType,
index: 0, index: 0,
} }
} }


func (l *limiterRunner) Run() error { func (l *limiterRunner) Run() error {
if err := l.LoadLimiters(l.limitCode); err != nil {
if err := l.LoadLimiters(); err != nil {
return err return err
} }
//todo 验证未配置的情况
for l.index <= len(l.limiters) {
for l.index < len(l.limiters) {
err := l.limit(l.limiters[l.index]) err := l.limit(l.limiters[l.index])
if err != nil { if err != nil {
log.Info("limiter check failed,%v", err)
return err return err
} }
l.index += 1 l.index += 1
@@ -62,32 +65,43 @@ func (l *limiterRunner) limit(r models.LimitConfig) error {
return nil return nil
} }


func (l *limiterRunner) LoadLimiters(limitCode string) error {
redisKey := redis_key.LimitConfig(limitCode)
func (l *limiterRunner) LoadLimiters() error {
limiters, err := GetLimiters(l.limitCode, l.limitType)
if err != nil {
return err
}
if limiters != nil {
l.limiters = limiters
}
return nil
}

func CheckLimit(limitCode string, limitType models.LimitType, userId, amount int64) error {
r := newLimiterRunner(limitCode, limitType, userId, amount)
return r.Run()
}

func GetLimiters(limitCode string, limitType models.LimitType) ([]models.LimitConfig, error) {
redisKey := redis_key.LimitConfig(limitCode, limitType)
val, _ := redis_client.Get(redisKey) val, _ := redis_client.Get(redisKey)
if val != "" { if val != "" {
if val == redis_key.EMPTY_REDIS_VAL { if val == redis_key.EMPTY_REDIS_VAL {
return nil
return nil, nil
} }
limiters := make([]models.LimitConfig, 0) limiters := make([]models.LimitConfig, 0)
json.Unmarshal([]byte(val), limiters)
return nil
json.Unmarshal([]byte(val), &limiters)
return limiters, nil
} }
limiters, err := models.GetLimitConfigByLimitCode(limitCode)
limiters, err := models.GetLimitConfigByLimitCode(limitCode, limitType)
if err != nil { if err != nil {
if models.IsErrRecordNotExist(err) { if models.IsErrRecordNotExist(err) {
redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second) redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second)
return nil
return nil, nil
} }
return err
return nil, err
} }
jsonStr, _ := json.Marshal(limiters) jsonStr, _ := json.Marshal(limiters)
redis_client.Setex(redisKey, string(jsonStr), 30*24*time.Hour) redis_client.Setex(redisKey, string(jsonStr), 30*24*time.Hour)


return nil
}

func CheckLimit(limitCode string, userId, amount int64) error {
r := newLimiterRunner(limitCode, userId, amount)
return r.Run()
return limiters, nil
} }

+ 33
- 28
services/reward/operator.go View File

@@ -5,6 +5,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_key" "code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock" "code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/reward/point" "code.gitea.io/gitea/services/reward/point"
"errors" "errors"
"fmt" "fmt"
@@ -15,26 +16,25 @@ var RewardOperatorMap = map[string]RewardOperator{
fmt.Sprint(models.RewardTypePoint): new(point.PointOperator), fmt.Sprint(models.RewardTypePoint): new(point.PointOperator),
} }


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

type RewardOperator interface { type RewardOperator interface {
IsLimited(ctx RewardOperateContext) bool
Operate(ctx RewardOperateContext) error
IsLimited(ctx models.RewardOperateContext) bool
Operate(ctx models.RewardOperateContext) error
} }


func Send(ctx RewardOperateContext) error {
func Send(ctx models.RewardOperateContext) error {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
//add lock //add lock
var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardSendLock(ctx.RequestId, ctx.SourceType.String()))
if !rewardLock.Lock(3 * time.Second) {
var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardSendLock(ctx.RequestId, ctx.SourceType))
isOk, err := rewardLock.Lock(3 * time.Second)
if err != nil {
return err
}
if !isOk {
log.Info("duplicated reward request,targetUserId=%d requestId=%s", ctx.TargetUserId, ctx.RequestId) log.Info("duplicated reward request,targetUserId=%d requestId=%s", ctx.TargetUserId, ctx.RequestId)
return nil return nil
} }
@@ -63,19 +63,22 @@ func Send(ctx RewardOperateContext) error {
} }


//new reward operate record //new reward operate record
if err := initAwardOperateRecord(ctx); err != nil {
recordId, err := initAwardOperateRecord(ctx)
if err != nil {
return err return err
} }


ctx.SourceId = recordId

//operate //operate
if err := operator.Operate(ctx); err != nil { if err := operator.Operate(ctx); err != nil {
updateAwardOperateRecordStatus(ctx.SourceType.String(), ctx.RequestId, models.OperateStatusOperating, models.OperateStatusFailed)
updateAwardOperateRecordStatus(ctx.SourceType, ctx.RequestId, models.OperateStatusOperating, models.OperateStatusFailed)
return err return err
} }


//if not a cycle operate,update status to success //if not a cycle operate,update status to success
if ctx.CycleIntervalSeconds > 0 {
updateAwardOperateRecordStatus(ctx.SourceType.String(), ctx.RequestId, models.OperateStatusOperating, models.OperateStatusSucceeded)
if ctx.CycleIntervalSeconds == 0 {
updateAwardOperateRecordStatus(ctx.SourceType, ctx.RequestId, models.OperateStatusOperating, models.OperateStatusSucceeded)
} }
return nil return nil
} }
@@ -84,8 +87,8 @@ func GetOperator(rewardType string) RewardOperator {
return RewardOperatorMap[rewardType] return RewardOperatorMap[rewardType]
} }


func isHandled(sourceType models.RewardSourceType, requestId string) (bool, error) {
_, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType.String(), requestId)
func isHandled(sourceType string, requestId string) (bool, error) {
_, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId)
if err != nil { if err != nil {
if models.IsErrRecordNotExist(err) { if models.IsErrRecordNotExist(err) {
return false, nil return false, nil
@@ -96,23 +99,25 @@ func isHandled(sourceType models.RewardSourceType, requestId string) (bool, erro


} }


func initAwardOperateRecord(ctx RewardOperateContext) error {
_, err := models.InsertAwardOperateRecord(&models.RewardOperateRecord{
func initAwardOperateRecord(ctx models.RewardOperateContext) (string, error) {
record := &models.RewardOperateRecord{
RecordId: util.UUID(),
UserId: ctx.TargetUserId, UserId: ctx.TargetUserId,
Amount: ctx.Reward.Amount, Amount: ctx.Reward.Amount,
RewardType: ctx.Reward.Type, RewardType: ctx.Reward.Type,
SourceType: ctx.SourceType.String(),
SourceType: ctx.SourceType,
SourceId: ctx.SourceId, SourceId: ctx.SourceId,
RequestId: ctx.RequestId, RequestId: ctx.RequestId,
OperateType: ctx.OperateType, OperateType: ctx.OperateType,
CycleIntervalSeconds: ctx.CycleIntervalSeconds, CycleIntervalSeconds: ctx.CycleIntervalSeconds,
Status: models.OperateStatusOperating, Status: models.OperateStatusOperating,
Remark: ctx.Remark, Remark: ctx.Remark,
})
}
_, err := models.InsertAwardOperateRecord(record)
if err != nil { if err != nil {
return err
return "", err
} }
return nil
return record.RecordId, nil
} }


func updateAwardOperateRecordStatus(sourceType, requestId, oldStatus, newStatus string) error { func updateAwardOperateRecordStatus(sourceType, requestId, oldStatus, newStatus string) error {


+ 6
- 2
services/reward/point/account/point_account.go View File

@@ -11,7 +11,7 @@ import (
) )


func GetAccount(userId int64) (*models.PointAccount, error) { func GetAccount(userId int64) (*models.PointAccount, error) {
redisKey := redis_key.PointAccountDetail(userId)
redisKey := redis_key.PointAccountInfo(userId)
val, _ := redis_client.Get(redisKey) val, _ := redis_client.Get(redisKey)
if val != "" { if val != "" {
account := &models.PointAccount{} account := &models.PointAccount{}
@@ -36,7 +36,11 @@ func GetAccount(userId int64) (*models.PointAccount, error) {


func InitAccount(userId int64) (*models.PointAccount, error) { func InitAccount(userId int64) (*models.PointAccount, error) {
lock := redis_lock.NewDistributeLock(redis_key.PointAccountInitLock(userId)) lock := redis_lock.NewDistributeLock(redis_key.PointAccountInitLock(userId))
if lock.LockWithWait(3*time.Second, 3*time.Second) {
isOk, err := lock.LockWithWait(3*time.Second, 3*time.Second)
if err != nil {
return nil, err
}
if isOk {
defer lock.UnLock() defer lock.UnLock()
account, _ := models.GetAccountByUserId(userId) account, _ := models.GetAccountByUserId(userId)
if account == nil { if account == nil {


+ 17
- 9
services/reward/point/point_operate.go View File

@@ -2,9 +2,9 @@ package point


import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"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" "code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/services/reward"
"code.gitea.io/gitea/services/reward/limiter" "code.gitea.io/gitea/services/reward/limiter"
"code.gitea.io/gitea/services/reward/point/account" "code.gitea.io/gitea/services/reward/point/account"
"errors" "errors"
@@ -14,28 +14,36 @@ import (
type PointOperator struct { type PointOperator struct {
} }


func (operator *PointOperator) IsLimited(ctx reward.RewardOperateContext) bool {
if err := limiter.CheckLimit(ctx.Reward.Type, ctx.TargetUserId, ctx.Reward.Amount); err != nil {
return false
func (operator *PointOperator) IsLimited(ctx models.RewardOperateContext) bool {
if err := limiter.CheckLimit(ctx.Reward.Type, models.LimitTypeReward, ctx.TargetUserId, ctx.Reward.Amount); err != nil {
return true
} }
return true
return false
} }


func (operator *PointOperator) Operate(ctx reward.RewardOperateContext) error {
func (operator *PointOperator) Operate(ctx models.RewardOperateContext) error {
a, err := account.GetAccount(ctx.TargetUserId) a, err := account.GetAccount(ctx.TargetUserId)
if err != nil || a == nil { if err != nil || a == nil {
return errors.New("get account error") return errors.New("get account error")
} }


lock := redis_lock.NewDistributeLock(redis_key.PointAccountOperateLock(a.AccountCode)) lock := redis_lock.NewDistributeLock(redis_key.PointAccountOperateLock(a.AccountCode))
if lock.LockWithWait(3*time.Second, 3*time.Second) {
isOk, err := lock.LockWithWait(3*time.Second, 3*time.Second)
if err != nil {
return err
}
if isOk {
defer lock.UnLock() defer lock.UnLock()
na, _ := account.GetAccount(ctx.TargetUserId) na, _ := account.GetAccount(ctx.TargetUserId)
if ctx.OperateType == models.OperateTypeIncrease { if ctx.OperateType == models.OperateTypeIncrease {
na.Increase(ctx.Reward.Amount, ctx.SourceId)
err = na.Increase(ctx.Reward.Amount, ctx.SourceId)
} else if ctx.OperateType == models.OperateTypeDecrease { } else if ctx.OperateType == models.OperateTypeDecrease {
na.Decrease(ctx.Reward.Amount, ctx.SourceId)
err = na.Decrease(ctx.Reward.Amount, ctx.SourceId)
}
if err != nil {
return err
} }
redis_client.Del(redis_key.PointAccountInfo(ctx.TargetUserId))


} else { } else {
return errors.New("Get account operate lock failed") return errors.New("Get account operate lock failed")


+ 0
- 6
services/reward/reward.go View File

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

type Reward struct {
Amount int64
Type string
}

+ 27
- 24
services/task/task.go View File

@@ -7,18 +7,30 @@ import (
"code.gitea.io/gitea/modules/redis/redis_lock" "code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/reward" "code.gitea.io/gitea/services/reward"
"code.gitea.io/gitea/services/task/period"
"code.gitea.io/gitea/services/reward/limiter"
"fmt"
"time" "time"
) )


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


func accomplish(userId int64, taskType models.TaskType, sourceId string) error {
func accomplish(userId int64, taskType string, sourceId string) error {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
//lock //lock
var taskLock = redis_lock.NewDistributeLock(redis_key.TaskAccomplishLock(sourceId, taskType)) var taskLock = redis_lock.NewDistributeLock(redis_key.TaskAccomplishLock(sourceId, taskType))
if !taskLock.Lock(3 * time.Second) {
isOk, err := taskLock.Lock(3 * time.Second)
if err != nil {
log.Error("get taskLock error. %v", err)
return err
}
if !isOk {
log.Info("duplicated task request,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId) log.Info("duplicated task request,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId)
return nil return nil
} }
@@ -47,12 +59,7 @@ func accomplish(userId int64, taskType models.TaskType, sourceId string) error {
} }


//is limited? //is limited?
isLimited, err := IsLimited(userId, config)
if err != nil {
log.Error("get limited error,%v", err)
return err
}
if isLimited {
if isLimited(userId, config) {
log.Info("task accomplish maximum times are reached,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId) log.Info("task accomplish maximum times are reached,userId=%d taskType=%s sourceId=%s", userId, taskType, sourceId)
return nil return nil
} }
@@ -71,22 +78,23 @@ func accomplish(userId int64, taskType models.TaskType, sourceId string) error {
} }


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


return nil return nil
} }


func isHandled(taskType models.TaskType, sourceId string) (bool, error) {
_, err := models.GetTaskAccomplishLogBySourceIdAndTaskCode(sourceId, taskType.String())
func isHandled(taskType string, sourceId string) (bool, error) {
_, err := models.GetTaskAccomplishLogBySourceIdAndTaskCode(sourceId, taskType)
if err != nil { if err != nil {
if models.IsErrRecordNotExist(err) { if models.IsErrRecordNotExist(err) {
return false, nil return false, nil
@@ -97,15 +105,10 @@ func isHandled(taskType models.TaskType, sourceId string) (bool, error) {


} }


func IsLimited(userId int64, config *models.TaskConfig) (bool, error) {
p, err := period.GetPeriod(config.RefreshRate)
if err != nil {
return false, err
}
n, err := models.CountInTaskPeriod(config.ID, userId, p)
if err != nil {
return false, err
func isLimited(userId int64, config *models.TaskConfig) bool {
if err := limiter.CheckLimit(config.TaskCode, models.LimitTypeTask, userId, 1); err != nil {
return true
} }
return n >= config.Times, nil
return false


} }

+ 2
- 2
services/task/task_config.go View File

@@ -10,7 +10,7 @@ import (


//GetTaskConfig get task config from redis cache first //GetTaskConfig get task config from redis cache first
// if not exist in redis, find in db and refresh the redis key // if not exist in redis, find in db and refresh the redis key
func GetTaskConfig(taskType models.TaskType) (*models.TaskConfig, error) {
func GetTaskConfig(taskType string) (*models.TaskConfig, error) {
redisKey := redis_key.TaskConfig(taskType) redisKey := redis_key.TaskConfig(taskType)
configStr, _ := redis_client.Get(redisKey) configStr, _ := redis_client.Get(redisKey)
if configStr != "" { if configStr != "" {
@@ -21,7 +21,7 @@ func GetTaskConfig(taskType models.TaskType) (*models.TaskConfig, error) {
json.Unmarshal([]byte(configStr), config) json.Unmarshal([]byte(configStr), config)
return config, nil return config, nil
} }
config, err := models.GetTaskConfigByTaskCode(taskType.String())
config, err := models.GetTaskConfigByTaskCode(taskType)
if err != nil { if err != nil {
if models.IsErrRecordNotExist(err) { if models.IsErrRecordNotExist(err) {
redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second) redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second)


Loading…
Cancel
Save