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

474 lines
14 KiB
Go
Raw 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"
camp_dao "dd_fiber_api/internal/camp/dao"
"dd_fiber_api/internal/camp/service"
"dd_fiber_api/internal/order"
order_dao "dd_fiber_api/internal/order/dao"
order_service "dd_fiber_api/internal/order/service"
"github.com/gofiber/fiber/v2"
)
// SectionHandler 小节处理器
type SectionHandler struct {
sectionService *service.SectionService
orderService *order_service.OrderService // 订单服务(用于购买小节)
orderDAO *order_dao.OrderDAO // 订单DAO用于查询是否已拥有
userCampDAO *camp_dao.UserCampDAO // 用户打卡营 DAO用于更新当前小节 is_current
}
// NewSectionHandler 创建小节处理器
func NewSectionHandler(sectionService *service.SectionService) *SectionHandler {
return &SectionHandler{
sectionService: sectionService,
}
}
// SetOrderService 设置订单服务
func (h *SectionHandler) SetOrderService(orderService *order_service.OrderService) {
h.orderService = orderService
}
// SetOrderDAO 设置订单DAO
func (h *SectionHandler) SetOrderDAO(orderDAO *order_dao.OrderDAO) {
h.orderDAO = orderDAO
}
// SetUserCampDAO 设置用户打卡营 DAO用于开启小节时更新 current_section_id保证 is_current 只有最新小节为 true
func (h *SectionHandler) SetUserCampDAO(userCampDAO *camp_dao.UserCampDAO) {
h.userCampDAO = userCampDAO
}
// CreateSection 创建小节
func (h *SectionHandler) CreateSection(c *fiber.Ctx) error {
// 先解析为 map 以处理 time_interval_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(),
})
}
// 转换 time_interval_type如果前端传的是数字转换为字符串
if timeIntervalTypeVal, ok := rawReq["time_interval_type"]; ok {
switch v := timeIntervalTypeVal.(type) {
case float64: // JSON 解析数字为 float64
switch int(v) {
case 0:
rawReq["time_interval_type"] = "none"
case 1:
rawReq["time_interval_type"] = "hour"
case 2:
rawReq["time_interval_type"] = "natural_day"
case 3:
rawReq["time_interval_type"] = "paid"
default:
rawReq["time_interval_type"] = "none"
}
case int:
switch v {
case 0:
rawReq["time_interval_type"] = "none"
case 1:
rawReq["time_interval_type"] = "hour"
case 2:
rawReq["time_interval_type"] = "natural_day"
case 3:
rawReq["time_interval_type"] = "paid"
default:
rawReq["time_interval_type"] = "none"
}
}
}
// 将转换后的 map 转换为 CreateSectionRequest
var req camp.CreateSectionRequest
if campID, ok := rawReq["camp_id"].(string); ok {
req.CampID = campID
}
if title, ok := rawReq["title"].(string); ok {
req.Title = title
}
if sectionNumber, ok := rawReq["section_number"].(float64); ok {
req.SectionNumber = int32(sectionNumber)
}
if priceFen, ok := rawReq["price_fen"].(float64); ok {
req.PriceFen = int32(priceFen)
}
// require_previous_section 已废弃,不再从请求中读取,创建时固定为 false
if timeIntervalTypeStr, ok := rawReq["time_interval_type"].(string); ok {
req.TimeIntervalType = camp.TimeIntervalType(timeIntervalTypeStr)
}
if timeIntervalValue, ok := rawReq["time_interval_value"].(float64); ok {
req.TimeIntervalValue = int32(timeIntervalValue)
}
// 验证必填字段
if req.CampID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "打卡营ID不能为空",
})
}
if req.Title == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "小节标题不能为空",
})
}
// 验证 TimeIntervalType
if req.TimeIntervalType != camp.TimeIntervalTypeNone && req.TimeIntervalType != camp.TimeIntervalTypeHour && req.TimeIntervalType != camp.TimeIntervalTypeNaturalDay && req.TimeIntervalType != camp.TimeIntervalTypePaid {
req.TimeIntervalType = camp.TimeIntervalTypeNone
}
resp, err := h.sectionService.CreateSection(&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)
}
// GetSection 获取小节
func (h *SectionHandler) GetSection(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.sectionService.GetSection(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)
}
// UpdateSection 更新小节
func (h *SectionHandler) UpdateSection(c *fiber.Ctx) error {
// 先解析为 map 以处理 time_interval_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(),
})
}
// 转换 time_interval_type如果前端传的是数字转换为字符串
if timeIntervalTypeVal, ok := rawReq["time_interval_type"]; ok {
switch v := timeIntervalTypeVal.(type) {
case float64: // JSON 解析数字为 float64
switch int(v) {
case 0:
rawReq["time_interval_type"] = "none"
case 1:
rawReq["time_interval_type"] = "hour"
case 2:
rawReq["time_interval_type"] = "natural_day"
case 3:
rawReq["time_interval_type"] = "paid"
default:
rawReq["time_interval_type"] = "none"
}
case int:
switch v {
case 0:
rawReq["time_interval_type"] = "none"
case 1:
rawReq["time_interval_type"] = "hour"
case 2:
rawReq["time_interval_type"] = "natural_day"
case 3:
rawReq["time_interval_type"] = "paid"
default:
rawReq["time_interval_type"] = "none"
}
}
}
// 将转换后的 map 转换为 UpdateSectionRequest
var req camp.UpdateSectionRequest
if id, ok := rawReq["id"].(string); ok {
req.ID = id
}
if campID, ok := rawReq["camp_id"].(string); ok {
req.CampID = campID
}
if title, ok := rawReq["title"].(string); ok {
req.Title = title
}
if sectionNumber, ok := rawReq["section_number"].(float64); ok {
req.SectionNumber = int32(sectionNumber)
}
if priceFen, ok := rawReq["price_fen"].(float64); ok {
req.PriceFen = int32(priceFen)
}
// require_previous_section 已废弃,不再从请求中读取,更新时保留原值
if timeIntervalTypeStr, ok := rawReq["time_interval_type"].(string); ok {
req.TimeIntervalType = camp.TimeIntervalType(timeIntervalTypeStr)
}
if timeIntervalValue, ok := rawReq["time_interval_value"].(float64); ok {
req.TimeIntervalValue = int32(timeIntervalValue)
}
// 从 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不能为空",
})
}
// 验证 TimeIntervalType
if req.TimeIntervalType != camp.TimeIntervalTypeNone && req.TimeIntervalType != camp.TimeIntervalTypeHour && req.TimeIntervalType != camp.TimeIntervalTypeNaturalDay && req.TimeIntervalType != camp.TimeIntervalTypePaid {
req.TimeIntervalType = camp.TimeIntervalTypeNone
}
resp, err := h.sectionService.UpdateSection(&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)
}
// DeleteSection 删除小节
func (h *SectionHandler) DeleteSection(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.sectionService.DeleteSection(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)
}
// ListSections 列出小节
func (h *SectionHandler) ListSections(c *fiber.Ctx) error {
var req camp.ListSectionsRequest
if err := c.QueryParser(&req); err != nil {
req.Page = 1
req.PageSize = 10
}
// user_id 是可选参数,如果前端传递了则使用,否则为空字符串
// 这样 service 层可以根据 user_id 获取用户相关的状态信息
resp, err := h.sectionService.ListSections(&req)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": "获取小节列表失败: " + err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(resp)
}
// PurchaseSection 购买小节(用于开启打卡营后的第一小节等场景)
func (h *SectionHandler) PurchaseSection(c *fiber.Ctx) error {
var req camp.PurchaseSectionRequest
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.CampID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "打卡营ID不能为空",
})
}
if req.SectionID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "小节ID不能为空",
})
}
// 检查订单服务是否已设置
if h.orderService == nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": "订单服务未初始化",
})
}
// 获取小节信息,检查价格
sectionResp, err := h.sectionService.GetSection(req.SectionID)
if err != nil || !sectionResp.Success {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "获取小节信息失败",
})
}
section := sectionResp.Section
if section == nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "小节不存在",
})
}
// 验证小节是否属于指定的打卡营
if section.CampID != req.CampID {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "小节不属于指定的打卡营",
})
}
// 检查是否已拥有该小节(参考 gRPC 版本的逻辑)
if h.orderDAO != nil {
alreadyOwned, err := h.orderDAO.CheckUserHasSection(req.UserID, req.SectionID)
if err == nil && alreadyOwned {
// 已拥有:幂等返回,同时将当前小节更新为该节,保证 is_current 只有最新开启的小节为 true
if h.userCampDAO != nil {
_ = h.userCampDAO.UpdateCurrentSection(req.UserID, req.CampID, req.SectionID)
}
return c.Status(fiber.StatusOK).JSON(camp.PurchaseSectionResponse{
Success: true,
Message: "已拥有",
AlreadyOwned: true,
})
}
}
// 无论免费还是付费,都创建订单(参考 gRPC 版本的逻辑)
// 如果小节价格为0免费直接创建已支付的订单
if section.PriceFen == 0 {
// 创建免费订单
orderReq := &order.CreateOrderRequest{
UserID: req.UserID,
CampID: req.CampID,
SectionID: req.SectionID,
PaymentMethod: order.PaymentMethodUnknown, // 免费订单不需要支付方式
}
orderResp, err := h.orderService.CreateOrder(c.Context(), orderReq)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": "创建订单失败: " + err.Error(),
})
}
if !orderResp.Success {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": orderResp.Message,
})
}
return c.Status(fiber.StatusOK).JSON(camp.PurchaseSectionResponse{
Success: true,
Message: "购买成功(免费)",
OrderID: orderResp.OrderID,
OrderStatus: string(orderResp.OrderStatus),
ActualAmount: orderResp.ActualAmount,
IsFree: true,
})
}
// 如果需要付费,也创建订单(参考 gRPC 版本的逻辑)
// 创建待支付订单
orderReq := &order.CreateOrderRequest{
UserID: req.UserID,
CampID: req.CampID,
SectionID: req.SectionID,
PaymentMethod: order.PaymentMethodUnknown, // 待支付订单,后续需要调用支付接口
}
orderResp, err := h.orderService.CreateOrder(c.Context(), orderReq)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": "创建订单失败: " + err.Error(),
})
}
if !orderResp.Success {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": orderResp.Message,
})
}
return c.Status(fiber.StatusOK).JSON(camp.PurchaseSectionResponse{
Success: true,
Message: "订单创建成功,请完成支付",
OrderID: orderResp.OrderID,
OrderStatus: string(orderResp.OrderStatus),
ActualAmount: orderResp.ActualAmount,
IsFree: false,
})
}