348 lines
8.6 KiB
Go
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
|
|
}
|
|
}
|
|
|