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 7.7 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
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
3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. return errors.New("operator of reward type is not exist")
  55. }
  56. if ctx.OperateType == models.OperateTypeIncrease {
  57. //is limited?
  58. if err := operator.IsLimited(ctx); err != nil {
  59. log.Info("operator IsLimited, err=%v", err)
  60. return err
  61. }
  62. }
  63. //new reward operate record
  64. recordId, err := initRewardOperateRecord(ctx)
  65. if err != nil {
  66. return err
  67. }
  68. ctx.SourceId = recordId
  69. //operate
  70. if err := operator.Operate(ctx); err != nil {
  71. UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusFailed)
  72. return err
  73. }
  74. UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusSucceeded)
  75. NotifyRewardOperation(ctx.TargetUserId, ctx.Reward.Amount, ctx.Reward.Type, ctx.OperateType)
  76. return nil
  77. }
  78. func checkRewardOperationParam(ctx *models.RewardOperateContext) bool {
  79. if ctx.Reward.Type == "" {
  80. return false
  81. }
  82. return true
  83. }
  84. func GetOperator(rewardType models.RewardType) RewardOperator {
  85. return RewardOperatorMap[rewardType.Name()]
  86. }
  87. func isHandled(sourceType string, requestId string, operateType string) (bool, error) {
  88. _, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType)
  89. if err != nil {
  90. if models.IsErrRecordNotExist(err) {
  91. return false, nil
  92. }
  93. return false, err
  94. }
  95. return true, nil
  96. }
  97. func initRewardOperateRecord(ctx *models.RewardOperateContext) (string, error) {
  98. sn, err := generateOperateSerialNo(ctx.OperateType, ctx.Reward.Type)
  99. if err != nil {
  100. return "", err
  101. }
  102. record := &models.RewardOperateRecord{
  103. UserId: ctx.TargetUserId,
  104. Amount: ctx.Reward.Amount,
  105. RewardType: ctx.Reward.Type.Name(),
  106. SourceType: ctx.SourceType.Name(),
  107. SourceId: ctx.SourceId,
  108. RequestId: ctx.RequestId,
  109. OperateType: ctx.OperateType.Name(),
  110. Status: models.OperateStatusOperating,
  111. Remark: ctx.Remark,
  112. Tittle: ctx.Tittle,
  113. SerialNo: sn,
  114. }
  115. _, err = models.InsertRewardOperateRecord(record)
  116. if err != nil {
  117. return "", err
  118. }
  119. return record.SerialNo, nil
  120. }
  121. func createPeriodicRewardOperateRecord(ctx *models.StartPeriodicTaskOpts) (string, error) {
  122. sn, err := generateOperateSerialNo(ctx.OperateType, ctx.RewardType)
  123. if err != nil {
  124. return "", err
  125. }
  126. record := &models.RewardOperateRecord{
  127. UserId: ctx.TargetUserId,
  128. Amount: 0,
  129. RewardType: ctx.RewardType.Name(),
  130. SourceType: ctx.SourceType.Name(),
  131. SourceId: ctx.SourceId,
  132. RequestId: ctx.RequestId,
  133. OperateType: ctx.OperateType.Name(),
  134. Status: models.OperateStatusOperating,
  135. Remark: ctx.Remark,
  136. Tittle: ctx.Tittle,
  137. SerialNo: sn,
  138. }
  139. _, err = models.InsertRewardOperateRecord(record)
  140. if err != nil {
  141. return "", err
  142. }
  143. return record.SerialNo, nil
  144. }
  145. func UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus string) error {
  146. _, err := models.UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus)
  147. if err != nil {
  148. return err
  149. }
  150. return nil
  151. }
  152. func StartPeriodicTaskAsyn(opts *models.StartPeriodicTaskOpts) {
  153. go StartAndGetPeriodicTask(opts)
  154. }
  155. func StartAndGetPeriodicTask(opts *models.StartPeriodicTaskOpts) (*models.RewardPeriodicTask, error) {
  156. defer func() {
  157. if err := recover(); err != nil {
  158. combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
  159. log.Error("PANIC:%v", combinedErr)
  160. }
  161. }()
  162. //add lock
  163. var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(opts.RequestId, opts.SourceType.Name(), opts.OperateType.Name()))
  164. isOk, err := rewardLock.Lock(3 * time.Second)
  165. if err != nil {
  166. return nil, err
  167. }
  168. if !isOk {
  169. log.Info("duplicated operate request,targetUserId=%d requestId=%s", opts.TargetUserId, opts.RequestId)
  170. return nil, nil
  171. }
  172. defer rewardLock.UnLock()
  173. _, err = models.GetPointOperateRecordBySourceTypeAndRequestId(opts.SourceType.Name(), opts.RequestId, opts.OperateType.Name())
  174. if err == nil {
  175. task, err := models.GetPeriodicTaskBySourceIdAndType(opts.SourceType, opts.SourceId, opts.OperateType)
  176. if err != nil {
  177. log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err)
  178. return nil, err
  179. }
  180. return task, nil
  181. }
  182. if err != nil && !models.IsErrRecordNotExist(err) {
  183. log.Error("operate is handled error,%v", err)
  184. return nil, err
  185. }
  186. //new reward operate record
  187. recordId, err := createPeriodicRewardOperateRecord(opts)
  188. if err != nil {
  189. return nil, err
  190. }
  191. if err = NewRewardPeriodicTask(recordId, opts); err != nil {
  192. UpdateRewardRecordToFinalStatus(opts.SourceType.Name(), opts.RequestId, models.OperateStatusFailed)
  193. return nil, err
  194. }
  195. task, err := models.GetPeriodicTaskBySourceIdAndType(opts.SourceType, opts.SourceId, opts.OperateType)
  196. if err != nil {
  197. log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err)
  198. return nil, err
  199. }
  200. return task, nil
  201. }
  202. func StopPeriodicTaskAsyn(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) {
  203. go StopPeriodicTask(sourceType, sourceId, operateType)
  204. }
  205. func StopPeriodicTask(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) error {
  206. defer func() {
  207. if err := recover(); err != nil {
  208. combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
  209. log.Error("PANIC:%v", combinedErr)
  210. }
  211. }()
  212. task, err := models.GetPeriodicTaskBySourceIdAndType(sourceType, sourceId, operateType)
  213. if err != nil {
  214. log.Error("StopPeriodicTask. GetPeriodicTaskBySourceIdAndType error. %v", err)
  215. return err
  216. }
  217. if task == nil {
  218. log.Info("Periodic task is not exist")
  219. return nil
  220. }
  221. if task.Status == models.PeriodicTaskStatusFinished {
  222. log.Info("Periodic task is finished")
  223. return nil
  224. }
  225. now := time.Now()
  226. RunRewardTask(*task, now)
  227. return models.StopPeriodicTask(task.ID, task.OperateSerialNo, now)
  228. }
  229. func generateOperateSerialNo(operateType models.RewardOperateType, rewardType models.RewardType) (string, error) {
  230. s, err := GetSerialNoByRedis()
  231. if err != nil {
  232. return "", err
  233. }
  234. switch operateType {
  235. case models.OperateTypeIncrease:
  236. s += "1"
  237. case models.OperateTypeDecrease:
  238. s += "2"
  239. default:
  240. s += "9"
  241. }
  242. switch rewardType {
  243. case models.RewardTypePoint:
  244. s += "1"
  245. default:
  246. s += "9"
  247. }
  248. return s, nil
  249. }