428 lines
11 KiB
Go
428 lines
11 KiB
Go
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)
|
||
}
|