474 lines
14 KiB
Go
474 lines
14 KiB
Go
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,
|
||
})
|
||
}
|