package service import ( "fmt" "dd_fiber_api/internal/camp" "dd_fiber_api/internal/camp/dao" question_dao "dd_fiber_api/internal/question/dao" "dd_fiber_api/pkg/snowflake" "dd_fiber_api/pkg/utils" ) // UserCampService 用户打卡营服务 type UserCampService struct { userCampDAO *dao.UserCampDAO sectionDAO *dao.SectionDAO progressDAO *dao.ProgressDAO taskDAO *dao.TaskDAO resetHistoryDAO *dao.ResetHistoryDAO answerRecordDAO question_dao.AnswerRecordDAOInterface } // NewUserCampService 创建用户打卡营服务 func NewUserCampService( userCampDAO *dao.UserCampDAO, sectionDAO *dao.SectionDAO, progressDAO *dao.ProgressDAO, taskDAO *dao.TaskDAO, resetHistoryDAO *dao.ResetHistoryDAO, answerRecordDAO question_dao.AnswerRecordDAOInterface, ) *UserCampService { return &UserCampService{ userCampDAO: userCampDAO, sectionDAO: sectionDAO, progressDAO: progressDAO, taskDAO: taskDAO, resetHistoryDAO: resetHistoryDAO, answerRecordDAO: answerRecordDAO, } } // JoinCamp 用户加入打卡营(幂等) func (s *UserCampService) JoinCamp(req *camp.JoinCampRequest) (*camp.JoinCampResponse, error) { if req.UserID == "" || req.CampID == "" { return &camp.JoinCampResponse{Success: false, Message: "参数缺失"}, nil } id := snowflake.GetInstance().NextIDString() if err := s.userCampDAO.CreateIfNotExists(id, req.UserID, req.CampID); err != nil { return &camp.JoinCampResponse{Success: false, Message: fmt.Sprintf("加入失败: %v", err)}, nil } // 获取打卡营中 section_number 最小的小节ID firstSectionID, err := s.sectionDAO.GetFirstSectionID(req.CampID) if err != nil { return &camp.JoinCampResponse{Success: false, Message: fmt.Sprintf("获取第一个小节失败: %v", err)}, nil } // 如果找到了第一个小节,且当前小节ID为空,则设置为第一个小节 if firstSectionID != "" { currentSectionID, err := s.userCampDAO.GetCurrentSection(req.UserID, req.CampID) if err != nil { return &camp.JoinCampResponse{Success: false, Message: fmt.Sprintf("查询当前小节失败: %v", err)}, nil } // 如果当前小节ID为空,则设置为第一个小节 if currentSectionID == "" { if err := s.userCampDAO.UpdateCurrentSection(req.UserID, req.CampID, firstSectionID); err != nil { return &camp.JoinCampResponse{Success: false, Message: fmt.Sprintf("设置当前小节失败: %v", err)}, nil } } } return &camp.JoinCampResponse{Success: true, Message: "加入成功"}, nil } // CheckUserCampStatus 检查用户是否加入了打卡营,并返回打卡营整体状态 func (s *UserCampService) CheckUserCampStatus(userID, campID string) (*camp.CheckUserCampStatusResponse, error) { if userID == "" || campID == "" { return &camp.CheckUserCampStatusResponse{ Success: false, Message: "参数缺失", IsJoined: false, CampStatus: camp.CampStatusNotStarted, }, nil } isJoined, joinedAt, currentSectionID, err := s.userCampDAO.CheckUserCampStatus(userID, campID) if err != nil { return &camp.CheckUserCampStatusResponse{ Success: false, Message: fmt.Sprintf("查询失败: %v", err), IsJoined: false, CampStatus: camp.CampStatusNotStarted, }, nil } // 格式化时间 formattedJoinedAt := utils.FormatNullTimeToStd(joinedAt) // 获取当前小节ID var currentSectionIDStr string if currentSectionID.Valid { currentSectionIDStr = currentSectionID.String } // 计算打卡营整体状态 campStatus := camp.CampStatusNotStarted if isJoined { // 查询总任务数和已完成任务数 totalTasks, completedTasks, err := s.progressDAO.CountTasksAndCompletedByCamp(userID, campID) if err != nil { // 查询失败不影响主流程,默认进行中 campStatus = camp.CampStatusInProgress } else if totalTasks == 0 { // 没有任务,视为已完成(空营) campStatus = camp.CampStatusCompleted } else if completedTasks >= totalTasks { campStatus = camp.CampStatusCompleted } else if completedTasks > 0 { campStatus = camp.CampStatusInProgress } else { // 已加入但没有完成任何任务,仍视为进行中(已报名) campStatus = camp.CampStatusInProgress } } return &camp.CheckUserCampStatusResponse{ Success: true, Message: "查询成功", IsJoined: isJoined, JoinedAt: formattedJoinedAt, CurrentSectionID: currentSectionIDStr, CampStatus: campStatus, }, nil } // ListUserCamps 获取用户已加入的打卡营列表 func (s *UserCampService) ListUserCamps(req *camp.ListUserCampsRequest) (*camp.ListUserCampsResponse, error) { if req.UserID == "" { return &camp.ListUserCampsResponse{ Success: false, Message: "用户ID不能为空", }, nil } // 设置默认分页参数 page := req.Page if page <= 0 { page = 1 } pageSize := req.PageSize if pageSize <= 0 { pageSize = 10 } if pageSize > 100 { pageSize = 100 // 限制最大页面大小 } joinedCamps, total, err := s.userCampDAO.ListUserJoinedCamps(req.UserID, page, pageSize) if err != nil { return &camp.ListUserCampsResponse{ Success: false, Message: fmt.Sprintf("获取用户打卡营列表失败: %v", err), }, nil } return &camp.ListUserCampsResponse{ Success: true, Message: "获取成功", Camps: joinedCamps, Total: total, }, nil } // ResetCampProgress 清空用户在营内的任务进度(已购访问不变),并写入重置历史、删除该营下客观题答题记录 func (s *UserCampService) ResetCampProgress(req *camp.ResetCampProgressRequest) (*camp.ResetCampProgressResponse, error) { if req.UserID == "" || req.CampID == "" { return &camp.ResetCampProgressResponse{Success: false, Message: "参数缺失"}, nil } if !req.Confirm { return &camp.ResetCampProgressResponse{Success: false, Message: "需要确认confirm=true"}, nil } // 1. 获取该营下所有任务 ID,删除用户在这些任务下的客观题答题记录 var taskIDs []string if s.taskDAO != nil && s.answerRecordDAO != nil { tasks, _, err := s.taskDAO.List("", req.CampID, "", camp.TaskTypeUnknown, 1, 10000) if err == nil { for _, t := range tasks { taskIDs = append(taskIDs, t.ID) } if len(taskIDs) > 0 { _, _ = s.answerRecordDAO.DeleteByUserAndTaskIDs(req.UserID, taskIDs) } } } // 2. 删除进度 cleared, err := s.progressDAO.DeleteByUserAndCamp(req.UserID, req.CampID) if err != nil { return &camp.ResetCampProgressResponse{Success: false, Message: fmt.Sprintf("清空进度失败: %v", err)}, nil } // 3. 写入重置历史 if s.resetHistoryDAO != nil { id := snowflake.GetInstance().NextIDString() note := req.Note if err := s.resetHistoryDAO.Create(id, req.UserID, req.CampID, int(cleared), note); err != nil { // 记录失败不阻断重置成功,仅日志或后续可告警 _ = err } } // 4. 将 camp_user_camps.current_section_id 重置为该营第一小节 ID if s.sectionDAO != nil && s.userCampDAO != nil { firstSectionID, err := s.sectionDAO.GetFirstSectionID(req.CampID) if err == nil && firstSectionID != "" { _ = s.userCampDAO.UpdateCurrentSection(req.UserID, req.CampID, firstSectionID) } } return &camp.ResetCampProgressResponse{Success: true, Message: fmt.Sprintf("重置成功,已清空 %d 条进度记录", cleared)}, nil }