package dao import ( "database/sql" "fmt" "dd_fiber_api/internal/camp" "dd_fiber_api/pkg/database" "dd_fiber_api/pkg/utils" "github.com/didi/gendry/builder" ) // UserCampDAO 用户加入打卡营 DAO type UserCampDAO struct { client *database.MySQLClient } // NewUserCampDAO 创建用户打卡营DAO实例 func NewUserCampDAO(client *database.MySQLClient) *UserCampDAO { return &UserCampDAO{client: client} } // CreateIfNotExists 幂等加入(存在则忽略) func (d *UserCampDAO) CreateIfNotExists(id, userID, campID string) error { table := "camp_user_camps" // 尝试插入;依赖 uk_user_camp 唯一约束保障幂等 data := []map[string]any{{ "id": id, "user_id": userID, "camp_id": campID, "status": "ACTIVE", // joined_at 由表默认值 CURRENT_TIMESTAMP 自动填充 }} cond, vals, err := builder.BuildInsert(table, data) if err != nil { return fmt.Errorf("构建插入语句失败: %v", err) } // ON DUPLICATE KEY UPDATE 保持幂等 cond += " ON DUPLICATE KEY UPDATE status=VALUES(status)" if _, err := d.client.DB.Exec(cond, vals...); err != nil { return fmt.Errorf("加入打卡营失败: %v", err) } return nil } // CheckUserCampStatus 检查用户是否加入了指定打卡营 func (d *UserCampDAO) CheckUserCampStatus(userID, campID string) (bool, sql.NullTime, sql.NullString, error) { table := "camp_user_camps" where := map[string]any{ "user_id": userID, "camp_id": campID, "status": "ACTIVE", } cond, vals, err := builder.BuildSelect(table, where, []string{"status", "joined_at", "current_section_id"}) if err != nil { return false, sql.NullTime{}, sql.NullString{}, fmt.Errorf("构建查询语句失败: %v", err) } var status string var joinedAt sql.NullTime var currentSectionID sql.NullString err = d.client.DB.QueryRow(cond, vals...).Scan(&status, &joinedAt, ¤tSectionID) if err != nil { if err == sql.ErrNoRows { return false, sql.NullTime{}, sql.NullString{}, nil // 用户未加入 } return false, sql.NullTime{}, sql.NullString{}, fmt.Errorf("查询用户打卡营状态失败: %v", err) } return true, joinedAt, currentSectionID, nil } // UpdateCurrentSection 更新用户在当前打卡营中的当前小节 func (d *UserCampDAO) UpdateCurrentSection(userID, campID, sectionID string) error { table := "camp_user_camps" where := map[string]any{ "user_id": userID, "camp_id": campID, } data := map[string]any{ "current_section_id": sectionID, } cond, vals, err := builder.BuildUpdate(table, 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 } // GetCurrentSection 获取用户在当前打卡营中的当前小节ID func (d *UserCampDAO) GetCurrentSection(userID, campID string) (string, error) { table := "camp_user_camps" where := map[string]any{ "user_id": userID, "camp_id": campID, "status": "ACTIVE", } cond, vals, err := builder.BuildSelect(table, where, []string{"current_section_id"}) if err != nil { return "", fmt.Errorf("构建查询语句失败: %v", err) } var currentSectionID sql.NullString err = d.client.DB.QueryRow(cond, vals...).Scan(¤tSectionID) if err != nil { if err == sql.ErrNoRows { return "", nil // 用户未加入 } return "", fmt.Errorf("查询当前小节失败: %v", err) } if !currentSectionID.Valid { return "", nil } return currentSectionID.String, nil } // ListUserJoinedCamps 获取用户已加入的打卡营列表 func (d *UserCampDAO) ListUserJoinedCamps(userID string, page, pageSize int) ([]*camp.UserJoinedCamp, int, error) { table := "camp_user_camps" // 查询总数 countCond, countVals, err := builder.BuildSelect(table, map[string]any{ "user_id": userID, "status": "ACTIVE", }, []string{"COUNT(*) as total"}) if err != nil { return nil, 0, fmt.Errorf("构建计数查询失败: %v", err) } var total int err = d.client.DB.QueryRow(countCond, countVals...).Scan(&total) if err != nil { return nil, 0, fmt.Errorf("查询总数失败: %v", err) } // 查询列表 where := map[string]any{ "user_id": userID, "status": "ACTIVE", } cond, vals, err := builder.BuildSelect(table, where, []string{"camp_id", "joined_at", "status"}) if err != nil { return nil, 0, fmt.Errorf("构建查询语句失败: %v", err) } // 添加分页 offset := (page - 1) * pageSize cond += " ORDER BY joined_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() var camps []*camp.UserJoinedCamp for rows.Next() { var camp camp.UserJoinedCamp var joinedAt sql.NullTime err := rows.Scan(&camp.CampID, &joinedAt, &camp.Status) if err != nil { return nil, 0, fmt.Errorf("扫描行数据失败: %v", err) } // 格式化时间 camp.JoinedAt = utils.FormatNullTimeToStd(joinedAt) camps = append(camps, &camp) } return camps, total, nil } // ListUserIDsByCamp 分页列出已加入指定打卡营的用户 ID(用于管理端进度矩阵:展示所有开启过该营的用户) // userKeyword 可选,对 user_id 做 LIKE 模糊匹配 func (d *UserCampDAO) ListUserIDsByCamp(campID, userKeyword string, page, pageSize int) ([]string, int, error) { table := "camp_user_camps" where := map[string]any{ "camp_id": campID, "status": "ACTIVE", } if userKeyword != "" { where["user_id like"] = "%" + userKeyword + "%" } // 总数 countCond, countVals, err := builder.BuildSelect(table, where, []string{"COUNT(*) as total"}) if err != nil { return nil, 0, fmt.Errorf("构建计数查询失败: %v", err) } var total int err = d.client.DB.QueryRow(countCond, countVals...).Scan(&total) if err != nil { return nil, 0, fmt.Errorf("查询总数失败: %v", err) } cond, vals, err := builder.BuildSelect(table, where, []string{"user_id"}) if err != nil { return nil, 0, fmt.Errorf("构建查询失败: %v", err) } offset := (page - 1) * pageSize cond += " ORDER BY joined_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() var userIDs []string for rows.Next() { var uid string if err := rows.Scan(&uid); err != nil { continue } userIDs = append(userIDs, uid) } return userIDs, total, nil }