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

348 lines
8.6 KiB
Go

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
}
}