You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

operator.go 8.4 kB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package reward
  2. import (
  3. "code.gitea.io/gitea/models"
  4. "code.gitea.io/gitea/modules/log"
  5. "code.gitea.io/gitea/modules/redis/redis_key"
  6. "code.gitea.io/gitea/modules/redis/redis_lock"
  7. "code.gitea.io/gitea/services/reward/point"
  8. "errors"
  9. "fmt"
  10. "time"
  11. )
  12. var RewardOperatorMap = map[string]RewardOperator{
  13. fmt.Sprint(models.RewardTypePoint): new(point.PointOperator),
  14. }
  15. type RewardOperator interface {
  16. IsLimited(ctx *models.RewardOperateContext) error
  17. Operate(ctx *models.RewardOperateContext) error
  18. }
  19. func Operate(ctx *models.RewardOperateContext) error {
  20. defer func() {
  21. if err := recover(); err != nil {
  22. combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
  23. log.Error("PANIC:%v", combinedErr)
  24. }
  25. }()
  26. if !checkRewardOperationParam(ctx) {
  27. log.Error("send reward error,param incorrect")
  28. return errors.New("param incorrect")
  29. }
  30. //add lock
  31. var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(ctx.RequestId, ctx.SourceType.Name(), ctx.OperateType.Name()))
  32. isOk, err := rewardLock.Lock(3 * time.Second)
  33. if err != nil {
  34. return err
  35. }
  36. if !isOk {
  37. log.Info("duplicated reward request,targetUserId=%d requestId=%s", ctx.TargetUserId, ctx.RequestId)
  38. return nil
  39. }
  40. defer rewardLock.UnLock()
  41. //is handled before?
  42. isHandled, err := isHandled(ctx.SourceType.Name(), ctx.RequestId, ctx.OperateType.Name())
  43. if err != nil {
  44. log.Error("reward is handled error,%v", err)
  45. return err
  46. }
  47. if isHandled {
  48. log.Info("reward has been handled,ctx=%+v", ctx)
  49. return nil
  50. }
  51. //get operator
  52. operator := GetOperator(ctx.Reward.Type)
  53. if operator == nil {
  54. log.Error("operator of reward type is not exist,ctx=%v", ctx)
  55. return errors.New("operator of reward type is not exist")
  56. }
  57. if ctx.OperateType == models.OperateTypeIncrease {
  58. //is limited?
  59. if err := operator.IsLimited(ctx); err != nil {
  60. log.Info("operator IsLimited, err=%v", err)
  61. return err
  62. }
  63. }
  64. //new reward operate record
  65. recordId, err := initRewardOperateRecord(ctx)
  66. if err != nil {
  67. log.Error("initRewardOperateRecord error,err=%v", err)
  68. return err
  69. }
  70. ctx.SourceId = recordId
  71. //operate
  72. if err := operator.Operate(ctx); err != nil {
  73. log.Error("operator Operate error,err=%v", err)
  74. UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusFailed)
  75. return err
  76. }
  77. UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusSucceeded)
  78. NotifyRewardOperation(ctx.TargetUserId, ctx.Reward.Amount, ctx.SourceType, ctx.Reward.Type, ctx.OperateType)
  79. return nil
  80. }
  81. func checkRewardOperationParam(ctx *models.RewardOperateContext) bool {
  82. if ctx.Reward.Type == "" {
  83. return false
  84. }
  85. return true
  86. }
  87. func GetOperator(rewardType models.RewardType) RewardOperator {
  88. return RewardOperatorMap[rewardType.Name()]
  89. }
  90. func isHandled(sourceType string, requestId string, operateType string) (bool, error) {
  91. _, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType)
  92. if err != nil {
  93. log.Error("operator isHandled error. %v", err)
  94. if models.IsErrRecordNotExist(err) {
  95. return false, nil
  96. }
  97. log.Error("GetPointOperateRecordBySourceTypeAndRequestId ZRangeByScore error. %v", err)
  98. return false, err
  99. }
  100. return true, nil
  101. }
  102. func initRewardOperateRecord(ctx *models.RewardOperateContext) (string, error) {
  103. sn, err := generateOperateSerialNo()
  104. if err != nil {
  105. log.Error("generateOperateSerialNo error. %v", err)
  106. return "", err
  107. }
  108. record := &models.RewardOperateRecord{
  109. UserId: ctx.TargetUserId,
  110. Amount: ctx.Reward.Amount,
  111. LossAmount: ctx.LossAmount,
  112. RewardType: ctx.Reward.Type.Name(),
  113. SourceType: ctx.SourceType.Name(),
  114. SourceId: ctx.SourceId,
  115. SourceTemplateId: ctx.SourceTemplateId,
  116. RequestId: ctx.RequestId,
  117. OperateType: ctx.OperateType.Name(),
  118. Status: models.OperateStatusOperating,
  119. Remark: ctx.Remark,
  120. Title: ctx.Title,
  121. SerialNo: sn,
  122. }
  123. _, err = models.InsertRewardOperateRecord(record)
  124. if err != nil {
  125. log.Error("InsertRewardOperateRecord error. %v", err)
  126. return "", err
  127. }
  128. return record.SerialNo, nil
  129. }
  130. func createPeriodicRewardOperateRecord(ctx *models.StartPeriodicTaskOpts) (string, error) {
  131. sn, err := generateOperateSerialNo()
  132. if err != nil {
  133. log.Error("createPeriodic generateOperateSerialNo error. %v", err)
  134. return "", err
  135. }
  136. record := &models.RewardOperateRecord{
  137. UserId: ctx.TargetUserId,
  138. Amount: 0,
  139. RewardType: ctx.RewardType.Name(),
  140. SourceType: ctx.SourceType.Name(),
  141. SourceId: ctx.SourceId,
  142. RequestId: ctx.RequestId,
  143. OperateType: ctx.OperateType.Name(),
  144. Status: models.OperateStatusOperating,
  145. Remark: ctx.Remark,
  146. Title: ctx.Title,
  147. SerialNo: sn,
  148. }
  149. _, err = models.InsertRewardOperateRecord(record)
  150. if err != nil {
  151. log.Error("createPeriodic InsertRewardOperateRecord error. %v", err)
  152. return "", err
  153. }
  154. return record.SerialNo, nil
  155. }
  156. func UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus string) error {
  157. _, err := models.UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus)
  158. if err != nil {
  159. log.Error("UpdateRewardRecord UpdateRewardRecordToFinalStatus error. %v", err)
  160. return err
  161. }
  162. return nil
  163. }
  164. func GetPeriodicTask(sourceType models.SourceType, sourceId, requestId string, operateType models.RewardOperateType) (*models.RewardPeriodicTask, error) {
  165. _, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType.Name(), requestId, operateType.Name())
  166. if err == nil {
  167. task, err := models.GetPeriodicTaskBySourceIdAndType(sourceType, sourceId, operateType)
  168. if err != nil {
  169. log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err)
  170. return nil, err
  171. }
  172. return task, nil
  173. }
  174. if err != nil && !models.IsErrRecordNotExist(err) {
  175. log.Error("GetPointOperateRecordBySourceTypeAndRequestId error,%v", err)
  176. return nil, err
  177. }
  178. return nil, nil
  179. }
  180. func StartPeriodicTask(opts *models.StartPeriodicTaskOpts) (*models.RewardPeriodicTask, error) {
  181. //add lock
  182. var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(opts.RequestId, opts.SourceType.Name(), opts.OperateType.Name()))
  183. isOk, err := rewardLock.Lock(3 * time.Second)
  184. if !isOk {
  185. log.Info("duplicated operate request,targetUserId=%d requestId=%s", opts.TargetUserId, opts.RequestId)
  186. return nil, nil
  187. }
  188. defer rewardLock.UnLock()
  189. r, err := GetPeriodicTask(opts.SourceType, opts.SourceId, opts.RequestId, opts.OperateType)
  190. if err != nil {
  191. return nil, err
  192. }
  193. if r != nil {
  194. return r, nil
  195. }
  196. //new reward operate record
  197. recordId, err := createPeriodicRewardOperateRecord(opts)
  198. if err != nil {
  199. log.Error("StartAndGetPeriodicTask createPeriodicRewardOperateRecord error. %v", err)
  200. return nil, err
  201. }
  202. if err = NewRewardPeriodicTask(recordId, opts); err != nil {
  203. log.Error("StartAndGetPeriodicTask NewRewardPeriodicTask error. %v", err)
  204. UpdateRewardRecordToFinalStatus(opts.SourceType.Name(), opts.RequestId, models.OperateStatusFailed)
  205. return nil, err
  206. }
  207. task, err := models.GetPeriodicTaskBySourceIdAndType(opts.SourceType, opts.SourceId, opts.OperateType)
  208. if err != nil {
  209. log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err)
  210. return nil, err
  211. }
  212. return task, nil
  213. }
  214. func StopPeriodicTaskAsyn(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) {
  215. go StopPeriodicTask(sourceType, sourceId, operateType)
  216. }
  217. func StopPeriodicTask(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) error {
  218. defer func() {
  219. if err := recover(); err != nil {
  220. combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
  221. log.Error("PANIC:%v", combinedErr)
  222. }
  223. }()
  224. task, err := models.GetPeriodicTaskBySourceIdAndType(sourceType, sourceId, operateType)
  225. if err != nil {
  226. log.Error("StopPeriodicTask. GetPeriodicTaskBySourceIdAndType error. %v", err)
  227. return err
  228. }
  229. if task == nil {
  230. log.Info("Periodic task is not exist")
  231. return nil
  232. }
  233. if task.Status == models.PeriodicTaskStatusFinished {
  234. log.Info("Periodic task is finished")
  235. return nil
  236. }
  237. now := time.Now()
  238. RunRewardTask(*task, now)
  239. return models.StopPeriodicTask(task.ID, task.OperateSerialNo, now)
  240. }
  241. func generateOperateSerialNo() (string, error) {
  242. s, err := GetSerialNoByRedis()
  243. if err != nil {
  244. log.Error("generateOperateSerialNo error. %v", err)
  245. return "", err
  246. }
  247. return s, nil
  248. }