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

354 lines
9.6 KiB
Go
Raw Permalink 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 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"
)
// SectionDAO 小节数据访问对象
type SectionDAO struct {
client *database.MySQLClient
}
// NewSectionDAO 创建小节DAO实例
func NewSectionDAO(client *database.MySQLClient) *SectionDAO {
return &SectionDAO{
client: client,
}
}
// Create 创建小节
func (d *SectionDAO) Create(section *camp.Section) error {
table := "camp_sections"
data := []map[string]any{
{
"id": section.ID,
"camp_id": section.CampID,
"title": section.Title,
"section_number": section.SectionNumber,
"price_fen": section.PriceFen,
"require_previous_section": section.RequirePreviousSection,
"time_interval_type": convertTimeIntervalType(section.TimeIntervalType),
"time_interval_value": section.TimeIntervalValue,
},
}
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 *SectionDAO) GetByID(id string) (*camp.Section, error) {
table := "camp_sections"
where := map[string]any{
"id": id,
}
selectFields := []string{"id", "camp_id", "title", "section_number", "price_fen", "require_previous_section", "time_interval_type", "time_interval_value", "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 section camp.Section
var timeIntervalTypeStr string
var deletedAt sql.NullTime
err = d.client.DB.QueryRow(cond, vals...).Scan(
&section.ID,
&section.CampID,
&section.Title,
&section.SectionNumber,
&section.PriceFen,
&section.RequirePreviousSection,
&timeIntervalTypeStr,
&section.TimeIntervalValue,
&deletedAt,
)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("小节不存在: %s", id)
}
if err != nil {
return nil, fmt.Errorf("查询小节失败: %v", err)
}
section.TimeIntervalType = parseTimeIntervalType(timeIntervalTypeStr)
section.DeletedAt = utils.FormatNullTimeToStd(deletedAt)
return &section, nil
}
// Update 更新小节
func (d *SectionDAO) Update(section *camp.Section) error {
table := "camp_sections"
where := map[string]any{
"id": section.ID,
}
data := map[string]any{
"camp_id": section.CampID,
"title": section.Title,
"section_number": section.SectionNumber,
"price_fen": section.PriceFen,
"require_previous_section": section.RequirePreviousSection,
"time_interval_type": convertTimeIntervalType(section.TimeIntervalType),
"time_interval_value": section.TimeIntervalValue,
}
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", section.ID)
}
return nil
}
// Delete 删除小节(软删除)
func (d *SectionDAO) Delete(id string) error {
table := "camp_sections"
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
}
// List 列出小节支持关键词搜索、按打卡营ID筛选
func (d *SectionDAO) List(keyword, campID string, page, pageSize int) ([]*camp.Section, int, error) {
table := "camp_sections"
// 构建查询条件
where := map[string]any{}
if campID != "" {
where["camp_id"] = campID
}
if keyword != "" {
where["_or"] = []map[string]any{
{"title like": "%" + keyword + "%"},
{"id like": "%" + keyword + "%"},
}
}
// 查询总数
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", "camp_id", "title", "section_number", "price_fen", "require_previous_section", "time_interval_type", "time_interval_value", "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 section_number ASC 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()
sections := make([]*camp.Section, 0)
for rows.Next() {
var section camp.Section
var timeIntervalTypeStr string
var deletedAt sql.NullTime
err := rows.Scan(
&section.ID,
&section.CampID,
&section.Title,
&section.SectionNumber,
&section.PriceFen,
&section.RequirePreviousSection,
&timeIntervalTypeStr,
&section.TimeIntervalValue,
&deletedAt,
)
if err != nil {
continue
}
section.TimeIntervalType = parseTimeIntervalType(timeIntervalTypeStr)
section.DeletedAt = utils.FormatNullTimeToStd(deletedAt)
sections = append(sections, &section)
}
if err = rows.Err(); err != nil {
return nil, 0, fmt.Errorf("遍历小节数据失败: %v", err)
}
return sections, total, nil
}
// CountActiveByCamp 统计打卡营下未删除的小节数量(与 List 条件一致,兼容 deleted_at 为 NULL 或 0001-01-01
func (d *SectionDAO) CountActiveByCamp(campID string) (int, error) {
query := "SELECT COUNT(*) FROM camp_sections WHERE camp_id = ? AND (deleted_at IS NULL OR deleted_at = '0001-01-01 00:00:00')"
var count int
err := d.client.DB.QueryRow(query, campID).Scan(&count)
if err != nil {
return 0, fmt.Errorf("统计小节数量失败: %v", err)
}
return count, nil
}
// CountByCampIDs 批量统计多个打卡营下未删除的小节数量,返回 map[campID]count用于列表展示时校正 section_count
func (d *SectionDAO) CountByCampIDs(campIDs []string) (map[string]int, error) {
if len(campIDs) == 0 {
return map[string]int{}, nil
}
placeholders := strings.Repeat("?,", len(campIDs))
placeholders = placeholders[:len(placeholders)-1]
query := "SELECT camp_id, COUNT(*) FROM camp_sections WHERE (deleted_at IS NULL OR deleted_at = '0001-01-01 00:00:00') AND camp_id IN (" + placeholders + ") GROUP BY camp_id"
args := make([]any, len(campIDs))
for i, id := range campIDs {
args[i] = id
}
rows, err := d.client.DB.Query(query, args...)
if err != nil {
return nil, fmt.Errorf("批量统计小节数量失败: %v", err)
}
defer rows.Close()
result := make(map[string]int)
for _, id := range campIDs {
result[id] = 0
}
for rows.Next() {
var campID string
var count int
if err := rows.Scan(&campID, &count); err != nil {
continue
}
result[campID] = count
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("遍历小节统计结果失败: %v", err)
}
return result, nil
}
// GetFirstSectionID 获取打卡营中 section_number 最小的小节ID
func (d *SectionDAO) GetFirstSectionID(campID string) (string, error) {
query := "SELECT id FROM camp_sections WHERE camp_id = ? AND deleted_at IS NULL ORDER BY section_number ASC LIMIT 1"
var sectionID string
err := d.client.DB.QueryRow(query, campID).Scan(&sectionID)
if err != nil {
if err == sql.ErrNoRows {
return "", nil // 没有小节,返回空字符串
}
return "", fmt.Errorf("获取第一个小节ID失败: %v", err)
}
return sectionID, nil
}
// convertTimeIntervalType 将 TimeIntervalType 转换为数据库字符串
func convertTimeIntervalType(timeIntervalType camp.TimeIntervalType) string {
switch timeIntervalType {
case camp.TimeIntervalTypeHour:
return "HOUR_INTERVAL"
case camp.TimeIntervalTypeNaturalDay:
return "NATURAL_DAY"
case camp.TimeIntervalTypePaid:
return "PAID"
default:
return "NONE"
}
}
// parseTimeIntervalType 将数据库字符串转换为 TimeIntervalType大小写不敏感
func parseTimeIntervalType(timeIntervalTypeStr string) camp.TimeIntervalType {
s := strings.TrimSpace(strings.ToUpper(timeIntervalTypeStr))
switch s {
case "HOUR_INTERVAL", "HOUR":
return camp.TimeIntervalTypeHour
case "NATURAL_DAY":
return camp.TimeIntervalTypeNaturalDay
case "PAID":
return camp.TimeIntervalTypePaid
default:
return camp.TimeIntervalTypeNone
}
}