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

245 lines
6.5 KiB
Go
Raw 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"
"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, &currentSectionID)
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(&currentSectionID)
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
}