package handler import ( "dd_fiber_api/internal/camp" "dd_fiber_api/internal/camp/service" "github.com/gofiber/fiber/v2" ) // CampHandler 打卡营处理器 type CampHandler struct { campService *service.CampService userCampService *service.UserCampService } // NewCampHandler 创建打卡营处理器(userCampService 可选,用于列表接口填充 is_joined) func NewCampHandler(campService *service.CampService, userCampService *service.UserCampService) *CampHandler { return &CampHandler{ campService: campService, userCampService: userCampService, } } // CreateCamp 创建打卡营 func (h *CampHandler) CreateCamp(c *fiber.Ctx) error { // 先解析为 map 以处理 intro_type 的数字到字符串转换 var rawReq map[string]interface{} if err := c.BodyParser(&rawReq); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "请求参数解析失败: " + err.Error(), }) } // 转换 intro_type:如果前端传的是数字,转换为字符串 if introTypeVal, ok := rawReq["intro_type"]; ok { switch v := introTypeVal.(type) { case float64: // JSON 解析数字为 float64 switch int(v) { case 0: rawReq["intro_type"] = "none" case 1: rawReq["intro_type"] = "image_text" case 2: rawReq["intro_type"] = "video" default: rawReq["intro_type"] = "none" } case int: switch v { case 0: rawReq["intro_type"] = "none" case 1: rawReq["intro_type"] = "image_text" case 2: rawReq["intro_type"] = "video" default: rawReq["intro_type"] = "none" } } } // 将转换后的 map 转换为 CreateCampRequest var req camp.CreateCampRequest if title, ok := rawReq["title"].(string); ok { req.Title = title } if coverImage, ok := rawReq["cover_image"].(string); ok { req.CoverImage = coverImage } if description, ok := rawReq["description"].(string); ok { req.Description = description } if introTypeStr, ok := rawReq["intro_type"].(string); ok { req.IntroType = camp.IntroType(introTypeStr) } if introContent, ok := rawReq["intro_content"].(string); ok { req.IntroContent = introContent } if categoryID, ok := rawReq["category_id"].(string); ok { req.CategoryID = categoryID } if isRecommended, ok := rawReq["is_recommended"].(bool); ok { req.IsRecommended = isRecommended } // 验证必填字段 if req.Title == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "打卡营标题不能为空", }) } // 验证 IntroType if req.IntroType != camp.IntroTypeNone && req.IntroType != camp.IntroTypeImageText && req.IntroType != camp.IntroTypeVideo { req.IntroType = camp.IntroTypeNone } resp, err := h.campService.CreateCamp(&req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "创建打卡营失败: " + err.Error(), }) } if !resp.Success { return c.Status(fiber.StatusBadRequest).JSON(resp) } return c.Status(fiber.StatusOK).JSON(resp) } // GetCamp 获取打卡营 // 支持两种方式: // 1. 路径参数:GET /camp/camps/:id // 2. 查询参数:GET /camp/camps/detail?id=xxx func (h *CampHandler) GetCamp(c *fiber.Ctx) error { // 优先从查询参数获取,如果没有则从路径参数获取 id := c.Query("id") if id == "" { id = c.Params("id") } if id == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "打卡营ID不能为空", }) } resp, err := h.campService.GetCamp(id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "获取打卡营失败: " + err.Error(), }) } if !resp.Success { return c.Status(fiber.StatusNotFound).JSON(resp) } return c.Status(fiber.StatusOK).JSON(resp) } // UpdateCamp 更新打卡营 func (h *CampHandler) UpdateCamp(c *fiber.Ctx) error { // 先解析为 map 以处理 intro_type 的数字到字符串转换 var rawReq map[string]interface{} if err := c.BodyParser(&rawReq); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "请求参数解析失败: " + err.Error(), }) } // 转换 intro_type:如果前端传的是数字,转换为字符串 if introTypeVal, ok := rawReq["intro_type"]; ok { switch v := introTypeVal.(type) { case float64: // JSON 解析数字为 float64 switch int(v) { case 0: rawReq["intro_type"] = "none" case 1: rawReq["intro_type"] = "image_text" case 2: rawReq["intro_type"] = "video" default: rawReq["intro_type"] = "none" } case int: switch v { case 0: rawReq["intro_type"] = "none" case 1: rawReq["intro_type"] = "image_text" case 2: rawReq["intro_type"] = "video" default: rawReq["intro_type"] = "none" } } } // 将转换后的 map 转换为 UpdateCampRequest var req camp.UpdateCampRequest if id, ok := rawReq["id"].(string); ok { req.ID = id } if title, ok := rawReq["title"].(string); ok { req.Title = title } if coverImage, ok := rawReq["cover_image"].(string); ok { req.CoverImage = coverImage } if description, ok := rawReq["description"].(string); ok { req.Description = description } if introTypeStr, ok := rawReq["intro_type"].(string); ok { req.IntroType = camp.IntroType(introTypeStr) } if introContent, ok := rawReq["intro_content"].(string); ok { req.IntroContent = introContent } if categoryID, ok := rawReq["category_id"].(string); ok { req.CategoryID = categoryID } if isRecommended, ok := rawReq["is_recommended"].(bool); ok { req.IsRecommended = isRecommended } // 从 URL 参数获取 ID(如果请求体中没有) if req.ID == "" { req.ID = c.Query("id") if req.ID == "" { req.ID = c.Params("id") } } if req.ID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "打卡营ID不能为空", }) } // 验证 IntroType if req.IntroType != camp.IntroTypeNone && req.IntroType != camp.IntroTypeImageText && req.IntroType != camp.IntroTypeVideo { req.IntroType = camp.IntroTypeNone } resp, err := h.campService.UpdateCamp(&req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "更新打卡营失败: " + err.Error(), }) } if !resp.Success { return c.Status(fiber.StatusBadRequest).JSON(resp) } return c.Status(fiber.StatusOK).JSON(resp) } // DeleteCamp 删除打卡营 func (h *CampHandler) DeleteCamp(c *fiber.Ctx) error { // 优先从查询参数获取,如果没有则从路径参数获取 id := c.Query("id") if id == "" { id = c.Params("id") } if id == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "打卡营ID不能为空", }) } resp, err := h.campService.DeleteCamp(id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "删除打卡营失败: " + err.Error(), }) } if !resp.Success { return c.Status(fiber.StatusNotFound).JSON(resp) } return c.Status(fiber.StatusOK).JSON(resp) } // ListCamps 列出打卡营 func (h *CampHandler) ListCamps(c *fiber.Ctx) error { var req camp.ListCampsRequest if err := c.QueryParser(&req); err != nil { req.Page = 1 req.PageSize = 10 } // 兼容前端/接口传 is_recommended(true/false)而非 recommend_filter if req.RecommendFilter == "" || req.RecommendFilter == camp.RecommendFilterAll { switch c.Query("is_recommended") { case "true", "1": req.RecommendFilter = camp.RecommendFilterOnlyTrue case "false", "0": req.RecommendFilter = camp.RecommendFilterOnlyFalse } } // 验证 RecommendFilter if req.RecommendFilter == "" { req.RecommendFilter = camp.RecommendFilterAll } resp, err := h.campService.ListCamps(&req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "获取打卡营列表失败: " + err.Error(), }) } // 保证每个营都带上 is_joined:若服务层未填充且提供了 user_id,则在此用 UserCampService 补全 if req.UserID != "" && len(resp.Camps) > 0 && h.userCampService != nil { for _, campItem := range resp.Camps { if campItem.IsJoined != nil { continue } statusResp, _ := h.userCampService.CheckUserCampStatus(req.UserID, campItem.ID) if statusResp != nil { joined := statusResp.IsJoined campItem.IsJoined = &joined } } } return c.Status(fiber.StatusOK).JSON(resp) } // GetCampDetailWithStatus 获取打卡营详情及状态(聚合接口) func (h *CampHandler) GetCampDetailWithStatus(c *fiber.Ctx) error { var req camp.GetCampDetailWithStatusRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "请求参数解析失败: " + err.Error(), }) } // 验证必填字段 if req.CampID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "打卡营ID不能为空", }) } if req.UserID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "用户ID不能为空", }) } resp, err := h.campService.GetCampDetailWithStatus(c.Context(), &req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "获取打卡营详情失败: " + err.Error(), }) } if !resp.Success { return c.Status(fiber.StatusBadRequest).JSON(resp) } return c.Status(fiber.StatusOK).JSON(resp) } // CanUnlockSection 检查是否可开启下一小节(后端查库:上一小节是否完成等) func (h *CampHandler) CanUnlockSection(c *fiber.Ctx) error { var req camp.CanUnlockSectionRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "请求参数解析失败: " + err.Error(), }) } if req.CampID == "" || req.SectionID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "打卡营ID和小节ID不能为空", }) } if req.UserID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "用户ID不能为空", }) } resp, err := h.campService.CanUnlockSection(&req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "检查失败: " + err.Error(), }) } return c.Status(fiber.StatusOK).JSON(resp) } // CanStartTask 检查用户是否可以开始指定任务(前置任务已完成) func (h *CampHandler) CanStartTask(c *fiber.Ctx) error { var req camp.CanStartTaskRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "请求参数解析失败: " + err.Error(), }) } if req.UserID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "用户ID不能为空", }) } if req.TaskID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "任务ID不能为空", }) } resp, err := h.campService.CanStartTask(&req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "检查失败: " + err.Error(), }) } return c.Status(fiber.StatusOK).JSON(resp) }