duidui_fiber/internal/camp/handler/camp_handler.go
2026-03-27 10:34:03 +08:00

428 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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_recommendedtrue/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)
}