task.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. // 检查用户今日领取数量(0表示不限制)
  49. if task.DailyLimit > 0 {
  50. today := time.Now().Format("2006-01-02")
  51. var todayCount int64
  52. db.Model(&entity.DtUserTask{}).
  53. Where("user_id = ? AND task_id = ? AND DATE(FROM_UNIXTIME(created_at)) = ?", userId, req.TaskId, today).
  54. Count(&todayCount)
  55. if int(todayCount) >= task.DailyLimit {
  56. ctx.Fail("daily_limit_reached")
  57. return
  58. }
  59. }
  60. // 检查用户总领取数量(0表示不限制)
  61. if task.TotalLimit > 0 {
  62. var totalCount int64
  63. db.Model(&entity.DtUserTask{}).
  64. Where("user_id = ? AND task_id = ? AND status != ?", userId, req.TaskId, entity.UserTaskStatusAbandoned).
  65. Count(&totalCount)
  66. if int(totalCount) >= task.TotalLimit {
  67. ctx.Fail("total_limit_reached")
  68. return
  69. }
  70. }
  71. // 检查是否有进行中或待审核的任务
  72. var pendingCount int64
  73. db.Model(&entity.DtUserTask{}).
  74. Where("user_id = ? AND task_id = ? AND status IN (?, ?)", userId, req.TaskId, entity.UserTaskStatusPending, entity.UserTaskStatusSubmitted).
  75. Count(&pendingCount)
  76. if pendingCount > 0 {
  77. ctx.Fail("task_in_progress")
  78. return
  79. }
  80. // 事务:创建任务记录 + 扣减剩余数量
  81. tx := db.Begin()
  82. // 先原子扣减剩余数量,防止超领
  83. result := tx.Model(&entity.DtTask{}).
  84. Where("id = ? AND remain_count > 0", req.TaskId).
  85. UpdateColumn("remain_count", tx.Raw("remain_count - 1"))
  86. if result.Error != nil || result.RowsAffected == 0 {
  87. tx.Rollback()
  88. ctx.Fail("task_sold_out")
  89. return
  90. }
  91. // 创建任务记录
  92. userTask := &entity.DtUserTask{
  93. UserId: userId,
  94. TaskId: req.TaskId,
  95. TaskNo: task.TaskNo,
  96. TaskTitle: task.Title,
  97. RewardAmount: task.RewardAmount,
  98. Status: entity.UserTaskStatusPending,
  99. }
  100. if err := tx.Create(userTask).Error; err != nil {
  101. tx.Rollback()
  102. ctx.Fail("claim_failed")
  103. return
  104. }
  105. tx.Commit()
  106. ctx.OK(userTask)
  107. }
  108. // TaskSubmit 提交任务
  109. func (s *Server) TaskSubmit(c *gin.Context) {
  110. ctx := s.FromContext(c)
  111. db := s.DB()
  112. userId := ctx.UserId()
  113. type SubmitRequest struct {
  114. UserTaskId int64 `json:"userTaskId" binding:"required"`
  115. Screenshots []string `json:"screenshots" binding:"required"`
  116. Remark string `json:"remark"`
  117. }
  118. var req SubmitRequest
  119. if err := c.ShouldBindJSON(&req); err != nil {
  120. ctx.Fail("invalid_params")
  121. return
  122. }
  123. // 获取用户任务
  124. userTask := &entity.DtUserTask{}
  125. if err := db.Where("id = ? AND user_id = ?", req.UserTaskId, userId).First(userTask).Error; err != nil {
  126. ctx.Fail("user_task_not_found")
  127. return
  128. }
  129. // 检查状态:进行中(0)、审核失败(-1)、已拒绝(3)或已打回(5)的任务可以提交
  130. if userTask.Status != entity.UserTaskStatusPending &&
  131. userTask.Status != entity.UserTaskStatusFailed &&
  132. userTask.Status != entity.UserTaskStatusRejected &&
  133. userTask.Status != entity.UserTaskStatusReturnBack {
  134. ctx.Fail("task_cannot_submit")
  135. return
  136. }
  137. // 获取任务检查是否需要截图
  138. task := &entity.DtTask{}
  139. db.Where("id = ?", userTask.TaskId).First(task)
  140. if task.RequireScreenshot == 1 && len(req.Screenshots) == 0 {
  141. ctx.Fail("screenshot_required")
  142. return
  143. }
  144. // 更新任务
  145. screenshotsJson, _ := json.Marshal(req.Screenshots)
  146. db.Model(&entity.DtUserTask{}).
  147. Where("id = ?", req.UserTaskId).
  148. Updates(map[string]interface{}{
  149. "screenshots": string(screenshotsJson),
  150. "remark": req.Remark,
  151. "submit_time": time.Now().Unix(),
  152. "status": entity.UserTaskStatusSubmitted,
  153. "reject_reason": "", // 重新提交时清空打回原因
  154. })
  155. ctx.OK(nil)
  156. }
  157. // TaskSupplementScreenshots 补充截图(待审核状态可以补充)
  158. func (s *Server) TaskSupplementScreenshots(c *gin.Context) {
  159. ctx := s.FromContext(c)
  160. db := s.DB()
  161. userId := ctx.UserId()
  162. type SupplementRequest struct {
  163. UserTaskId int64 `json:"userTaskId" binding:"required"`
  164. Screenshots []string `json:"screenshots" binding:"required"` // 新增的截图
  165. }
  166. var req SupplementRequest
  167. if err := c.ShouldBindJSON(&req); err != nil {
  168. ctx.Fail("invalid_params")
  169. return
  170. }
  171. // 获取用户任务
  172. userTask := &entity.DtUserTask{}
  173. if err := db.Where("id = ? AND user_id = ?", req.UserTaskId, userId).First(userTask).Error; err != nil {
  174. ctx.Fail("user_task_not_found")
  175. return
  176. }
  177. // 检查状态:只有待审核(1)的任务可以补充截图
  178. if userTask.Status != entity.UserTaskStatusSubmitted {
  179. ctx.Fail("task_cannot_supplement")
  180. return
  181. }
  182. // 解析现有截图
  183. existingScreenshots := make([]string, 0)
  184. if userTask.Screenshots != "" {
  185. json.Unmarshal([]byte(userTask.Screenshots), &existingScreenshots)
  186. }
  187. // 合并截图
  188. allScreenshots := append(existingScreenshots, req.Screenshots...)
  189. screenshotsJson, _ := json.Marshal(allScreenshots)
  190. // 更新任务
  191. db.Model(&entity.DtUserTask{}).
  192. Where("id = ?", req.UserTaskId).
  193. Update("screenshots", string(screenshotsJson))
  194. ctx.OK(gin.H{
  195. "screenshots": allScreenshots,
  196. })
  197. }
  198. // TaskAbandon 放弃任务
  199. func (s *Server) TaskAbandon(c *gin.Context) {
  200. ctx := s.FromContext(c)
  201. db := s.DB()
  202. userId := ctx.UserId()
  203. type AbandonRequest struct {
  204. UserTaskId int64 `json:"userTaskId" binding:"required"`
  205. }
  206. var req AbandonRequest
  207. if err := c.ShouldBindJSON(&req); err != nil {
  208. ctx.Fail("invalid_params")
  209. return
  210. }
  211. // 获取用户任务
  212. userTask := &entity.DtUserTask{}
  213. if err := db.Where("id = ? AND user_id = ?", req.UserTaskId, userId).First(userTask).Error; err != nil {
  214. ctx.Fail("user_task_not_found")
  215. return
  216. }
  217. // 只有进行中的任务可以放弃
  218. if userTask.Status != entity.UserTaskStatusPending {
  219. ctx.Fail("task_cannot_abandon")
  220. return
  221. }
  222. // 事务:更新状态 + 恢复数量
  223. tx := db.Begin()
  224. tx.Model(&entity.DtUserTask{}).
  225. Where("id = ?", req.UserTaskId).
  226. Update("status", entity.UserTaskStatusAbandoned)
  227. tx.Model(&entity.DtTask{}).
  228. Where("id = ?", userTask.TaskId).
  229. UpdateColumn("remain_count", tx.Raw("remain_count + 1"))
  230. tx.Commit()
  231. ctx.OK(nil)
  232. }
  233. // TaskMy 我的任务列表
  234. func (s *Server) TaskMy(c *gin.Context) {
  235. ctx := s.FromContext(c)
  236. db := s.DB()
  237. userId := ctx.UserId()
  238. status := ctx.QueryInt64("status", -99) // -99表示全部
  239. paging := &services.Pagination{
  240. Current: ctx.QueryInt64("current", 1),
  241. Size: ctx.QueryInt64("size", 20),
  242. }
  243. query := db.Model(&entity.DtUserTask{}).Where("user_id = ?", userId)
  244. if status != -99 {
  245. query = query.Where("status = ?", status)
  246. }
  247. query.Count(&paging.Total)
  248. paging.Computer()
  249. userTasks := make([]*entity.DtUserTask, 0)
  250. query.Order("created_at DESC").
  251. Offset(int(paging.Start)).
  252. Limit(int(paging.Size)).
  253. Find(&userTasks)
  254. // 批量获取关联的任务信息(避免N+1查询)
  255. type TaskWithInfo struct {
  256. *entity.DtUserTask
  257. Task *entity.DtTask `json:"task"`
  258. }
  259. // 收集所有 taskId
  260. taskIds := make([]int64, 0, len(userTasks))
  261. for _, ut := range userTasks {
  262. taskIds = append(taskIds, ut.TaskId)
  263. }
  264. // 一次性查询所有任务
  265. taskMap := make(map[int64]*entity.DtTask)
  266. if len(taskIds) > 0 {
  267. tasks := make([]*entity.DtTask, 0)
  268. db.Where("id IN ?", taskIds).Find(&tasks)
  269. for _, t := range tasks {
  270. taskMap[t.Id] = t
  271. }
  272. }
  273. result := make([]*TaskWithInfo, 0)
  274. for _, ut := range userTasks {
  275. task := taskMap[ut.TaskId]
  276. if task == nil {
  277. task = &entity.DtTask{}
  278. }
  279. result = append(result, &TaskWithInfo{
  280. DtUserTask: ut,
  281. Task: task,
  282. })
  283. }
  284. ctx.OK(gin.H{
  285. "list": result,
  286. "paging": paging,
  287. })
  288. }
  289. // TaskMyDetail 我的任务详情
  290. func (s *Server) TaskMyDetail(c *gin.Context) {
  291. ctx := s.FromContext(c)
  292. db := s.DB()
  293. userId := ctx.UserId()
  294. userTaskId := ctx.QueryInt64("id", 0)
  295. if userTaskId == 0 {
  296. ctx.Fail("id_required")
  297. return
  298. }
  299. // 获取用户任务
  300. userTask := &entity.DtUserTask{}
  301. if err := db.Where("id = ? AND user_id = ?", userTaskId, userId).First(userTask).Error; err != nil {
  302. ctx.Fail("user_task_not_found")
  303. return
  304. }
  305. // 获取任务
  306. task := &entity.DtTask{}
  307. db.Where("id = ?", userTask.TaskId).First(task)
  308. // 获取任务分类
  309. category := &entity.DtTaskCategory{}
  310. db.Where("id = ?", task.CategoryId).First(category)
  311. // 获取任务步骤
  312. steps := make([]*entity.DtTaskStep, 0)
  313. db.Model(&entity.DtTaskStep{}).
  314. Where("task_id = ?", userTask.TaskId).
  315. Order("step_no ASC").
  316. Find(&steps)
  317. ctx.OK(gin.H{
  318. "userTask": userTask,
  319. "task": task,
  320. "category": category,
  321. "steps": steps,
  322. })
  323. }