task.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. package daytask
  2. import (
  3. "app/commons/model/entity"
  4. "app/commons/services"
  5. "encoding/json"
  6. "time"
  7. "github.com/gin-gonic/gin"
  8. )
  9. // TaskApply 领取任务
  10. func (s *Server) TaskApply(c *gin.Context) {
  11. ctx := s.FromContext(c)
  12. db := s.DB()
  13. userId := ctx.UserId()
  14. type ApplyRequest struct {
  15. TaskId int64 `json:"taskId" binding:"required"`
  16. }
  17. var req ApplyRequest
  18. if err := c.ShouldBindJSON(&req); err != nil {
  19. ctx.Fail("invalid_params")
  20. return
  21. }
  22. // 获取任务
  23. task := &entity.DtTask{}
  24. if err := db.Where("id = ? AND status = ?", req.TaskId, 1).First(task).Error; err != nil {
  25. ctx.Fail("task_not_found")
  26. return
  27. }
  28. // 检查任务剩余数量
  29. if task.RemainCount <= 0 {
  30. ctx.Fail("task_sold_out")
  31. return
  32. }
  33. // 获取用户
  34. user := &entity.DtUser{}
  35. if err := db.Where("id = ?", userId).First(user).Error; err != nil {
  36. ctx.Fail("user_not_found")
  37. return
  38. }
  39. // 检查用户等级
  40. if task.MinLevel > 0 {
  41. level := &entity.DtUserLevel{}
  42. db.Where("id = ?", user.LevelId).First(level)
  43. if level.Level < task.MinLevel {
  44. ctx.Fail("level_not_enough")
  45. return
  46. }
  47. }
  48. // 检查用户今日领取数量
  49. today := time.Now().Format("2006-01-02")
  50. var todayCount int64
  51. db.Model(&entity.DtUserTask{}).
  52. Where("user_id = ? AND task_id = ? AND DATE(FROM_UNIXTIME(created_at)) = ?", userId, req.TaskId, today).
  53. Count(&todayCount)
  54. if int(todayCount) >= task.DailyLimit {
  55. ctx.Fail("daily_limit_reached")
  56. return
  57. }
  58. // 检查用户总领取数量
  59. var totalCount int64
  60. db.Model(&entity.DtUserTask{}).
  61. Where("user_id = ? AND task_id = ? AND status != ?", userId, req.TaskId, entity.UserTaskStatusAbandoned).
  62. Count(&totalCount)
  63. if int(totalCount) >= task.TotalLimit {
  64. ctx.Fail("total_limit_reached")
  65. return
  66. }
  67. // 检查是否有进行中或待审核的任务
  68. var pendingCount int64
  69. db.Model(&entity.DtUserTask{}).
  70. Where("user_id = ? AND task_id = ? AND status IN (?, ?)", userId, req.TaskId, entity.UserTaskStatusPending, entity.UserTaskStatusSubmitted).
  71. Count(&pendingCount)
  72. if pendingCount > 0 {
  73. ctx.Fail("task_in_progress")
  74. return
  75. }
  76. // 事务:创建任务记录 + 扣减剩余数量
  77. tx := db.Begin()
  78. // 先原子扣减剩余数量,防止超领
  79. result := tx.Model(&entity.DtTask{}).
  80. Where("id = ? AND remain_count > 0", req.TaskId).
  81. UpdateColumn("remain_count", tx.Raw("remain_count - 1"))
  82. if result.Error != nil || result.RowsAffected == 0 {
  83. tx.Rollback()
  84. ctx.Fail("task_sold_out")
  85. return
  86. }
  87. // 创建任务记录
  88. userTask := &entity.DtUserTask{
  89. UserId: userId,
  90. TaskId: req.TaskId,
  91. TaskNo: task.TaskNo,
  92. TaskTitle: task.Title,
  93. RewardAmount: task.RewardAmount,
  94. Status: entity.UserTaskStatusPending,
  95. }
  96. if err := tx.Create(userTask).Error; err != nil {
  97. tx.Rollback()
  98. ctx.Fail("claim_failed")
  99. return
  100. }
  101. tx.Commit()
  102. ctx.OK(userTask)
  103. }
  104. // TaskSubmit 提交任务
  105. func (s *Server) TaskSubmit(c *gin.Context) {
  106. ctx := s.FromContext(c)
  107. db := s.DB()
  108. userId := ctx.UserId()
  109. type SubmitRequest struct {
  110. UserTaskId int64 `json:"userTaskId" binding:"required"`
  111. Screenshots []string `json:"screenshots" binding:"required"`
  112. Remark string `json:"remark"`
  113. }
  114. var req SubmitRequest
  115. if err := c.ShouldBindJSON(&req); err != nil {
  116. ctx.Fail("invalid_params")
  117. return
  118. }
  119. // 获取用户任务
  120. userTask := &entity.DtUserTask{}
  121. if err := db.Where("id = ? AND user_id = ?", req.UserTaskId, userId).First(userTask).Error; err != nil {
  122. ctx.Fail("user_task_not_found")
  123. return
  124. }
  125. // 检查状态
  126. if userTask.Status != entity.UserTaskStatusPending {
  127. ctx.Fail("task_cannot_submit")
  128. return
  129. }
  130. // 获取任务检查是否需要截图
  131. task := &entity.DtTask{}
  132. db.Where("id = ?", userTask.TaskId).First(task)
  133. if task.RequireScreenshot == 1 && len(req.Screenshots) == 0 {
  134. ctx.Fail("screenshot_required")
  135. return
  136. }
  137. // 更新任务
  138. screenshotsJson, _ := json.Marshal(req.Screenshots)
  139. db.Model(&entity.DtUserTask{}).
  140. Where("id = ?", req.UserTaskId).
  141. Updates(map[string]interface{}{
  142. "screenshots": string(screenshotsJson),
  143. "remark": req.Remark,
  144. "submit_time": time.Now().Unix(),
  145. "status": entity.UserTaskStatusSubmitted,
  146. })
  147. ctx.OK(nil)
  148. }
  149. // TaskAbandon 放弃任务
  150. func (s *Server) TaskAbandon(c *gin.Context) {
  151. ctx := s.FromContext(c)
  152. db := s.DB()
  153. userId := ctx.UserId()
  154. type AbandonRequest struct {
  155. UserTaskId int64 `json:"userTaskId" binding:"required"`
  156. }
  157. var req AbandonRequest
  158. if err := c.ShouldBindJSON(&req); err != nil {
  159. ctx.Fail("invalid_params")
  160. return
  161. }
  162. // 获取用户任务
  163. userTask := &entity.DtUserTask{}
  164. if err := db.Where("id = ? AND user_id = ?", req.UserTaskId, userId).First(userTask).Error; err != nil {
  165. ctx.Fail("user_task_not_found")
  166. return
  167. }
  168. // 只有进行中的任务可以放弃
  169. if userTask.Status != entity.UserTaskStatusPending {
  170. ctx.Fail("task_cannot_abandon")
  171. return
  172. }
  173. // 事务:更新状态 + 恢复数量
  174. tx := db.Begin()
  175. tx.Model(&entity.DtUserTask{}).
  176. Where("id = ?", req.UserTaskId).
  177. Update("status", entity.UserTaskStatusAbandoned)
  178. tx.Model(&entity.DtTask{}).
  179. Where("id = ?", userTask.TaskId).
  180. UpdateColumn("remain_count", tx.Raw("remain_count + 1"))
  181. tx.Commit()
  182. ctx.OK(nil)
  183. }
  184. // TaskMy 我的任务列表
  185. func (s *Server) TaskMy(c *gin.Context) {
  186. ctx := s.FromContext(c)
  187. db := s.DB()
  188. userId := ctx.UserId()
  189. status := ctx.QueryInt64("status", -99) // -99表示全部
  190. paging := &services.Pagination{
  191. Current: ctx.QueryInt64("current", 1),
  192. Size: ctx.QueryInt64("size", 20),
  193. }
  194. query := db.Model(&entity.DtUserTask{}).Where("user_id = ?", userId)
  195. if status != -99 {
  196. query = query.Where("status = ?", status)
  197. }
  198. query.Count(&paging.Total)
  199. paging.Computer()
  200. userTasks := make([]*entity.DtUserTask, 0)
  201. query.Order("created_at DESC").
  202. Offset(int(paging.Start)).
  203. Limit(int(paging.Size)).
  204. Find(&userTasks)
  205. // 批量获取关联的任务信息(避免N+1查询)
  206. type TaskWithInfo struct {
  207. *entity.DtUserTask
  208. Task *entity.DtTask `json:"task"`
  209. }
  210. // 收集所有 taskId
  211. taskIds := make([]int64, 0, len(userTasks))
  212. for _, ut := range userTasks {
  213. taskIds = append(taskIds, ut.TaskId)
  214. }
  215. // 一次性查询所有任务
  216. taskMap := make(map[int64]*entity.DtTask)
  217. if len(taskIds) > 0 {
  218. tasks := make([]*entity.DtTask, 0)
  219. db.Where("id IN ?", taskIds).Find(&tasks)
  220. for _, t := range tasks {
  221. taskMap[t.Id] = t
  222. }
  223. }
  224. result := make([]*TaskWithInfo, 0)
  225. for _, ut := range userTasks {
  226. task := taskMap[ut.TaskId]
  227. if task == nil {
  228. task = &entity.DtTask{}
  229. }
  230. result = append(result, &TaskWithInfo{
  231. DtUserTask: ut,
  232. Task: task,
  233. })
  234. }
  235. ctx.OK(gin.H{
  236. "list": result,
  237. "paging": paging,
  238. })
  239. }
  240. // TaskMyDetail 我的任务详情
  241. func (s *Server) TaskMyDetail(c *gin.Context) {
  242. ctx := s.FromContext(c)
  243. db := s.DB()
  244. userId := ctx.UserId()
  245. userTaskId := ctx.QueryInt64("id", 0)
  246. if userTaskId == 0 {
  247. ctx.Fail("id_required")
  248. return
  249. }
  250. // 获取用户任务
  251. userTask := &entity.DtUserTask{}
  252. if err := db.Where("id = ? AND user_id = ?", userTaskId, userId).First(userTask).Error; err != nil {
  253. ctx.Fail("user_task_not_found")
  254. return
  255. }
  256. // 获取任务
  257. task := &entity.DtTask{}
  258. db.Where("id = ?", userTask.TaskId).First(task)
  259. // 获取任务分类
  260. category := &entity.DtTaskCategory{}
  261. db.Where("id = ?", task.CategoryId).First(category)
  262. // 获取任务步骤
  263. steps := make([]*entity.DtTaskStep, 0)
  264. db.Model(&entity.DtTaskStep{}).
  265. Where("task_id = ?", userTask.TaskId).
  266. Order("step_no ASC").
  267. Find(&steps)
  268. ctx.OK(gin.H{
  269. "userTask": userTask,
  270. "task": task,
  271. "category": category,
  272. "steps": steps,
  273. })
  274. }