package dao import ( "database/sql" "fmt" "strings" "time" "dd_fiber_api/internal/camp" "dd_fiber_api/pkg/database" "dd_fiber_api/pkg/utils" "github.com/didi/gendry/builder" ) // CampDAO 打卡营数据访问对象 type CampDAO struct { client *database.MySQLClient } // NewCampDAO 创建打卡营DAO实例 func NewCampDAO(client *database.MySQLClient) *CampDAO { return &CampDAO{ client: client, } } // Create 创建打卡营 func (d *CampDAO) Create(camp *camp.Camp) error { table := "camp_camps" data := []map[string]any{ { "id": camp.ID, "category_id": camp.CategoryID, "title": camp.Title, "cover_image": camp.CoverImage, "description": camp.Description, "intro_type": convertIntroType(camp.IntroType), "intro_content": camp.IntroContent, "is_recommended": camp.IsRecommended, "section_count": camp.SectionCount, }, } cond, vals, err := builder.BuildInsert(table, data) if err != nil { return fmt.Errorf("构建插入语句失败: %v", err) } _, err = d.client.DB.Exec(cond, vals...) if err != nil { return fmt.Errorf("创建打卡营失败: %v", err) } return nil } // GetByID 根据ID获取打卡营 func (d *CampDAO) GetByID(id string) (*camp.Camp, error) { table := "camp_camps" where := map[string]any{ "id": id, } selectFields := []string{"id", "category_id", "title", "cover_image", "description", "intro_type", "intro_content", "is_recommended", "section_count", "deleted_at"} cond, vals, err := builder.BuildSelect(table, where, selectFields) if err != nil { return nil, fmt.Errorf("构建查询失败: %v", err) } if strings.Contains(cond, "WHERE") { cond += " AND deleted_at IS NULL" } else { cond += " WHERE deleted_at IS NULL" } var campObj camp.Camp var coverImage, description, introContent sql.NullString var introTypeStr string var deletedAt sql.NullTime err = d.client.DB.QueryRow(cond, vals...).Scan( &campObj.ID, &campObj.CategoryID, &campObj.Title, &coverImage, &description, &introTypeStr, &introContent, &campObj.IsRecommended, &campObj.SectionCount, &deletedAt, ) if err == sql.ErrNoRows { return nil, fmt.Errorf("打卡营不存在: %s", id) } if err != nil { return nil, fmt.Errorf("查询打卡营失败: %v", err) } campObj.CoverImage = coverImage.String campObj.Description = description.String campObj.IntroType = parseIntroType(introTypeStr) campObj.IntroContent = introContent.String campObj.DeletedAt = utils.FormatNullTimeToStd(deletedAt) return &campObj, nil } // Update 更新打卡营 func (d *CampDAO) Update(campObj *camp.Camp) error { table := "camp_camps" where := map[string]any{ "id": campObj.ID, } data := map[string]any{ "category_id": campObj.CategoryID, "title": campObj.Title, "cover_image": campObj.CoverImage, "description": campObj.Description, "intro_type": convertIntroType(campObj.IntroType), "intro_content": campObj.IntroContent, "is_recommended": campObj.IsRecommended, "section_count": campObj.SectionCount, } cond, vals, err := builder.BuildUpdate(table, where, data) if err != nil { return fmt.Errorf("构建更新语句失败: %v", err) } cond += " AND deleted_at IS NULL" result, err := d.client.DB.Exec(cond, vals...) if err != nil { return fmt.Errorf("更新打卡营失败: %v", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("获取影响行数失败: %v", err) } if rows == 0 { return fmt.Errorf("打卡营不存在: %s", campObj.ID) } return nil } // Delete 删除打卡营(软删除) func (d *CampDAO) Delete(id string) error { table := "camp_camps" where := map[string]any{ "id": id, } data := map[string]any{ "deleted_at": time.Now(), } cond, vals, err := builder.BuildUpdate(table, where, data) if err != nil { return fmt.Errorf("构建删除语句失败: %v", err) } cond += " AND deleted_at IS NULL" result, err := d.client.DB.Exec(cond, vals...) if err != nil { return fmt.Errorf("删除打卡营失败: %v", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("获取影响行数失败: %v", err) } if rows == 0 { return fmt.Errorf("打卡营不存在: %s", id) } return nil } // Search 搜索打卡营(支持关键词、分类、推荐状态) func (d *CampDAO) Search(keyword, categoryID string, isRecommended *bool, page, pageSize int) ([]*camp.Camp, int, error) { table := "camp_camps" // 构建查询条件 where := map[string]any{} if keyword != "" { where["_or"] = []map[string]any{ {"title like": "%" + keyword + "%"}, {"description like": "%" + keyword + "%"}, } } if categoryID != "" { where["category_id"] = categoryID } if isRecommended != nil { where["is_recommended"] = *isRecommended } // 查询总数 countCond, countVals, err := builder.BuildSelect(table, where, []string{"count(*) as total"}) if err != nil { return nil, 0, fmt.Errorf("构建统计查询失败: %v", err) } if strings.Contains(countCond, "WHERE") { countCond += " AND deleted_at IS NULL" } else { countCond += " WHERE deleted_at IS NULL" } var total int err = d.client.DB.QueryRow(countCond, countVals...).Scan(&total) if err != nil { return nil, 0, fmt.Errorf("查询打卡营总数失败: %v", err) } // 查询数据 selectFields := []string{"id", "category_id", "title", "cover_image", "description", "intro_type", "intro_content", "is_recommended", "section_count", "deleted_at"} cond, vals, err := builder.BuildSelect(table, where, selectFields) if err != nil { return nil, 0, fmt.Errorf("构建查询失败: %v", err) } if strings.Contains(cond, "WHERE") { cond += " AND deleted_at IS NULL" } else { cond += " WHERE deleted_at IS NULL" } // 添加排序和分页 offset := (page - 1) * pageSize cond += " ORDER BY created_at DESC LIMIT ? OFFSET ?" vals = append(vals, pageSize, offset) rows, err := d.client.DB.Query(cond, vals...) if err != nil { return nil, 0, fmt.Errorf("查询打卡营列表失败: %v", err) } defer rows.Close() camps := make([]*camp.Camp, 0) for rows.Next() { var campObj camp.Camp var coverImage, description, introContent sql.NullString var introTypeStr string var deletedAt sql.NullTime err := rows.Scan( &campObj.ID, &campObj.CategoryID, &campObj.Title, &coverImage, &description, &introTypeStr, &introContent, &campObj.IsRecommended, &campObj.SectionCount, &deletedAt, ) if err != nil { continue } campObj.CoverImage = coverImage.String campObj.Description = description.String campObj.IntroType = parseIntroType(introTypeStr) campObj.IntroContent = introContent.String campObj.DeletedAt = utils.FormatNullTimeToStd(deletedAt) camps = append(camps, &campObj) } if err = rows.Err(); err != nil { return nil, 0, fmt.Errorf("遍历打卡营数据失败: %v", err) } return camps, total, nil } // CountByCategoryID 统计指定分类下的打卡营数量(未软删除) func (d *CampDAO) CountByCategoryID(categoryID string) (int, error) { query := "SELECT COUNT(*) FROM camp_camps WHERE category_id = ? AND deleted_at IS NULL" var count int err := d.client.DB.QueryRow(query, categoryID).Scan(&count) if err != nil { return 0, fmt.Errorf("统计分类下打卡营数量失败: %v", err) } return count, nil } // UpdateSectionCount 根据实际小节数量更新打卡营的 section_count func (d *CampDAO) UpdateSectionCount(campID string) error { // 统计该打卡营的实际小节数量 countQuery := `SELECT COUNT(*) FROM camp_sections WHERE camp_id = ? AND deleted_at IS NULL` var actualCount int err := d.client.DB.QueryRow(countQuery, campID).Scan(&actualCount) if err != nil { return fmt.Errorf("统计小节数量失败: %v", err) } // 更新打卡营的 section_count where := map[string]any{ "id": campID, } data := map[string]any{ "section_count": actualCount, } cond, vals, err := builder.BuildUpdate("camp_camps", where, data) if err != nil { return fmt.Errorf("构建更新语句失败: %v", err) } _, err = d.client.DB.Exec(cond, vals...) if err != nil { return fmt.Errorf("更新打卡营小节数量失败: %v", err) } return nil } // convertIntroType 将 IntroType 转换为数据库 ENUM 字符串 func convertIntroType(introType camp.IntroType) string { switch introType { case camp.IntroTypeImageText: return "IMAGE_TEXT" case camp.IntroTypeVideo: return "VIDEO" default: return "NONE" } } // parseIntroType 将数据库 ENUM 字符串转换为 IntroType func parseIntroType(introTypeStr string) camp.IntroType { switch introTypeStr { case "IMAGE_TEXT": return camp.IntroTypeImageText case "VIDEO": return camp.IntroTypeVideo default: return camp.IntroTypeNone } }