refactor: update repository interfaces and improve dependency injection
- Refactored repository structures to use interfaces instead of concrete types, enhancing flexibility and testability. - Updated various repository methods to accept interfaces, allowing for better dependency management. - Modified wire generation to accommodate new repository interfaces, ensuring proper service injection throughout the application. - Enhanced handler methods to utilize DTOs for filtering and data handling, improving code clarity and maintainability.
This commit is contained in:
@@ -6,16 +6,26 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ChannelRepository 频道配置仓储
|
||||
type ChannelRepository struct {
|
||||
// ChannelRepository 频道配置仓储接口
|
||||
type ChannelRepository interface {
|
||||
ListActive() ([]*model.Channel, error)
|
||||
ListAll() ([]*model.Channel, error)
|
||||
Create(channel *model.Channel) error
|
||||
Update(channel *model.Channel) error
|
||||
GetByID(id string) (*model.Channel, error)
|
||||
Delete(id string) error
|
||||
}
|
||||
|
||||
// channelRepository 频道配置仓储实现
|
||||
type channelRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewChannelRepository(db *gorm.DB) *ChannelRepository {
|
||||
return &ChannelRepository{db: db}
|
||||
func NewChannelRepository(db *gorm.DB) ChannelRepository {
|
||||
return &channelRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *ChannelRepository) ListActive() ([]*model.Channel, error) {
|
||||
func (r *channelRepository) ListActive() ([]*model.Channel, error) {
|
||||
var channels []*model.Channel
|
||||
err := r.db.
|
||||
Where("is_active = ?", true).
|
||||
@@ -24,7 +34,7 @@ func (r *ChannelRepository) ListActive() ([]*model.Channel, error) {
|
||||
return channels, err
|
||||
}
|
||||
|
||||
func (r *ChannelRepository) ListAll() ([]*model.Channel, error) {
|
||||
func (r *channelRepository) ListAll() ([]*model.Channel, error) {
|
||||
var channels []*model.Channel
|
||||
err := r.db.
|
||||
Order("sort_order ASC, created_at ASC").
|
||||
@@ -32,15 +42,15 @@ func (r *ChannelRepository) ListAll() ([]*model.Channel, error) {
|
||||
return channels, err
|
||||
}
|
||||
|
||||
func (r *ChannelRepository) Create(channel *model.Channel) error {
|
||||
func (r *channelRepository) Create(channel *model.Channel) error {
|
||||
return r.db.Create(channel).Error
|
||||
}
|
||||
|
||||
func (r *ChannelRepository) Update(channel *model.Channel) error {
|
||||
func (r *channelRepository) Update(channel *model.Channel) error {
|
||||
return r.db.Save(channel).Error
|
||||
}
|
||||
|
||||
func (r *ChannelRepository) GetByID(id string) (*model.Channel, error) {
|
||||
func (r *channelRepository) GetByID(id string) (*model.Channel, error) {
|
||||
var channel model.Channel
|
||||
if err := r.db.First(&channel, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -48,7 +58,7 @@ func (r *ChannelRepository) GetByID(id string) (*model.Channel, error) {
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (r *ChannelRepository) Delete(id string) error {
|
||||
func (r *channelRepository) Delete(id string) error {
|
||||
return r.db.Delete(&model.Channel{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
|
||||
@@ -8,23 +8,47 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CommentRepository 评论仓储
|
||||
type CommentRepository struct {
|
||||
// CommentRepository 评论仓储接口
|
||||
type CommentRepository interface {
|
||||
Create(comment *model.Comment) error
|
||||
GetByID(id string) (*model.Comment, error)
|
||||
Update(comment *model.Comment) error
|
||||
UpdateModerationStatus(commentID string, status model.CommentStatus) error
|
||||
Delete(id string) error
|
||||
ApplyPublishedStats(comment *model.Comment) error
|
||||
GetByPostID(postID string, page, pageSize int) ([]*model.Comment, int64, error)
|
||||
GetByPostIDWithReplies(postID string, page, pageSize, replyLimit int) ([]*model.Comment, int64, error)
|
||||
GetRepliesByRootID(rootID string, page, pageSize int) ([]*model.Comment, int64, error)
|
||||
GetReplies(parentID string) ([]*model.Comment, error)
|
||||
Like(commentID, userID string) error
|
||||
Unlike(commentID, userID string) error
|
||||
IsLiked(commentID, userID string) bool
|
||||
IsLikedBatch(commentIDs []string, userID string) map[string]bool
|
||||
GetAdminCommentList(filter AdminCommentListFilter) ([]*model.Comment, int64, error)
|
||||
GetAdminCommentByID(id string) (*model.Comment, error)
|
||||
BatchDelete(ids []string) ([]string, error)
|
||||
GetCommentsByPostIDForAdmin(postID string, page, pageSize int) ([]*model.Comment, int64, error)
|
||||
GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error)
|
||||
GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error)
|
||||
}
|
||||
|
||||
// commentRepository 评论仓储实现
|
||||
type commentRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewCommentRepository 创建评论仓储
|
||||
func NewCommentRepository(db *gorm.DB) *CommentRepository {
|
||||
return &CommentRepository{db: db}
|
||||
func NewCommentRepository(db *gorm.DB) CommentRepository {
|
||||
return &commentRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建评论
|
||||
func (r *CommentRepository) Create(comment *model.Comment) error {
|
||||
func (r *commentRepository) Create(comment *model.Comment) error {
|
||||
return r.db.Create(comment).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取评论
|
||||
func (r *CommentRepository) GetByID(id string) (*model.Comment, error) {
|
||||
func (r *commentRepository) GetByID(id string) (*model.Comment, error) {
|
||||
var comment model.Comment
|
||||
err := r.db.Preload("User").First(&comment, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -34,12 +58,12 @@ func (r *CommentRepository) GetByID(id string) (*model.Comment, error) {
|
||||
}
|
||||
|
||||
// Update 更新评论
|
||||
func (r *CommentRepository) Update(comment *model.Comment) error {
|
||||
func (r *commentRepository) Update(comment *model.Comment) error {
|
||||
return r.db.Save(comment).Error
|
||||
}
|
||||
|
||||
// UpdateModerationStatus 更新评论审核状态
|
||||
func (r *CommentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus) error {
|
||||
func (r *commentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus) error {
|
||||
// 审核状态更新属于管理操作,不应影响评论内容更新时间(updated_at)
|
||||
return r.db.Model(&model.Comment{}).
|
||||
Where("id = ?", commentID).
|
||||
@@ -47,7 +71,7 @@ func (r *CommentRepository) UpdateModerationStatus(commentID string, status mode
|
||||
}
|
||||
|
||||
// Delete 删除评论(软删除,同时清理关联数据)
|
||||
func (r *CommentRepository) Delete(id string) error {
|
||||
func (r *commentRepository) Delete(id string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 先查询评论获取post_id和parent_id
|
||||
var comment model.Comment
|
||||
@@ -91,7 +115,7 @@ func (r *CommentRepository) Delete(id string) error {
|
||||
}
|
||||
|
||||
// ApplyPublishedStats 在评论审核通过后更新帖子评论数/回复数
|
||||
func (r *CommentRepository) ApplyPublishedStats(comment *model.Comment) error {
|
||||
func (r *commentRepository) ApplyPublishedStats(comment *model.Comment) error {
|
||||
if comment == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -118,7 +142,7 @@ func (r *CommentRepository) ApplyPublishedStats(comment *model.Comment) error {
|
||||
}
|
||||
|
||||
// GetByPostID 获取帖子评论
|
||||
func (r *CommentRepository) GetByPostID(postID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
||||
func (r *commentRepository) GetByPostID(postID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
||||
var comments []*model.Comment
|
||||
var total int64
|
||||
|
||||
@@ -136,7 +160,7 @@ func (r *CommentRepository) GetByPostID(postID string, page, pageSize int) ([]*m
|
||||
|
||||
// GetByPostIDWithReplies 获取帖子评论(包含回复,扁平化结构)
|
||||
// 所有层级的回复都扁平化展示在顶级评论的 replies 中
|
||||
func (r *CommentRepository) GetByPostIDWithReplies(postID string, page, pageSize, replyLimit int) ([]*model.Comment, int64, error) {
|
||||
func (r *commentRepository) GetByPostIDWithReplies(postID string, page, pageSize, replyLimit int) ([]*model.Comment, int64, error) {
|
||||
var comments []*model.Comment
|
||||
var total int64
|
||||
|
||||
@@ -212,7 +236,7 @@ func (r *CommentRepository) GetByPostIDWithReplies(postID string, page, pageSize
|
||||
}
|
||||
|
||||
// loadFlatReplies 加载评论的所有回复(扁平化,所有层级都在同一层)
|
||||
func (r *CommentRepository) loadFlatReplies(rootComment *model.Comment, limit int) {
|
||||
func (r *commentRepository) loadFlatReplies(rootComment *model.Comment, limit int) {
|
||||
var allReplies []*model.Comment
|
||||
|
||||
// 查询所有以该评论为根评论的回复(不包括顶级评论本身)
|
||||
@@ -226,7 +250,7 @@ func (r *CommentRepository) loadFlatReplies(rootComment *model.Comment, limit in
|
||||
}
|
||||
|
||||
// GetRepliesByRootID 根据根评论ID分页获取回复(扁平化)
|
||||
func (r *CommentRepository) GetRepliesByRootID(rootID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
||||
func (r *commentRepository) GetRepliesByRootID(rootID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
||||
var replies []*model.Comment
|
||||
var total int64
|
||||
|
||||
@@ -246,7 +270,7 @@ func (r *CommentRepository) GetRepliesByRootID(rootID string, page, pageSize int
|
||||
}
|
||||
|
||||
// GetReplies 获取回复
|
||||
func (r *CommentRepository) GetReplies(parentID string) ([]*model.Comment, error) {
|
||||
func (r *commentRepository) GetReplies(parentID string) ([]*model.Comment, error) {
|
||||
var comments []*model.Comment
|
||||
err := r.db.Where("parent_id = ? AND status = ?", parentID, model.CommentStatusPublished).
|
||||
Preload("User").
|
||||
@@ -256,7 +280,7 @@ func (r *CommentRepository) GetReplies(parentID string) ([]*model.Comment, error
|
||||
}
|
||||
|
||||
// Like 点赞评论(使用事务保证数据一致性)
|
||||
func (r *CommentRepository) Like(commentID, userID string) error {
|
||||
func (r *commentRepository) Like(commentID, userID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 检查是否已经点赞
|
||||
var existing model.CommentLike
|
||||
@@ -285,7 +309,7 @@ func (r *CommentRepository) Like(commentID, userID string) error {
|
||||
}
|
||||
|
||||
// Unlike 取消点赞评论(使用事务保证数据一致性)
|
||||
func (r *CommentRepository) Unlike(commentID, userID string) error {
|
||||
func (r *commentRepository) Unlike(commentID, userID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Where("comment_id = ? AND user_id = ?", commentID, userID).Delete(&model.CommentLike{})
|
||||
if result.Error != nil {
|
||||
@@ -301,7 +325,7 @@ func (r *CommentRepository) Unlike(commentID, userID string) error {
|
||||
}
|
||||
|
||||
// IsLiked 检查是否已点赞
|
||||
func (r *CommentRepository) IsLiked(commentID, userID string) bool {
|
||||
func (r *commentRepository) IsLiked(commentID, userID string) bool {
|
||||
var count int64
|
||||
r.db.Model(&model.CommentLike{}).Where("comment_id = ? AND user_id = ?", commentID, userID).Count(&count)
|
||||
return count > 0
|
||||
@@ -309,7 +333,7 @@ func (r *CommentRepository) IsLiked(commentID, userID string) bool {
|
||||
|
||||
// IsLikedBatch 批量检查是否点赞(解决 N+1 问题)
|
||||
// 返回 map[commentID]bool
|
||||
func (r *CommentRepository) IsLikedBatch(commentIDs []string, userID string) map[string]bool {
|
||||
func (r *commentRepository) IsLikedBatch(commentIDs []string, userID string) map[string]bool {
|
||||
result := make(map[string]bool)
|
||||
if len(commentIDs) == 0 || userID == "" {
|
||||
return result
|
||||
@@ -347,7 +371,7 @@ type AdminCommentListFilter struct {
|
||||
}
|
||||
|
||||
// GetAdminCommentList 获取管理端评论列表
|
||||
func (r *CommentRepository) GetAdminCommentList(filter AdminCommentListFilter) ([]*model.Comment, int64, error) {
|
||||
func (r *commentRepository) GetAdminCommentList(filter AdminCommentListFilter) ([]*model.Comment, int64, error) {
|
||||
var comments []*model.Comment
|
||||
var total int64
|
||||
|
||||
@@ -388,7 +412,7 @@ func (r *CommentRepository) GetAdminCommentList(filter AdminCommentListFilter) (
|
||||
}
|
||||
|
||||
// GetAdminCommentByID 获取管理端评论详情(包含关联信息)
|
||||
func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, error) {
|
||||
func (r *commentRepository) GetAdminCommentByID(id string) (*model.Comment, error) {
|
||||
var comment model.Comment
|
||||
err := r.db.Preload("User").First(&comment, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -398,7 +422,7 @@ func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, erro
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除评论(使用单次事务批量删除,避免 N+1 问题)
|
||||
func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
func (r *commentRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -452,7 +476,7 @@ func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
}
|
||||
|
||||
// GetCommentsByPostIDForAdmin 管理端获取帖子的评论列表(包含所有状态)
|
||||
func (r *CommentRepository) GetCommentsByPostIDForAdmin(postID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
||||
func (r *commentRepository) GetCommentsByPostIDForAdmin(postID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
||||
var comments []*model.Comment
|
||||
var total int64
|
||||
|
||||
@@ -472,7 +496,7 @@ func (r *CommentRepository) GetCommentsByPostIDForAdmin(postID string, page, pag
|
||||
|
||||
// GetCommentsByCursor 游标分页获取帖子评论(包含回复)
|
||||
// 排序方式:created_at DESC
|
||||
func (r *CommentRepository) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
||||
func (r *commentRepository) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
||||
// 构建基础查询 - 只查询顶级评论
|
||||
query := r.db.WithContext(ctx).Model(&model.Comment{}).
|
||||
Where("post_id = ? AND parent_id IS NULL AND status = ?", postID, model.CommentStatusPublished)
|
||||
@@ -531,7 +555,7 @@ func (r *CommentRepository) GetCommentsByCursor(ctx context.Context, postID stri
|
||||
}
|
||||
|
||||
// loadRepliesForComments 为评论列表加载回复
|
||||
func (r *CommentRepository) loadRepliesForComments(comments []*model.Comment, replyLimit int) {
|
||||
func (r *commentRepository) loadRepliesForComments(comments []*model.Comment, replyLimit int) {
|
||||
if len(comments) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -593,7 +617,7 @@ func (r *CommentRepository) loadRepliesForComments(comments []*model.Comment, re
|
||||
|
||||
// GetRepliesByCursor 游标分页获取根评论的回复
|
||||
// 排序方式:created_at ASC(回复按时间正序)
|
||||
func (r *CommentRepository) GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
||||
func (r *commentRepository) GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
||||
// 构建基础查询
|
||||
query := r.db.WithContext(ctx).Model(&model.Comment{}).
|
||||
Where("root_id = ? AND status = ?", rootID, model.CommentStatusPublished)
|
||||
|
||||
@@ -3,28 +3,40 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DataChangeLogRepository 数据变更日志仓储
|
||||
type DataChangeLogRepository struct {
|
||||
// DataChangeLogRepository 数据变更日志仓储接口
|
||||
type DataChangeLogRepository interface {
|
||||
CreateDataChangeLog(ctx context.Context, log *model.DataChangeLog) error
|
||||
BatchCreateDataChangeLogs(ctx context.Context, logs []*model.DataChangeLog) error
|
||||
GetDataChangeLogs(ctx context.Context, filters dto.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error)
|
||||
GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error)
|
||||
GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error)
|
||||
DeleteOldLogs(ctx context.Context, beforeDate string) error
|
||||
GetStatistics(ctx context.Context, startTime, endTime string) (*DataChangeLogStatistics, error)
|
||||
}
|
||||
|
||||
// dataChangeLogRepository 数据变更日志仓储实现
|
||||
type dataChangeLogRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDataChangeLogRepository 创建数据变更日志仓储
|
||||
func NewDataChangeLogRepository(db *gorm.DB) *DataChangeLogRepository {
|
||||
return &DataChangeLogRepository{db: db}
|
||||
func NewDataChangeLogRepository(db *gorm.DB) DataChangeLogRepository {
|
||||
return &dataChangeLogRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateDataChangeLog 创建单条数据变更日志
|
||||
func (r *DataChangeLogRepository) CreateDataChangeLog(ctx context.Context, log *model.DataChangeLog) error {
|
||||
func (r *dataChangeLogRepository) CreateDataChangeLog(ctx context.Context, log *model.DataChangeLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// BatchCreateDataChangeLogs 批量创建数据变更日志
|
||||
func (r *DataChangeLogRepository) BatchCreateDataChangeLogs(ctx context.Context, logs []*model.DataChangeLog) error {
|
||||
func (r *dataChangeLogRepository) BatchCreateDataChangeLogs(ctx context.Context, logs []*model.DataChangeLog) error {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -32,7 +44,7 @@ func (r *DataChangeLogRepository) BatchCreateDataChangeLogs(ctx context.Context,
|
||||
}
|
||||
|
||||
// GetDataChangeLogs 获取数据变更日志列表(分页)
|
||||
func (r *DataChangeLogRepository) GetDataChangeLogs(ctx context.Context, filters DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) {
|
||||
func (r *dataChangeLogRepository) GetDataChangeLogs(ctx context.Context, filters dto.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) {
|
||||
query := r.db.WithContext(ctx).Model(&model.DataChangeLog{})
|
||||
|
||||
if filters.UserID != "" {
|
||||
@@ -75,22 +87,22 @@ func (r *DataChangeLogRepository) GetDataChangeLogs(ctx context.Context, filters
|
||||
}
|
||||
|
||||
// GetDataChangeLogsByUser 获取指定用户的数据变更日志
|
||||
func (r *DataChangeLogRepository) GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) {
|
||||
return r.GetDataChangeLogs(ctx, DataChangeFilter{UserID: userID}, page, pageSize)
|
||||
func (r *dataChangeLogRepository) GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) {
|
||||
return r.GetDataChangeLogs(ctx, dto.DataChangeFilter{UserID: userID}, page, pageSize)
|
||||
}
|
||||
|
||||
// GetDataChangeLogsByOperator 获取指定操作人的日志
|
||||
func (r *DataChangeLogRepository) GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) {
|
||||
return r.GetDataChangeLogs(ctx, DataChangeFilter{OperatorID: operatorID}, page, pageSize)
|
||||
func (r *dataChangeLogRepository) GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) {
|
||||
return r.GetDataChangeLogs(ctx, dto.DataChangeFilter{OperatorID: operatorID}, page, pageSize)
|
||||
}
|
||||
|
||||
// DeleteOldLogs 删除旧的数据变更日志(用于定时清理)
|
||||
func (r *DataChangeLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error {
|
||||
func (r *dataChangeLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error {
|
||||
return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.DataChangeLog{}).Error
|
||||
}
|
||||
|
||||
// GetStatistics 获取数据变更日志统计
|
||||
func (r *DataChangeLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*DataChangeLogStatistics, error) {
|
||||
func (r *dataChangeLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*DataChangeLogStatistics, error) {
|
||||
stats := &DataChangeLogStatistics{}
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&model.DataChangeLog{})
|
||||
@@ -116,18 +128,6 @@ func (r *DataChangeLogRepository) GetStatistics(ctx context.Context, startTime,
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// DataChangeFilter 数据变更日志过滤条件
|
||||
type DataChangeFilter struct {
|
||||
UserID string
|
||||
OperatorID string
|
||||
ChangeType string
|
||||
TargetType string
|
||||
OperatorType string
|
||||
IP string
|
||||
StartTime string
|
||||
EndTime string
|
||||
}
|
||||
|
||||
// DataChangeLogStatistics 数据变更日志统计
|
||||
type DataChangeLogStatistics struct {
|
||||
TotalCount int64
|
||||
|
||||
@@ -8,23 +8,44 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DeviceTokenRepository 设备Token仓储
|
||||
type DeviceTokenRepository struct {
|
||||
// DeviceTokenRepository 设备Token仓储接口
|
||||
type DeviceTokenRepository interface {
|
||||
Create(token *model.DeviceToken) error
|
||||
GetByID(id int64) (*model.DeviceToken, error)
|
||||
Update(token *model.DeviceToken) error
|
||||
Delete(id int64) error
|
||||
GetByUserID(userID string) ([]*model.DeviceToken, error)
|
||||
GetActiveByUserID(userID string) ([]*model.DeviceToken, error)
|
||||
GetByDeviceID(deviceID string) (*model.DeviceToken, error)
|
||||
GetByPushToken(pushToken string) (*model.DeviceToken, error)
|
||||
DeactivateAllExcept(userID int64, deviceID string) error
|
||||
Upsert(token *model.DeviceToken) error
|
||||
UpdateLastUsed(deviceID string) error
|
||||
Deactivate(deviceID string) error
|
||||
Activate(deviceID string) error
|
||||
DeleteByUserID(userID int64) error
|
||||
GetDeviceCountByUserID(userID int64) (int64, error)
|
||||
GetActiveDeviceCountByUserID(userID int64) (int64, error)
|
||||
DeleteInactiveDevices(before time.Time) error
|
||||
}
|
||||
|
||||
// deviceTokenRepository 设备Token仓储实现
|
||||
type deviceTokenRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDeviceTokenRepository 创建设备Token仓储
|
||||
func NewDeviceTokenRepository(db *gorm.DB) *DeviceTokenRepository {
|
||||
return &DeviceTokenRepository{db: db}
|
||||
func NewDeviceTokenRepository(db *gorm.DB) DeviceTokenRepository {
|
||||
return &deviceTokenRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建设备Token
|
||||
func (r *DeviceTokenRepository) Create(token *model.DeviceToken) error {
|
||||
func (r *deviceTokenRepository) Create(token *model.DeviceToken) error {
|
||||
return r.db.Create(token).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取设备Token
|
||||
func (r *DeviceTokenRepository) GetByID(id int64) (*model.DeviceToken, error) {
|
||||
func (r *deviceTokenRepository) GetByID(id int64) (*model.DeviceToken, error) {
|
||||
var token model.DeviceToken
|
||||
err := r.db.First(&token, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -34,18 +55,18 @@ func (r *DeviceTokenRepository) GetByID(id int64) (*model.DeviceToken, error) {
|
||||
}
|
||||
|
||||
// Update 更新设备Token
|
||||
func (r *DeviceTokenRepository) Update(token *model.DeviceToken) error {
|
||||
func (r *deviceTokenRepository) Update(token *model.DeviceToken) error {
|
||||
return r.db.Save(token).Error
|
||||
}
|
||||
|
||||
// Delete 删除设备Token(软删除)
|
||||
func (r *DeviceTokenRepository) Delete(id int64) error {
|
||||
func (r *deviceTokenRepository) Delete(id int64) error {
|
||||
return r.db.Delete(&model.DeviceToken{}, id).Error
|
||||
}
|
||||
|
||||
// GetByUserID 获取用户所有设备
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *DeviceTokenRepository) GetByUserID(userID string) ([]*model.DeviceToken, error) {
|
||||
func (r *deviceTokenRepository) GetByUserID(userID string) ([]*model.DeviceToken, error) {
|
||||
var tokens []*model.DeviceToken
|
||||
err := r.db.Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
@@ -55,7 +76,7 @@ func (r *DeviceTokenRepository) GetByUserID(userID string) ([]*model.DeviceToken
|
||||
|
||||
// GetActiveByUserID 获取用户活跃设备
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *DeviceTokenRepository) GetActiveByUserID(userID string) ([]*model.DeviceToken, error) {
|
||||
func (r *deviceTokenRepository) GetActiveByUserID(userID string) ([]*model.DeviceToken, error) {
|
||||
var tokens []*model.DeviceToken
|
||||
err := r.db.Where("user_id = ? AND is_active = ?", userID, true).
|
||||
Order("last_used_at DESC").
|
||||
@@ -64,7 +85,7 @@ func (r *DeviceTokenRepository) GetActiveByUserID(userID string) ([]*model.Devic
|
||||
}
|
||||
|
||||
// GetByDeviceID 根据设备ID获取设备Token
|
||||
func (r *DeviceTokenRepository) GetByDeviceID(deviceID string) (*model.DeviceToken, error) {
|
||||
func (r *deviceTokenRepository) GetByDeviceID(deviceID string) (*model.DeviceToken, error) {
|
||||
var token model.DeviceToken
|
||||
err := r.db.Where("device_id = ?", deviceID).First(&token).Error
|
||||
if err != nil {
|
||||
@@ -74,7 +95,7 @@ func (r *DeviceTokenRepository) GetByDeviceID(deviceID string) (*model.DeviceTok
|
||||
}
|
||||
|
||||
// GetByPushToken 根据推送Token获取设备信息
|
||||
func (r *DeviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceToken, error) {
|
||||
func (r *deviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceToken, error) {
|
||||
var token model.DeviceToken
|
||||
err := r.db.Where("push_token = ?", pushToken).First(&token).Error
|
||||
if err != nil {
|
||||
@@ -84,7 +105,7 @@ func (r *DeviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceT
|
||||
}
|
||||
|
||||
// DeactivateAllExcept 登出其他设备(停用除指定设备外的所有设备)
|
||||
func (r *DeviceTokenRepository) DeactivateAllExcept(userID int64, deviceID string) error {
|
||||
func (r *deviceTokenRepository) DeactivateAllExcept(userID int64, deviceID string) error {
|
||||
return r.db.Model(&model.DeviceToken{}).
|
||||
Where("user_id = ? AND device_id != ?", userID, deviceID).
|
||||
Update("is_active", false).Error
|
||||
@@ -92,7 +113,7 @@ func (r *DeviceTokenRepository) DeactivateAllExcept(userID int64, deviceID strin
|
||||
|
||||
// Upsert 创建或更新设备Token
|
||||
// 如果设备ID已存在,则更新Token和激活状态;否则创建新记录
|
||||
func (r *DeviceTokenRepository) Upsert(token *model.DeviceToken) error {
|
||||
func (r *deviceTokenRepository) Upsert(token *model.DeviceToken) error {
|
||||
var existing model.DeviceToken
|
||||
err := r.db.Where("device_id = ?", token.DeviceID).First(&existing).Error
|
||||
|
||||
@@ -113,21 +134,21 @@ func (r *DeviceTokenRepository) Upsert(token *model.DeviceToken) error {
|
||||
}
|
||||
|
||||
// UpdateLastUsed 更新最后使用时间
|
||||
func (r *DeviceTokenRepository) UpdateLastUsed(deviceID string) error {
|
||||
func (r *deviceTokenRepository) UpdateLastUsed(deviceID string) error {
|
||||
return r.db.Model(&model.DeviceToken{}).
|
||||
Where("device_id = ?", deviceID).
|
||||
Update("last_used_at", time.Now()).Error
|
||||
}
|
||||
|
||||
// Deactivate 停用设备
|
||||
func (r *DeviceTokenRepository) Deactivate(deviceID string) error {
|
||||
func (r *deviceTokenRepository) Deactivate(deviceID string) error {
|
||||
return r.db.Model(&model.DeviceToken{}).
|
||||
Where("device_id = ?", deviceID).
|
||||
Update("is_active", false).Error
|
||||
}
|
||||
|
||||
// Activate 激活设备
|
||||
func (r *DeviceTokenRepository) Activate(deviceID string) error {
|
||||
func (r *deviceTokenRepository) Activate(deviceID string) error {
|
||||
return r.db.Model(&model.DeviceToken{}).
|
||||
Where("device_id = ?", deviceID).
|
||||
Updates(map[string]interface{}{
|
||||
@@ -137,12 +158,12 @@ func (r *DeviceTokenRepository) Activate(deviceID string) error {
|
||||
}
|
||||
|
||||
// DeleteByUserID 删除用户所有设备Token
|
||||
func (r *DeviceTokenRepository) DeleteByUserID(userID int64) error {
|
||||
func (r *deviceTokenRepository) DeleteByUserID(userID int64) error {
|
||||
return r.db.Where("user_id = ?", userID).Delete(&model.DeviceToken{}).Error
|
||||
}
|
||||
|
||||
// GetDeviceCountByUserID 获取用户设备数量
|
||||
func (r *DeviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, error) {
|
||||
func (r *deviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.DeviceToken{}).
|
||||
Where("user_id = ?", userID).
|
||||
@@ -151,7 +172,7 @@ func (r *DeviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, err
|
||||
}
|
||||
|
||||
// GetActiveDeviceCountByUserID 获取用户活跃设备数量
|
||||
func (r *DeviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int64, error) {
|
||||
func (r *deviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.DeviceToken{}).
|
||||
Where("user_id = ? AND is_active = ?", userID, true).
|
||||
@@ -160,7 +181,7 @@ func (r *DeviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int6
|
||||
}
|
||||
|
||||
// DeleteInactiveDevices 删除长时间未使用的设备
|
||||
func (r *DeviceTokenRepository) DeleteInactiveDevices(before time.Time) error {
|
||||
func (r *deviceTokenRepository) DeleteInactiveDevices(before time.Time) error {
|
||||
return r.db.Where("is_active = ? AND last_used_at < ?", false, before).
|
||||
Delete(&model.DeviceToken{}).Error
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type GroupRepository interface {
|
||||
Update(group *model.Group) error
|
||||
Delete(id string) error
|
||||
GetByOwnerID(ownerID string, page, pageSize int) ([]model.Group, int64, error)
|
||||
TransferOwner(groupID, oldOwnerID, newOwnerID string) error
|
||||
|
||||
// 群成员操作
|
||||
AddMember(member *model.GroupMember) error
|
||||
@@ -90,6 +91,32 @@ func (r *groupRepository) Update(group *model.Group) error {
|
||||
return r.db.Save(group).Error
|
||||
}
|
||||
|
||||
// TransferOwner 转让群主(在事务中更新群主和角色)
|
||||
func (r *groupRepository) TransferOwner(groupID, oldOwnerID, newOwnerID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 更新群组的群主
|
||||
if err := tx.Model(&model.Group{}).Where("id = ?", groupID).Update("owner_id", newOwnerID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新原群主为管理员
|
||||
if err := tx.Model(&model.GroupMember{}).
|
||||
Where("group_id = ? AND user_id = ?", groupID, oldOwnerID).
|
||||
Update("role", model.GroupRoleAdmin).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新新群主为群主
|
||||
if err := tx.Model(&model.GroupMember{}).
|
||||
Where("group_id = ? AND user_id = ?", groupID, newOwnerID).
|
||||
Update("role", model.GroupRoleOwner).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除群组
|
||||
func (r *groupRepository) Delete(id string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
@@ -4,28 +4,41 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LoginLogRepository 登录日志仓储
|
||||
type LoginLogRepository struct {
|
||||
// LoginLogRepository 登录日志仓储接口
|
||||
type LoginLogRepository interface {
|
||||
CreateLoginLog(ctx context.Context, log *model.LoginLog) error
|
||||
BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error
|
||||
GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error)
|
||||
GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error)
|
||||
GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error)
|
||||
GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error)
|
||||
DeleteOldLogs(ctx context.Context, beforeDate string) error
|
||||
GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error)
|
||||
}
|
||||
|
||||
// loginLogRepository 登录日志仓储实现
|
||||
type loginLogRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewLoginLogRepository 创建登录日志仓储
|
||||
func NewLoginLogRepository(db *gorm.DB) *LoginLogRepository {
|
||||
return &LoginLogRepository{db: db}
|
||||
func NewLoginLogRepository(db *gorm.DB) LoginLogRepository {
|
||||
return &loginLogRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateLoginLog 创建单条登录日志
|
||||
func (r *LoginLogRepository) CreateLoginLog(ctx context.Context, log *model.LoginLog) error {
|
||||
func (r *loginLogRepository) CreateLoginLog(ctx context.Context, log *model.LoginLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// BatchCreateLoginLogs 批量创建登录日志
|
||||
func (r *LoginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error {
|
||||
func (r *loginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -33,7 +46,7 @@ func (r *LoginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*m
|
||||
}
|
||||
|
||||
// GetLoginLogs 获取登录日志列表(分页)
|
||||
func (r *LoginLogRepository) GetLoginLogs(ctx context.Context, filters LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) {
|
||||
func (r *loginLogRepository) GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) {
|
||||
query := r.db.WithContext(ctx).Model(&model.LoginLog{})
|
||||
|
||||
if filters.UserID != "" {
|
||||
@@ -76,12 +89,12 @@ func (r *LoginLogRepository) GetLoginLogs(ctx context.Context, filters LoginFilt
|
||||
}
|
||||
|
||||
// GetLoginLogsByUser 获取指定用户的登录日志
|
||||
func (r *LoginLogRepository) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) {
|
||||
return r.GetLoginLogs(ctx, LoginFilter{UserID: userID}, page, pageSize)
|
||||
func (r *loginLogRepository) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) {
|
||||
return r.GetLoginLogs(ctx, dto.LoginFilter{UserID: userID}, page, pageSize)
|
||||
}
|
||||
|
||||
// GetFailedLoginsByIP 获取指定IP的失败登录记录(用于风控)
|
||||
func (r *LoginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error) {
|
||||
func (r *loginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error) {
|
||||
startTime := time.Now().Add(-timeWindow)
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).
|
||||
@@ -92,7 +105,7 @@ func (r *LoginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string,
|
||||
}
|
||||
|
||||
// GetRecentSuccessLogins 获取最近成功的登录记录
|
||||
func (r *LoginLogRepository) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) {
|
||||
func (r *loginLogRepository) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) {
|
||||
var logs []*model.LoginLog
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("user_id = ? AND result = ? AND event = ?", userID, string(model.LoginResultSuccess), string(model.LoginEventLogin)).
|
||||
@@ -103,12 +116,12 @@ func (r *LoginLogRepository) GetRecentSuccessLogins(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// DeleteOldLogs 删除旧的登录日志(用于定时清理)
|
||||
func (r *LoginLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error {
|
||||
func (r *loginLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error {
|
||||
return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.LoginLog{}).Error
|
||||
}
|
||||
|
||||
// GetStatistics 获取登录日志统计(使用单次 GROUP BY 查询,避免多次 COUNT)
|
||||
func (r *LoginLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) {
|
||||
func (r *loginLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) {
|
||||
stats := &LoginLogStatistics{}
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&model.LoginLog{})
|
||||
@@ -155,18 +168,6 @@ func (r *LoginLogRepository) GetStatistics(ctx context.Context, startTime, endTi
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// LoginFilter 登录日志过滤条件
|
||||
type LoginFilter struct {
|
||||
UserID string
|
||||
Event string
|
||||
Result string
|
||||
FailReason string
|
||||
LoginType string
|
||||
IP string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
}
|
||||
|
||||
// LoginLogStatistics 登录日志统计
|
||||
type LoginLogStatistics struct {
|
||||
TotalCount int64
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MaterialSubjectRepository 学科分类仓储
|
||||
type MaterialSubjectRepository struct {
|
||||
// MaterialSubjectRepository 学科分类仓储接口
|
||||
type MaterialSubjectRepository interface {
|
||||
ListActive() ([]*model.MaterialSubject, error)
|
||||
ListAll() ([]*model.MaterialSubject, error)
|
||||
GetByID(id string) (*model.MaterialSubject, error)
|
||||
Create(subject *model.MaterialSubject) error
|
||||
Update(subject *model.MaterialSubject) error
|
||||
Delete(id string) error
|
||||
CountMaterials(subjectID string) (int64, error)
|
||||
}
|
||||
|
||||
// materialSubjectRepository 学科分类仓储实现
|
||||
type materialSubjectRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewMaterialSubjectRepository(db *gorm.DB) *MaterialSubjectRepository {
|
||||
return &MaterialSubjectRepository{db: db}
|
||||
func NewMaterialSubjectRepository(db *gorm.DB) MaterialSubjectRepository {
|
||||
return &materialSubjectRepository{db: db}
|
||||
}
|
||||
|
||||
// ListActive 获取所有启用的学科分类
|
||||
func (r *MaterialSubjectRepository) ListActive() ([]*model.MaterialSubject, error) {
|
||||
func (r *materialSubjectRepository) ListActive() ([]*model.MaterialSubject, error) {
|
||||
var subjects []*model.MaterialSubject
|
||||
err := r.db.
|
||||
Where("is_active = ?", true).
|
||||
@@ -26,7 +38,7 @@ func (r *MaterialSubjectRepository) ListActive() ([]*model.MaterialSubject, erro
|
||||
}
|
||||
|
||||
// ListAll 获取所有学科分类(管理端用)
|
||||
func (r *MaterialSubjectRepository) ListAll() ([]*model.MaterialSubject, error) {
|
||||
func (r *materialSubjectRepository) ListAll() ([]*model.MaterialSubject, error) {
|
||||
var subjects []*model.MaterialSubject
|
||||
err := r.db.
|
||||
Order("sort_order ASC, created_at ASC").
|
||||
@@ -35,7 +47,7 @@ func (r *MaterialSubjectRepository) ListAll() ([]*model.MaterialSubject, error)
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取学科分类
|
||||
func (r *MaterialSubjectRepository) GetByID(id string) (*model.MaterialSubject, error) {
|
||||
func (r *materialSubjectRepository) GetByID(id string) (*model.MaterialSubject, error) {
|
||||
var subject model.MaterialSubject
|
||||
if err := r.db.First(&subject, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -44,50 +56,53 @@ func (r *MaterialSubjectRepository) GetByID(id string) (*model.MaterialSubject,
|
||||
}
|
||||
|
||||
// Create 创建学科分类
|
||||
func (r *MaterialSubjectRepository) Create(subject *model.MaterialSubject) error {
|
||||
func (r *materialSubjectRepository) Create(subject *model.MaterialSubject) error {
|
||||
return r.db.Create(subject).Error
|
||||
}
|
||||
|
||||
// Update 更新学科分类
|
||||
func (r *MaterialSubjectRepository) Update(subject *model.MaterialSubject) error {
|
||||
func (r *materialSubjectRepository) Update(subject *model.MaterialSubject) error {
|
||||
return r.db.Save(subject).Error
|
||||
}
|
||||
|
||||
// Delete 删除学科分类
|
||||
func (r *MaterialSubjectRepository) Delete(id string) error {
|
||||
func (r *materialSubjectRepository) Delete(id string) error {
|
||||
return r.db.Delete(&model.MaterialSubject{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// CountMaterials 统计学科下的资料数量
|
||||
func (r *MaterialSubjectRepository) CountMaterials(subjectID string) (int64, error) {
|
||||
func (r *materialSubjectRepository) CountMaterials(subjectID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.MaterialFile{}).Where("subject_id = ?", subjectID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// MaterialFileRepository 文件资料仓储
|
||||
type MaterialFileRepository struct {
|
||||
// MaterialFileRepository 文件资料仓储接口
|
||||
type MaterialFileRepository interface {
|
||||
List(params dto.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error)
|
||||
GetByID(id string) (*model.MaterialFile, error)
|
||||
GetByIDWithSubject(id string) (*model.MaterialFile, error)
|
||||
Create(file *model.MaterialFile) error
|
||||
Update(file *model.MaterialFile) error
|
||||
Delete(id string) error
|
||||
IncrementDownloadCount(id string) error
|
||||
GetBySubjectID(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error)
|
||||
Search(keyword string, page, pageSize int) ([]*model.MaterialFile, int64, error)
|
||||
GetHotMaterials(limit int) ([]*model.MaterialFile, error)
|
||||
GetLatestMaterials(limit int) ([]*model.MaterialFile, error)
|
||||
}
|
||||
|
||||
// materialFileRepository 文件资料仓储实现
|
||||
type materialFileRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewMaterialFileRepository(db *gorm.DB) *MaterialFileRepository {
|
||||
return &MaterialFileRepository{db: db}
|
||||
}
|
||||
|
||||
// ListQueryParams 查询参数
|
||||
type MaterialFileQueryParams struct {
|
||||
SubjectID string
|
||||
FileType string
|
||||
Status string
|
||||
Keyword string
|
||||
Page int
|
||||
PageSize int
|
||||
SortBy string
|
||||
SortOrder string
|
||||
func NewMaterialFileRepository(db *gorm.DB) MaterialFileRepository {
|
||||
return &materialFileRepository{db: db}
|
||||
}
|
||||
|
||||
// List 获取资料列表(支持筛选和分页)
|
||||
func (r *MaterialFileRepository) List(params MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) {
|
||||
func (r *materialFileRepository) List(params dto.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) {
|
||||
var files []*model.MaterialFile
|
||||
var total int64
|
||||
|
||||
@@ -144,7 +159,7 @@ func (r *MaterialFileRepository) List(params MaterialFileQueryParams) ([]*model.
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取资料详情
|
||||
func (r *MaterialFileRepository) GetByID(id string) (*model.MaterialFile, error) {
|
||||
func (r *materialFileRepository) GetByID(id string) (*model.MaterialFile, error) {
|
||||
var file model.MaterialFile
|
||||
if err := r.db.First(&file, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -153,7 +168,7 @@ func (r *MaterialFileRepository) GetByID(id string) (*model.MaterialFile, error)
|
||||
}
|
||||
|
||||
// GetByIDWithSubject 根据ID获取资料详情(包含学科信息)
|
||||
func (r *MaterialFileRepository) GetByIDWithSubject(id string) (*model.MaterialFile, error) {
|
||||
func (r *materialFileRepository) GetByIDWithSubject(id string) (*model.MaterialFile, error) {
|
||||
var file model.MaterialFile
|
||||
if err := r.db.Preload("Subject").First(&file, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -162,28 +177,28 @@ func (r *MaterialFileRepository) GetByIDWithSubject(id string) (*model.MaterialF
|
||||
}
|
||||
|
||||
// Create 创建资料
|
||||
func (r *MaterialFileRepository) Create(file *model.MaterialFile) error {
|
||||
func (r *materialFileRepository) Create(file *model.MaterialFile) error {
|
||||
return r.db.Create(file).Error
|
||||
}
|
||||
|
||||
// Update 更新资料
|
||||
func (r *MaterialFileRepository) Update(file *model.MaterialFile) error {
|
||||
func (r *materialFileRepository) Update(file *model.MaterialFile) error {
|
||||
return r.db.Save(file).Error
|
||||
}
|
||||
|
||||
// Delete 删除资料
|
||||
func (r *MaterialFileRepository) Delete(id string) error {
|
||||
func (r *materialFileRepository) Delete(id string) error {
|
||||
return r.db.Delete(&model.MaterialFile{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// IncrementDownloadCount 增加下载次数
|
||||
func (r *MaterialFileRepository) IncrementDownloadCount(id string) error {
|
||||
func (r *materialFileRepository) IncrementDownloadCount(id string) error {
|
||||
return r.db.Model(&model.MaterialFile{}).Where("id = ?", id).
|
||||
UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error
|
||||
}
|
||||
|
||||
// GetBySubjectID 根据学科ID获取资料列表
|
||||
func (r *MaterialFileRepository) GetBySubjectID(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error) {
|
||||
func (r *materialFileRepository) GetBySubjectID(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error) {
|
||||
var files []*model.MaterialFile
|
||||
var total int64
|
||||
|
||||
@@ -202,7 +217,7 @@ func (r *MaterialFileRepository) GetBySubjectID(subjectID string, page, pageSize
|
||||
}
|
||||
|
||||
// Search 搜索资料
|
||||
func (r *MaterialFileRepository) Search(keyword string, page, pageSize int) ([]*model.MaterialFile, int64, error) {
|
||||
func (r *materialFileRepository) Search(keyword string, page, pageSize int) ([]*model.MaterialFile, int64, error) {
|
||||
var files []*model.MaterialFile
|
||||
var total int64
|
||||
|
||||
@@ -225,7 +240,7 @@ func (r *MaterialFileRepository) Search(keyword string, page, pageSize int) ([]*
|
||||
}
|
||||
|
||||
// GetHotMaterials 获取热门资料(按下载次数排序)
|
||||
func (r *MaterialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFile, error) {
|
||||
func (r *materialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFile, error) {
|
||||
var files []*model.MaterialFile
|
||||
err := r.db.
|
||||
Where("status = ?", model.MaterialStatusActive).
|
||||
@@ -236,7 +251,7 @@ func (r *MaterialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFi
|
||||
}
|
||||
|
||||
// GetLatestMaterials 获取最新资料
|
||||
func (r *MaterialFileRepository) GetLatestMaterials(limit int) ([]*model.MaterialFile, error) {
|
||||
func (r *materialFileRepository) GetLatestMaterials(limit int) ([]*model.MaterialFile, error) {
|
||||
var files []*model.MaterialFile
|
||||
err := r.db.
|
||||
Where("status = ?", model.MaterialStatusActive).
|
||||
|
||||
@@ -14,23 +14,64 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// MessageRepository 消息仓储
|
||||
type MessageRepository struct {
|
||||
// MessageRepository 消息仓储接口
|
||||
type MessageRepository interface {
|
||||
CreateMessage(msg *model.Message) error
|
||||
GetConversation(id string) (*model.Conversation, error)
|
||||
GetOrCreatePrivateConversation(user1ID, user2ID string) (*model.Conversation, error)
|
||||
CreateConversationWithParticipants(conv *model.Conversation, participants []string) error
|
||||
GetConversations(userID string, page, pageSize int) ([]*model.Conversation, int64, error)
|
||||
GetMessages(conversationID string, page, pageSize int) ([]*model.Message, int64, error)
|
||||
GetMessagesAfterSeq(conversationID string, afterSeq int64, limit int) ([]*model.Message, error)
|
||||
GetMessagesBeforeSeq(conversationID string, beforeSeq int64, limit int) ([]*model.Message, error)
|
||||
GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error)
|
||||
GetParticipant(conversationID string, userID string) (*model.ConversationParticipant, error)
|
||||
UpdateLastReadSeq(conversationID string, userID string, lastReadSeq int64) error
|
||||
UpdatePinned(conversationID string, userID string, isPinned bool) error
|
||||
GetUnreadCount(conversationID string, userID string) (int64, error)
|
||||
UpdateConversationLastSeq(conversationID string, seq int64) error
|
||||
GetNextSeq(conversationID string) (int64, error)
|
||||
CreateMessageWithSeq(msg *model.Message) error
|
||||
GetAllUnreadCount(userID string) (int64, error)
|
||||
GetMessageByID(messageID string) (*model.Message, error)
|
||||
CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error)
|
||||
UpdateMessageStatus(messageID string, status model.MessageStatus) error
|
||||
RecallMessage(messageID string, userID string) error
|
||||
GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error)
|
||||
GetSystemMessagesUnreadCount(userID string) (int64, error)
|
||||
MarkAllSystemMessagesAsRead(userID string) error
|
||||
GetConversationByGroupID(groupID string) (*model.Conversation, error)
|
||||
RemoveParticipant(conversationID string, userID string) error
|
||||
AddParticipant(conversationID string, userID string) error
|
||||
DeleteConversationByGroupID(groupID string) error
|
||||
HideConversationForUser(conversationID, userID string) error
|
||||
BatchWriteMessages(ctx context.Context, messages []*model.Message) error
|
||||
BatchUpdateParticipants(ctx context.Context, updates []ParticipantUpdate) error
|
||||
UpdateConversationLastSeqWithContext(ctx context.Context, convID string, lastSeq int64, lastMsgTime time.Time) error
|
||||
BatchWriteMessagesWithTx(tx *gorm.DB, messages []*model.Message) error
|
||||
BatchUpdateParticipantsWithTx(tx *gorm.DB, updates []ParticipantUpdate) error
|
||||
UpdateConversationLastSeqWithTx(tx *gorm.DB, convID string, lastSeq int64, lastMsgTime time.Time) error
|
||||
GetMessagesByCursor(ctx context.Context, conversationID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Message], error)
|
||||
GetConversationsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Conversation], error)
|
||||
}
|
||||
|
||||
// messageRepository 消息仓储实现
|
||||
type messageRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewMessageRepository 创建消息仓储
|
||||
func NewMessageRepository(db *gorm.DB) *MessageRepository {
|
||||
return &MessageRepository{db: db}
|
||||
func NewMessageRepository(db *gorm.DB) MessageRepository {
|
||||
return &messageRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateMessage 创建消息
|
||||
func (r *MessageRepository) CreateMessage(msg *model.Message) error {
|
||||
func (r *messageRepository) CreateMessage(msg *model.Message) error {
|
||||
return r.db.Create(msg).Error
|
||||
}
|
||||
|
||||
// GetConversation 获取会话
|
||||
func (r *MessageRepository) GetConversation(id string) (*model.Conversation, error) {
|
||||
func (r *messageRepository) GetConversation(id string) (*model.Conversation, error) {
|
||||
var conv model.Conversation
|
||||
err := r.db.Preload("Group").First(&conv, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -39,10 +80,32 @@ func (r *MessageRepository) GetConversation(id string) (*model.Conversation, err
|
||||
return &conv, nil
|
||||
}
|
||||
|
||||
// CreateConversationWithParticipants 创建会话并添加参与者(在事务中)
|
||||
func (r *messageRepository) CreateConversationWithParticipants(conv *model.Conversation, participants []string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(conv).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, userID := range participants {
|
||||
participant := model.ConversationParticipant{
|
||||
ConversationID: conv.ID,
|
||||
UserID: userID,
|
||||
LastReadSeq: 0,
|
||||
}
|
||||
if err := tx.Create(&participant).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetOrCreatePrivateConversation 获取或创建私聊会话
|
||||
// 使用参与者关系表来管理会话
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *MessageRepository) GetOrCreatePrivateConversation(user1ID, user2ID string) (*model.Conversation, error) {
|
||||
func (r *messageRepository) GetOrCreatePrivateConversation(user1ID, user2ID string) (*model.Conversation, error) {
|
||||
var conv model.Conversation
|
||||
|
||||
// 查找两个用户共同参与的私聊会话
|
||||
@@ -91,7 +154,7 @@ func (r *MessageRepository) GetOrCreatePrivateConversation(user1ID, user2ID stri
|
||||
|
||||
// GetConversations 获取用户会话列表
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *MessageRepository) GetConversations(userID string, page, pageSize int) ([]*model.Conversation, int64, error) {
|
||||
func (r *messageRepository) GetConversations(userID string, page, pageSize int) ([]*model.Conversation, int64, error) {
|
||||
var convs []*model.Conversation
|
||||
var total int64
|
||||
|
||||
@@ -121,7 +184,7 @@ func (r *MessageRepository) GetConversations(userID string, page, pageSize int)
|
||||
}
|
||||
|
||||
// GetMessages 获取会话消息
|
||||
func (r *MessageRepository) GetMessages(conversationID string, page, pageSize int) ([]*model.Message, int64, error) {
|
||||
func (r *messageRepository) GetMessages(conversationID string, page, pageSize int) ([]*model.Message, int64, error) {
|
||||
var messages []*model.Message
|
||||
var total int64
|
||||
|
||||
@@ -142,7 +205,7 @@ func (r *MessageRepository) GetMessages(conversationID string, page, pageSize in
|
||||
}
|
||||
|
||||
// GetMessagesAfterSeq 获取指定seq之后的消息(用于增量同步)
|
||||
func (r *MessageRepository) GetMessagesAfterSeq(conversationID string, afterSeq int64, limit int) ([]*model.Message, error) {
|
||||
func (r *messageRepository) GetMessagesAfterSeq(conversationID string, afterSeq int64, limit int) ([]*model.Message, error) {
|
||||
var messages []*model.Message
|
||||
err := r.db.Where("conversation_id = ? AND seq > ?", conversationID, afterSeq).
|
||||
Order("seq ASC").
|
||||
@@ -157,7 +220,7 @@ func (r *MessageRepository) GetMessagesAfterSeq(conversationID string, afterSeq
|
||||
}
|
||||
|
||||
// GetMessagesBeforeSeq 获取指定seq之前的历史消息(用于下拉加载更多)
|
||||
func (r *MessageRepository) GetMessagesBeforeSeq(conversationID string, beforeSeq int64, limit int) ([]*model.Message, error) {
|
||||
func (r *messageRepository) GetMessagesBeforeSeq(conversationID string, beforeSeq int64, limit int) ([]*model.Message, error) {
|
||||
var messages []*model.Message
|
||||
err := r.db.Where("conversation_id = ? AND seq < ?", conversationID, beforeSeq).
|
||||
Order("seq DESC"). // 降序获取最新消息在前
|
||||
@@ -176,7 +239,7 @@ func (r *MessageRepository) GetMessagesBeforeSeq(conversationID string, beforeSe
|
||||
}
|
||||
|
||||
// GetConversationParticipants 获取会话参与者
|
||||
func (r *MessageRepository) GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error) {
|
||||
func (r *messageRepository) GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error) {
|
||||
var participants []*model.ConversationParticipant
|
||||
err := r.db.Where("conversation_id = ?", conversationID).Find(&participants).Error
|
||||
return participants, err
|
||||
@@ -184,7 +247,7 @@ func (r *MessageRepository) GetConversationParticipants(conversationID string) (
|
||||
|
||||
// GetParticipant 获取用户在会话中的参与者信息
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *MessageRepository) GetParticipant(conversationID string, userID string) (*model.ConversationParticipant, error) {
|
||||
func (r *messageRepository) GetParticipant(conversationID string, userID string) (*model.ConversationParticipant, error) {
|
||||
var participant model.ConversationParticipant
|
||||
err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error
|
||||
if err != nil {
|
||||
@@ -211,7 +274,7 @@ func (r *MessageRepository) GetParticipant(conversationID string, userID string)
|
||||
|
||||
// UpdateLastReadSeq 更新已读位置
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *MessageRepository) UpdateLastReadSeq(conversationID string, userID string, lastReadSeq int64) error {
|
||||
func (r *messageRepository) UpdateLastReadSeq(conversationID string, userID string, lastReadSeq int64) error {
|
||||
result := r.db.Model(&model.ConversationParticipant{}).
|
||||
Where("conversation_id = ? AND user_id = ?", conversationID, userID).
|
||||
Update("last_read_seq", lastReadSeq)
|
||||
@@ -246,7 +309,7 @@ func (r *MessageRepository) UpdateLastReadSeq(conversationID string, userID stri
|
||||
}
|
||||
|
||||
// UpdatePinned 更新会话置顶状态(用户维度)
|
||||
func (r *MessageRepository) UpdatePinned(conversationID string, userID string, isPinned bool) error {
|
||||
func (r *messageRepository) UpdatePinned(conversationID string, userID string, isPinned bool) error {
|
||||
result := r.db.Model(&model.ConversationParticipant{}).
|
||||
Where("conversation_id = ? AND user_id = ?", conversationID, userID).
|
||||
Update("is_pinned", isPinned)
|
||||
@@ -277,7 +340,7 @@ func (r *MessageRepository) UpdatePinned(conversationID string, userID string, i
|
||||
|
||||
// GetUnreadCount 获取未读消息数
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *MessageRepository) GetUnreadCount(conversationID string, userID string) (int64, error) {
|
||||
func (r *messageRepository) GetUnreadCount(conversationID string, userID string) (int64, error) {
|
||||
var participant model.ConversationParticipant
|
||||
err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error
|
||||
if err != nil {
|
||||
@@ -292,7 +355,7 @@ func (r *MessageRepository) GetUnreadCount(conversationID string, userID string)
|
||||
}
|
||||
|
||||
// UpdateConversationLastSeq 更新会话的最后消息seq和时间
|
||||
func (r *MessageRepository) UpdateConversationLastSeq(conversationID string, seq int64) error {
|
||||
func (r *messageRepository) UpdateConversationLastSeq(conversationID string, seq int64) error {
|
||||
return r.db.Model(&model.Conversation{}).
|
||||
Where("id = ?", conversationID).
|
||||
Updates(map[string]interface{}{
|
||||
@@ -302,7 +365,7 @@ func (r *MessageRepository) UpdateConversationLastSeq(conversationID string, seq
|
||||
}
|
||||
|
||||
// GetNextSeq 获取会话的下一个seq值
|
||||
func (r *MessageRepository) GetNextSeq(conversationID string) (int64, error) {
|
||||
func (r *messageRepository) GetNextSeq(conversationID string) (int64, error) {
|
||||
var conv model.Conversation
|
||||
err := r.db.Select("last_seq").Where("id = ?", conversationID).First(&conv).Error
|
||||
if err != nil {
|
||||
@@ -312,7 +375,7 @@ func (r *MessageRepository) GetNextSeq(conversationID string) (int64, error) {
|
||||
}
|
||||
|
||||
// CreateMessageWithSeq 创建消息并更新seq(事务操作)
|
||||
func (r *MessageRepository) CreateMessageWithSeq(msg *model.Message) error {
|
||||
func (r *messageRepository) CreateMessageWithSeq(msg *model.Message) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 获取当前seq并+1
|
||||
var conv model.Conversation
|
||||
@@ -350,7 +413,7 @@ func (r *MessageRepository) CreateMessageWithSeq(msg *model.Message) error {
|
||||
|
||||
// GetAllUnreadCount 获取用户所有会话的未读消息总数
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *MessageRepository) GetAllUnreadCount(userID string) (int64, error) {
|
||||
func (r *messageRepository) GetAllUnreadCount(userID string) (int64, error) {
|
||||
var totalUnread int64
|
||||
err := r.db.Table("conversation_participants AS cp").
|
||||
Joins("LEFT JOIN messages AS m ON m.conversation_id = cp.conversation_id AND m.sender_id <> ? AND m.seq > cp.last_read_seq AND m.deleted_at IS NULL", userID).
|
||||
@@ -361,7 +424,7 @@ func (r *MessageRepository) GetAllUnreadCount(userID string) (int64, error) {
|
||||
}
|
||||
|
||||
// GetMessageByID 根据ID获取消息
|
||||
func (r *MessageRepository) GetMessageByID(messageID string) (*model.Message, error) {
|
||||
func (r *messageRepository) GetMessageByID(messageID string) (*model.Message, error) {
|
||||
var message model.Message
|
||||
err := r.db.First(&message, "id = ?", messageID).Error
|
||||
if err != nil {
|
||||
@@ -373,7 +436,7 @@ func (r *MessageRepository) GetMessageByID(messageID string) (*model.Message, er
|
||||
}
|
||||
|
||||
// CountMessagesBySenderInConversation 统计会话中某用户已发送消息数
|
||||
func (r *MessageRepository) CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error) {
|
||||
func (r *messageRepository) CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Message{}).
|
||||
Where("conversation_id = ? AND sender_id = ?", conversationID, senderID).
|
||||
@@ -382,15 +445,25 @@ func (r *MessageRepository) CountMessagesBySenderInConversation(conversationID,
|
||||
}
|
||||
|
||||
// UpdateMessageStatus 更新消息状态
|
||||
func (r *MessageRepository) UpdateMessageStatus(messageID int64, status model.MessageStatus) error {
|
||||
func (r *messageRepository) UpdateMessageStatus(messageID string, status model.MessageStatus) error {
|
||||
return r.db.Model(&model.Message{}).
|
||||
Where("id = ?", messageID).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// RecallMessage 撤回消息(2分钟内)
|
||||
func (r *messageRepository) RecallMessage(messageID string, userID string) error {
|
||||
return r.db.Model(&model.Message{}).
|
||||
Where("id = ? AND sender_id = ?", messageID, userID).
|
||||
Updates(map[string]interface{}{
|
||||
"status": model.MessageStatusRecalled,
|
||||
"segments": model.MessageSegments{},
|
||||
}).Error
|
||||
}
|
||||
|
||||
// GetOrCreateSystemParticipant 获取或创建用户在系统会话中的参与者记录
|
||||
// 系统会话是虚拟会话,但需要参与者记录来跟踪已读状态
|
||||
func (r *MessageRepository) GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error) {
|
||||
func (r *messageRepository) GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error) {
|
||||
var participant model.ConversationParticipant
|
||||
err := r.db.Where("conversation_id = ? AND user_id = ?",
|
||||
model.SystemConversationID, userID).First(&participant).Error
|
||||
@@ -418,7 +491,7 @@ func (r *MessageRepository) GetOrCreateSystemParticipant(userID string) (*model.
|
||||
}
|
||||
|
||||
// GetSystemMessagesUnreadCount 获取系统消息未读数
|
||||
func (r *MessageRepository) GetSystemMessagesUnreadCount(userID string) (int64, error) {
|
||||
func (r *messageRepository) GetSystemMessagesUnreadCount(userID string) (int64, error) {
|
||||
// 获取或创建参与者记录
|
||||
participant, err := r.GetOrCreateSystemParticipant(userID)
|
||||
if err != nil {
|
||||
@@ -436,7 +509,7 @@ func (r *MessageRepository) GetSystemMessagesUnreadCount(userID string) (int64,
|
||||
}
|
||||
|
||||
// MarkAllSystemMessagesAsRead 标记所有系统消息已读
|
||||
func (r *MessageRepository) MarkAllSystemMessagesAsRead(userID string) error {
|
||||
func (r *messageRepository) MarkAllSystemMessagesAsRead(userID string) error {
|
||||
// 获取系统会话的最新 seq
|
||||
var maxSeq int64
|
||||
err := r.db.Model(&model.Message{}).
|
||||
@@ -465,7 +538,7 @@ func (r *MessageRepository) MarkAllSystemMessagesAsRead(userID string) error {
|
||||
}
|
||||
|
||||
// GetConversationByGroupID 通过群组ID获取会话
|
||||
func (r *MessageRepository) GetConversationByGroupID(groupID string) (*model.Conversation, error) {
|
||||
func (r *messageRepository) GetConversationByGroupID(groupID string) (*model.Conversation, error) {
|
||||
var conv model.Conversation
|
||||
err := r.db.Where("group_id = ?", groupID).First(&conv).Error
|
||||
if err != nil {
|
||||
@@ -476,14 +549,14 @@ func (r *MessageRepository) GetConversationByGroupID(groupID string) (*model.Con
|
||||
|
||||
// RemoveParticipant 移除会话参与者
|
||||
// 当用户退出群聊时,需要同时移除其在对应会话中的参与者记录
|
||||
func (r *MessageRepository) RemoveParticipant(conversationID string, userID string) error {
|
||||
func (r *messageRepository) RemoveParticipant(conversationID string, userID string) error {
|
||||
return r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).
|
||||
Delete(&model.ConversationParticipant{}).Error
|
||||
}
|
||||
|
||||
// AddParticipant 添加会话参与者
|
||||
// 当用户加入群聊时,需要同时将其添加到对应会话的参与者记录
|
||||
func (r *MessageRepository) AddParticipant(conversationID string, userID string) error {
|
||||
func (r *messageRepository) AddParticipant(conversationID string, userID string) error {
|
||||
// 先检查是否已经是参与者
|
||||
var count int64
|
||||
err := r.db.Model(&model.ConversationParticipant{}).
|
||||
@@ -509,7 +582,7 @@ func (r *MessageRepository) AddParticipant(conversationID string, userID string)
|
||||
|
||||
// DeleteConversationByGroupID 删除群组对应的会话及其参与者
|
||||
// 当解散群组时调用
|
||||
func (r *MessageRepository) DeleteConversationByGroupID(groupID string) error {
|
||||
func (r *messageRepository) DeleteConversationByGroupID(groupID string) error {
|
||||
// 获取群组对应的会话
|
||||
conv, err := r.GetConversationByGroupID(groupID)
|
||||
if err != nil {
|
||||
@@ -538,7 +611,7 @@ func (r *MessageRepository) DeleteConversationByGroupID(groupID string) error {
|
||||
}
|
||||
|
||||
// HideConversationForUser 仅对当前用户隐藏会话(私聊删除)
|
||||
func (r *MessageRepository) HideConversationForUser(conversationID, userID string) error {
|
||||
func (r *messageRepository) HideConversationForUser(conversationID, userID string) error {
|
||||
now := time.Now()
|
||||
return r.db.Model(&model.ConversationParticipant{}).
|
||||
Where("conversation_id = ? AND user_id = ?", conversationID, userID).
|
||||
@@ -554,7 +627,7 @@ type ParticipantUpdate struct {
|
||||
|
||||
// BatchWriteMessages 批量写入消息
|
||||
// 使用 GORM 的 CreateInBatches 实现高效批量插入
|
||||
func (r *MessageRepository) BatchWriteMessages(ctx context.Context, messages []*model.Message) error {
|
||||
func (r *messageRepository) BatchWriteMessages(ctx context.Context, messages []*model.Message) error {
|
||||
if len(messages) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -563,7 +636,7 @@ func (r *MessageRepository) BatchWriteMessages(ctx context.Context, messages []*
|
||||
|
||||
// BatchUpdateParticipants 批量更新参与者(使用 CASE WHEN 优化)
|
||||
// 使用单条 SQL 更新多条记录,避免循环执行 UPDATE
|
||||
func (r *MessageRepository) BatchUpdateParticipants(ctx context.Context, updates []ParticipantUpdate) error {
|
||||
func (r *messageRepository) BatchUpdateParticipants(ctx context.Context, updates []ParticipantUpdate) error {
|
||||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -601,7 +674,7 @@ func (r *MessageRepository) BatchUpdateParticipants(ctx context.Context, updates
|
||||
}
|
||||
|
||||
// UpdateConversationLastSeqWithContext 更新会话最后消息序号
|
||||
func (r *MessageRepository) UpdateConversationLastSeqWithContext(ctx context.Context, convID string, lastSeq int64, lastMsgTime time.Time) error {
|
||||
func (r *messageRepository) UpdateConversationLastSeqWithContext(ctx context.Context, convID string, lastSeq int64, lastMsgTime time.Time) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&model.Conversation{}).
|
||||
Where("id = ?", convID).
|
||||
@@ -613,7 +686,7 @@ func (r *MessageRepository) UpdateConversationLastSeqWithContext(ctx context.Con
|
||||
}
|
||||
|
||||
// BatchWriteMessagesWithTx 在事务中批量写入消息
|
||||
func (r *MessageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*model.Message) error {
|
||||
func (r *messageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*model.Message) error {
|
||||
if len(messages) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -621,7 +694,7 @@ func (r *MessageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*mo
|
||||
}
|
||||
|
||||
// BatchUpdateParticipantsWithTx 在事务中批量更新参与者
|
||||
func (r *MessageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates []ParticipantUpdate) error {
|
||||
func (r *messageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates []ParticipantUpdate) error {
|
||||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -649,7 +722,7 @@ func (r *MessageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates [
|
||||
}
|
||||
|
||||
// UpdateConversationLastSeqWithTx 在事务中更新会话最后消息序号
|
||||
func (r *MessageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID string, lastSeq int64, lastMsgTime time.Time) error {
|
||||
func (r *messageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID string, lastSeq int64, lastMsgTime time.Time) error {
|
||||
return tx.Model(&model.Conversation{}).
|
||||
Where("id = ?", convID).
|
||||
Updates(map[string]interface{}{
|
||||
@@ -663,7 +736,7 @@ func (r *MessageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID
|
||||
|
||||
// GetMessagesByCursor 游标分页获取会话消息
|
||||
// 消息按 seq DESC 排序
|
||||
func (r *MessageRepository) GetMessagesByCursor(ctx context.Context, conversationID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Message], error) {
|
||||
func (r *messageRepository) GetMessagesByCursor(ctx context.Context, conversationID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Message], error) {
|
||||
// 构建基础查询
|
||||
query := r.db.WithContext(ctx).Model(&model.Message{}).Where("conversation_id = ?", conversationID)
|
||||
|
||||
@@ -722,7 +795,7 @@ func (r *MessageRepository) GetMessagesByCursor(ctx context.Context, conversatio
|
||||
// GetConversationsByCursor 游标分页获取用户会话列表
|
||||
// 会话按置顶优先,然后按 updated_at DESC 排序
|
||||
// 注意:会话列表的排序涉及 conversation_participants 表的 is_pinned 和 updated_at 字段
|
||||
func (r *MessageRepository) GetConversationsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Conversation], error) {
|
||||
func (r *messageRepository) GetConversationsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Conversation], error) {
|
||||
// 构建基础查询 - 关联 conversation_participants 表
|
||||
query := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
||||
Joins("INNER JOIN conversation_participants cp ON conversations.id = cp.conversation_id").
|
||||
|
||||
@@ -8,23 +8,36 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NotificationRepository 通知仓储
|
||||
type NotificationRepository struct {
|
||||
// NotificationRepository 通知仓储接口
|
||||
type NotificationRepository interface {
|
||||
Create(notification *model.Notification) error
|
||||
GetByID(id string) (*model.Notification, error)
|
||||
GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error)
|
||||
MarkAsRead(id string) error
|
||||
MarkAllAsRead(userID string) error
|
||||
Delete(id string) error
|
||||
GetUnreadCount(userID string) (int64, error)
|
||||
DeleteAllByUserID(userID string) error
|
||||
GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error)
|
||||
}
|
||||
|
||||
// notificationRepository 通知仓储实现
|
||||
type notificationRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewNotificationRepository 创建通知仓储
|
||||
func NewNotificationRepository(db *gorm.DB) *NotificationRepository {
|
||||
return &NotificationRepository{db: db}
|
||||
func NewNotificationRepository(db *gorm.DB) NotificationRepository {
|
||||
return ¬ificationRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建通知
|
||||
func (r *NotificationRepository) Create(notification *model.Notification) error {
|
||||
func (r *notificationRepository) Create(notification *model.Notification) error {
|
||||
return r.db.Create(notification).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取通知
|
||||
func (r *NotificationRepository) GetByID(id string) (*model.Notification, error) {
|
||||
func (r *notificationRepository) GetByID(id string) (*model.Notification, error) {
|
||||
var notification model.Notification
|
||||
err := r.db.First(¬ification, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -34,7 +47,7 @@ func (r *NotificationRepository) GetByID(id string) (*model.Notification, error)
|
||||
}
|
||||
|
||||
// GetByUserID 获取用户通知
|
||||
func (r *NotificationRepository) GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) {
|
||||
func (r *notificationRepository) GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) {
|
||||
var notifications []*model.Notification
|
||||
var total int64
|
||||
|
||||
@@ -53,29 +66,29 @@ func (r *NotificationRepository) GetByUserID(userID string, page, pageSize int,
|
||||
}
|
||||
|
||||
// MarkAsRead 标记为已读
|
||||
func (r *NotificationRepository) MarkAsRead(id string) error {
|
||||
func (r *notificationRepository) MarkAsRead(id string) error {
|
||||
return r.db.Model(&model.Notification{}).Where("id = ?", id).Update("is_read", true).Error
|
||||
}
|
||||
|
||||
// MarkAllAsRead 标记所有为已读
|
||||
func (r *NotificationRepository) MarkAllAsRead(userID string) error {
|
||||
func (r *notificationRepository) MarkAllAsRead(userID string) error {
|
||||
return r.db.Model(&model.Notification{}).Where("user_id = ?", userID).Update("is_read", true).Error
|
||||
}
|
||||
|
||||
// Delete 删除通知
|
||||
func (r *NotificationRepository) Delete(id string) error {
|
||||
func (r *notificationRepository) Delete(id string) error {
|
||||
return r.db.Delete(&model.Notification{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// GetUnreadCount 获取未读数量
|
||||
func (r *NotificationRepository) GetUnreadCount(userID string) (int64, error) {
|
||||
func (r *notificationRepository) GetUnreadCount(userID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Notification{}).Where("user_id = ? AND is_read = ?", userID, false).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// DeleteAllByUserID 删除用户所有通知
|
||||
func (r *NotificationRepository) DeleteAllByUserID(userID string) error {
|
||||
func (r *notificationRepository) DeleteAllByUserID(userID string) error {
|
||||
return r.db.Where("user_id = ?", userID).Delete(&model.Notification{}).Error
|
||||
}
|
||||
|
||||
@@ -83,7 +96,7 @@ func (r *NotificationRepository) DeleteAllByUserID(userID string) error {
|
||||
|
||||
// GetNotificationsByCursor 游标分页获取用户通知
|
||||
// 排序方式:created_at DESC
|
||||
func (r *NotificationRepository) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) {
|
||||
func (r *notificationRepository) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) {
|
||||
// 构建基础查询
|
||||
query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ?", userID)
|
||||
|
||||
|
||||
@@ -3,28 +3,40 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OperationLogRepository 操作日志仓储
|
||||
type OperationLogRepository struct {
|
||||
// OperationLogRepository 操作日志仓储接口
|
||||
type OperationLogRepository interface {
|
||||
CreateOperationLog(ctx context.Context, log *model.OperationLog) error
|
||||
BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error
|
||||
GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error)
|
||||
GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error)
|
||||
GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error)
|
||||
DeleteOldLogs(ctx context.Context, beforeDate string) error
|
||||
GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error)
|
||||
}
|
||||
|
||||
// operationLogRepository 操作日志仓储实现
|
||||
type operationLogRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewOperationLogRepository 创建操作日志仓储
|
||||
func NewOperationLogRepository(db *gorm.DB) *OperationLogRepository {
|
||||
return &OperationLogRepository{db: db}
|
||||
func NewOperationLogRepository(db *gorm.DB) OperationLogRepository {
|
||||
return &operationLogRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateOperationLog 创建单条操作日志
|
||||
func (r *OperationLogRepository) CreateOperationLog(ctx context.Context, log *model.OperationLog) error {
|
||||
func (r *operationLogRepository) CreateOperationLog(ctx context.Context, log *model.OperationLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// BatchCreateOperationLogs 批量创建操作日志
|
||||
func (r *OperationLogRepository) BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error {
|
||||
func (r *operationLogRepository) BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -32,7 +44,7 @@ func (r *OperationLogRepository) BatchCreateOperationLogs(ctx context.Context, l
|
||||
}
|
||||
|
||||
// GetOperationLogs 获取操作日志列表(分页)
|
||||
func (r *OperationLogRepository) GetOperationLogs(ctx context.Context, filters LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
||||
func (r *operationLogRepository) GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
||||
query := r.db.WithContext(ctx).Model(&model.OperationLog{})
|
||||
|
||||
if filters.UserID != "" {
|
||||
@@ -75,22 +87,22 @@ func (r *OperationLogRepository) GetOperationLogs(ctx context.Context, filters L
|
||||
}
|
||||
|
||||
// GetOperationLogsByUser 获取指定用户的操作日志
|
||||
func (r *OperationLogRepository) GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
||||
return r.GetOperationLogs(ctx, LogFilter{UserID: userID}, page, pageSize)
|
||||
func (r *operationLogRepository) GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
||||
return r.GetOperationLogs(ctx, dto.LogFilter{UserID: userID}, page, pageSize)
|
||||
}
|
||||
|
||||
// GetOperationLogsByTimeRange 按时间范围获取操作日志
|
||||
func (r *OperationLogRepository) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
||||
return r.GetOperationLogs(ctx, LogFilter{StartTime: startTime, EndTime: endTime}, page, pageSize)
|
||||
func (r *operationLogRepository) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
||||
return r.GetOperationLogs(ctx, dto.LogFilter{StartTime: startTime, EndTime: endTime}, page, pageSize)
|
||||
}
|
||||
|
||||
// DeleteOldLogs 删除旧的操作日志(用于定时清理)
|
||||
func (r *OperationLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error {
|
||||
func (r *operationLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error {
|
||||
return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.OperationLog{}).Error
|
||||
}
|
||||
|
||||
// GetStatistics 获取操作日志统计(使用单次 GROUP BY 查询,避免多次 COUNT)
|
||||
func (r *OperationLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error) {
|
||||
func (r *operationLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error) {
|
||||
stats := &OperationLogStatistics{}
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&model.OperationLog{})
|
||||
@@ -125,18 +137,6 @@ func (r *OperationLogRepository) GetStatistics(ctx context.Context, startTime, e
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// LogFilter 日志过滤条件
|
||||
type LogFilter struct {
|
||||
UserID string
|
||||
Operation string
|
||||
TargetType string
|
||||
TargetID string
|
||||
Status string
|
||||
IP string
|
||||
StartTime string
|
||||
EndTime string
|
||||
}
|
||||
|
||||
// OperationLogStatistics 操作日志统计
|
||||
type OperationLogStatistics struct {
|
||||
TotalCount int64
|
||||
|
||||
@@ -11,18 +11,63 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PostRepository 帖子仓储
|
||||
type PostRepository struct {
|
||||
// PostRepository 帖子仓储接口
|
||||
type PostRepository interface {
|
||||
Create(post *model.Post, images []string) error
|
||||
GetByID(id string) (*model.Post, error)
|
||||
Update(post *model.Post) error
|
||||
UpdateWithImages(post *model.Post, images *[]string) error
|
||||
UpdateModerationStatus(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error
|
||||
Delete(id string) error
|
||||
List(page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error)
|
||||
GetUserPosts(userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error)
|
||||
GetFavorites(userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
Like(postID, userID string) error
|
||||
Unlike(postID, userID string) error
|
||||
IsLiked(postID, userID string) bool
|
||||
IsLikedBatch(postIDs []string, userID string) map[string]bool
|
||||
Favorite(postID, userID string) error
|
||||
Unfavorite(postID, userID string) error
|
||||
IsFavorited(postID, userID string) bool
|
||||
IsFavoritedBatch(postIDs []string, userID string) map[string]bool
|
||||
IncrementViews(postID string) error
|
||||
IncrementShares(postID string) (int, error)
|
||||
Search(keyword string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
GetFollowingPosts(userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
GetHotPosts(page, pageSize int) ([]*model.Post, int64, error)
|
||||
ListPublishedPostIDsSince(since time.Time, limit int) ([]string, error)
|
||||
ListPinnedPublishedPostIDs() ([]string, error)
|
||||
GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error)
|
||||
GetByIDs(ids []string) ([]*model.Post, error)
|
||||
CreateWithContext(ctx context.Context, post *model.Post, images []string) error
|
||||
GetByIDWithContext(ctx context.Context, id string) (*model.Post, error)
|
||||
UpdateWithContext(ctx context.Context, post *model.Post) error
|
||||
DeleteWithContext(ctx context.Context, id string) error
|
||||
AdminList(query AdminPostListQuery) ([]*model.Post, int64, error)
|
||||
GetByIDForAdmin(id string) (*model.Post, error)
|
||||
BatchDelete(ids []string) ([]string, error)
|
||||
BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error)
|
||||
UpdatePinStatus(postID string, isPinned bool) error
|
||||
UpdateFeatureStatus(postID string, isFeatured bool) error
|
||||
GetPostsByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
SearchPostsByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
}
|
||||
|
||||
// postRepository 帖子仓储实现
|
||||
type postRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPostRepository 创建帖子仓储
|
||||
func NewPostRepository(db *gorm.DB) *PostRepository {
|
||||
return &PostRepository{db: db}
|
||||
func NewPostRepository(db *gorm.DB) PostRepository {
|
||||
return &postRepository{db: db}
|
||||
}
|
||||
|
||||
// getDB 获取数据库连接(优先使用 context 中的事务)
|
||||
func (r *PostRepository) getDB(ctx context.Context) *gorm.DB {
|
||||
func (r *postRepository) getDB(ctx context.Context) *gorm.DB {
|
||||
if tx := GetTxFromContext(ctx); tx != nil {
|
||||
return tx
|
||||
}
|
||||
@@ -30,7 +75,7 @@ func (r *PostRepository) getDB(ctx context.Context) *gorm.DB {
|
||||
}
|
||||
|
||||
// Create 创建帖子
|
||||
func (r *PostRepository) Create(post *model.Post, images []string) error {
|
||||
func (r *postRepository) Create(post *model.Post, images []string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 创建帖子
|
||||
if err := tx.Create(post).Error; err != nil {
|
||||
@@ -68,7 +113,7 @@ func (r *PostRepository) Create(post *model.Post, images []string) error {
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取帖子
|
||||
func (r *PostRepository) GetByID(id string) (*model.Post, error) {
|
||||
func (r *postRepository) GetByID(id string) (*model.Post, error) {
|
||||
var post model.Post
|
||||
err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -78,7 +123,7 @@ func (r *PostRepository) GetByID(id string) (*model.Post, error) {
|
||||
}
|
||||
|
||||
// Update 更新帖子
|
||||
func (r *PostRepository) Update(post *model.Post) error {
|
||||
func (r *postRepository) Update(post *model.Post) error {
|
||||
now := time.Now()
|
||||
post.UpdatedAt = now
|
||||
post.ContentEditedAt = &now
|
||||
@@ -86,7 +131,7 @@ func (r *PostRepository) Update(post *model.Post) error {
|
||||
}
|
||||
|
||||
// UpdateWithImages 更新帖子及其图片(images=nil 表示不更新图片)
|
||||
func (r *PostRepository) UpdateWithImages(post *model.Post, images *[]string) error {
|
||||
func (r *postRepository) UpdateWithImages(post *model.Post, images *[]string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
now := time.Now()
|
||||
post.UpdatedAt = now
|
||||
@@ -119,7 +164,7 @@ func (r *PostRepository) UpdateWithImages(post *model.Post, images *[]string) er
|
||||
}
|
||||
|
||||
// UpdateModerationStatus 更新帖子审核状态
|
||||
func (r *PostRepository) UpdateModerationStatus(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error {
|
||||
func (r *postRepository) UpdateModerationStatus(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error {
|
||||
// 审核状态更新属于管理操作,不应影响帖子内容更新时间(updated_at)
|
||||
updates := map[string]interface{}{
|
||||
"status": status,
|
||||
@@ -132,7 +177,7 @@ func (r *PostRepository) UpdateModerationStatus(postID string, status model.Post
|
||||
}
|
||||
|
||||
// Delete 删除帖子(软删除,同时清理关联数据)
|
||||
func (r *PostRepository) Delete(id string) error {
|
||||
func (r *postRepository) Delete(id string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 删除帖子图片
|
||||
if err := tx.Where("post_id = ?", id).Delete(&model.PostImage{}).Error; err != nil {
|
||||
@@ -166,7 +211,7 @@ func (r *PostRepository) Delete(id string) error {
|
||||
|
||||
// List 分页获取帖子列表
|
||||
// includePending=true 时,仅在指定 userID 下额外返回 pending(用于作者查看自己待审核帖子)
|
||||
func (r *PostRepository) List(page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error) {
|
||||
func (r *postRepository) List(page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
@@ -196,7 +241,7 @@ func (r *PostRepository) List(page, pageSize int, userID string, includePending
|
||||
}
|
||||
|
||||
// GetUserPosts 获取用户帖子
|
||||
func (r *PostRepository) GetUserPosts(userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error) {
|
||||
func (r *postRepository) GetUserPosts(userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
@@ -227,7 +272,7 @@ func (r *PostRepository) GetUserPosts(userID string, page, pageSize int, include
|
||||
}
|
||||
|
||||
// GetFavorites 获取用户收藏
|
||||
func (r *PostRepository) GetFavorites(userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (r *postRepository) GetFavorites(userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
@@ -241,7 +286,7 @@ func (r *PostRepository) GetFavorites(userID string, page, pageSize int) ([]*mod
|
||||
}
|
||||
|
||||
// Like 点赞帖子
|
||||
func (r *PostRepository) Like(postID, userID string) error {
|
||||
func (r *postRepository) Like(postID, userID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 检查是否已经点赞
|
||||
var existing model.PostLike
|
||||
@@ -272,7 +317,7 @@ func (r *PostRepository) Like(postID, userID string) error {
|
||||
}
|
||||
|
||||
// Unlike 取消点赞
|
||||
func (r *PostRepository) Unlike(postID, userID string) error {
|
||||
func (r *postRepository) Unlike(postID, userID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.PostLike{})
|
||||
if result.Error != nil {
|
||||
@@ -290,7 +335,7 @@ func (r *PostRepository) Unlike(postID, userID string) error {
|
||||
}
|
||||
|
||||
// IsLiked 检查是否点赞
|
||||
func (r *PostRepository) IsLiked(postID, userID string) bool {
|
||||
func (r *postRepository) IsLiked(postID, userID string) bool {
|
||||
var count int64
|
||||
r.db.Model(&model.PostLike{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count)
|
||||
return count > 0
|
||||
@@ -298,7 +343,7 @@ func (r *PostRepository) IsLiked(postID, userID string) bool {
|
||||
|
||||
// IsLikedBatch 批量检查是否点赞(解决 N+1 问题)
|
||||
// 返回 map[postID]bool
|
||||
func (r *PostRepository) IsLikedBatch(postIDs []string, userID string) map[string]bool {
|
||||
func (r *postRepository) IsLikedBatch(postIDs []string, userID string) map[string]bool {
|
||||
result := make(map[string]bool)
|
||||
if len(postIDs) == 0 || userID == "" {
|
||||
return result
|
||||
@@ -324,7 +369,7 @@ func (r *PostRepository) IsLikedBatch(postIDs []string, userID string) map[strin
|
||||
}
|
||||
|
||||
// Favorite 收藏帖子
|
||||
func (r *PostRepository) Favorite(postID, userID string) error {
|
||||
func (r *postRepository) Favorite(postID, userID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 检查是否已经收藏
|
||||
var existing model.Favorite
|
||||
@@ -355,7 +400,7 @@ func (r *PostRepository) Favorite(postID, userID string) error {
|
||||
}
|
||||
|
||||
// Unfavorite 取消收藏
|
||||
func (r *PostRepository) Unfavorite(postID, userID string) error {
|
||||
func (r *postRepository) Unfavorite(postID, userID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.Favorite{})
|
||||
if result.Error != nil {
|
||||
@@ -374,7 +419,7 @@ func (r *PostRepository) Unfavorite(postID, userID string) error {
|
||||
}
|
||||
|
||||
// IsFavorited 检查是否收藏
|
||||
func (r *PostRepository) IsFavorited(postID, userID string) bool {
|
||||
func (r *postRepository) IsFavorited(postID, userID string) bool {
|
||||
var count int64
|
||||
r.db.Model(&model.Favorite{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count)
|
||||
return count > 0
|
||||
@@ -382,7 +427,7 @@ func (r *PostRepository) IsFavorited(postID, userID string) bool {
|
||||
|
||||
// IsFavoritedBatch 批量检查是否收藏(解决 N+1 问题)
|
||||
// 返回 map[postID]bool
|
||||
func (r *PostRepository) IsFavoritedBatch(postIDs []string, userID string) map[string]bool {
|
||||
func (r *postRepository) IsFavoritedBatch(postIDs []string, userID string) map[string]bool {
|
||||
result := make(map[string]bool)
|
||||
if len(postIDs) == 0 || userID == "" {
|
||||
return result
|
||||
@@ -408,7 +453,7 @@ func (r *PostRepository) IsFavoritedBatch(postIDs []string, userID string) map[s
|
||||
}
|
||||
|
||||
// IncrementViews 增加帖子观看量
|
||||
func (r *PostRepository) IncrementViews(postID string) error {
|
||||
func (r *postRepository) IncrementViews(postID string) error {
|
||||
return r.db.Model(&model.Post{}).Where("id = ?", postID).
|
||||
UpdateColumns(map[string]interface{}{
|
||||
"views_count": gorm.Expr("views_count + 1"),
|
||||
@@ -417,7 +462,7 @@ func (r *PostRepository) IncrementViews(postID string) error {
|
||||
}
|
||||
|
||||
// IncrementShares 已发布帖子分享次数 +1,返回新的 shares_count;非已发布或不存在返回 gorm.ErrRecordNotFound
|
||||
func (r *PostRepository) IncrementShares(postID string) (int, error) {
|
||||
func (r *postRepository) IncrementShares(postID string) (int, error) {
|
||||
var newCount int
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
res := tx.Model(&model.Post{}).
|
||||
@@ -443,7 +488,7 @@ func (r *PostRepository) IncrementShares(postID string) (int, error) {
|
||||
}
|
||||
|
||||
// Search 搜索帖子
|
||||
func (r *PostRepository) Search(keyword string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (r *postRepository) Search(keyword string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
@@ -472,7 +517,7 @@ func (r *PostRepository) Search(keyword string, page, pageSize int) ([]*model.Po
|
||||
}
|
||||
|
||||
// GetFollowingPosts 获取关注用户的帖子
|
||||
func (r *PostRepository) GetFollowingPosts(userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (r *postRepository) GetFollowingPosts(userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
@@ -493,7 +538,7 @@ func (r *PostRepository) GetFollowingPosts(userID string, page, pageSize int) ([
|
||||
}
|
||||
|
||||
// GetHotPosts 热门榜降级:Redis 未就绪时按最新发布排序
|
||||
func (r *PostRepository) GetHotPosts(page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (r *postRepository) GetHotPosts(page, pageSize int) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
@@ -520,7 +565,7 @@ type PostHotSnapshot struct {
|
||||
}
|
||||
|
||||
// ListPublishedPostIDsSince 按发布时间倒序取 ID(热门候选池,避免全表扫快照)
|
||||
func (r *PostRepository) ListPublishedPostIDsSince(since time.Time, limit int) ([]string, error) {
|
||||
func (r *postRepository) ListPublishedPostIDsSince(since time.Time, limit int) ([]string, error) {
|
||||
if limit <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -547,7 +592,7 @@ func (r *PostRepository) ListPublishedPostIDsSince(since time.Time, limit int) (
|
||||
}
|
||||
|
||||
// ListPinnedPublishedPostIDs 已发布且置顶的帖子 ID(越新发布的置顶越靠前)
|
||||
func (r *PostRepository) ListPinnedPublishedPostIDs() ([]string, error) {
|
||||
func (r *postRepository) ListPinnedPublishedPostIDs() ([]string, error) {
|
||||
type row struct {
|
||||
ID string `gorm:"column:id"`
|
||||
}
|
||||
@@ -570,7 +615,7 @@ func (r *PostRepository) ListPinnedPublishedPostIDs() ([]string, error) {
|
||||
}
|
||||
|
||||
// GetPostHotSnapshotsByIDs 仅拉取候选 ID 对应的热度字段(分批 IN 查询)
|
||||
func (r *PostRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) {
|
||||
func (r *postRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -596,7 +641,7 @@ func (r *PostRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapsh
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取帖子(保持传入顺序)
|
||||
func (r *PostRepository) GetByIDs(ids []string) ([]*model.Post, error) {
|
||||
func (r *postRepository) GetByIDs(ids []string) ([]*model.Post, error) {
|
||||
if len(ids) == 0 {
|
||||
return []*model.Post{}, nil
|
||||
}
|
||||
@@ -628,7 +673,7 @@ func (r *PostRepository) GetByIDs(ids []string) ([]*model.Post, error) {
|
||||
// ========== Context-aware methods for transaction support ==========
|
||||
|
||||
// CreateWithContext 创建帖子(支持事务)
|
||||
func (r *PostRepository) CreateWithContext(ctx context.Context, post *model.Post, images []string) error {
|
||||
func (r *postRepository) CreateWithContext(ctx context.Context, post *model.Post, images []string) error {
|
||||
db := r.getDB(ctx)
|
||||
|
||||
// 如果 context 中有事务,直接使用
|
||||
@@ -643,7 +688,7 @@ func (r *PostRepository) CreateWithContext(ctx context.Context, post *model.Post
|
||||
}
|
||||
|
||||
// createWithTx 使用指定事务创建帖子
|
||||
func (r *PostRepository) createWithTx(tx *gorm.DB, post *model.Post, images []string) error {
|
||||
func (r *postRepository) createWithTx(tx *gorm.DB, post *model.Post, images []string) error {
|
||||
// 创建帖子
|
||||
if err := tx.Create(post).Error; err != nil {
|
||||
return err
|
||||
@@ -665,7 +710,7 @@ func (r *PostRepository) createWithTx(tx *gorm.DB, post *model.Post, images []st
|
||||
}
|
||||
|
||||
// GetByIDWithContext 根据ID获取帖子(支持事务)
|
||||
func (r *PostRepository) GetByIDWithContext(ctx context.Context, id string) (*model.Post, error) {
|
||||
func (r *postRepository) GetByIDWithContext(ctx context.Context, id string) (*model.Post, error) {
|
||||
var post model.Post
|
||||
err := r.getDB(ctx).WithContext(ctx).Preload("User").Preload("Images").First(&post, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -675,7 +720,7 @@ func (r *PostRepository) GetByIDWithContext(ctx context.Context, id string) (*mo
|
||||
}
|
||||
|
||||
// UpdateWithContext 更新帖子(支持事务)
|
||||
func (r *PostRepository) UpdateWithContext(ctx context.Context, post *model.Post) error {
|
||||
func (r *postRepository) UpdateWithContext(ctx context.Context, post *model.Post) error {
|
||||
now := time.Now()
|
||||
post.UpdatedAt = now
|
||||
post.ContentEditedAt = &now
|
||||
@@ -683,7 +728,7 @@ func (r *PostRepository) UpdateWithContext(ctx context.Context, post *model.Post
|
||||
}
|
||||
|
||||
// DeleteWithContext 删除帖子(支持事务)
|
||||
func (r *PostRepository) DeleteWithContext(ctx context.Context, id string) error {
|
||||
func (r *postRepository) DeleteWithContext(ctx context.Context, id string) error {
|
||||
db := r.getDB(ctx)
|
||||
|
||||
// 如果 context 中有事务,直接使用
|
||||
@@ -698,7 +743,7 @@ func (r *PostRepository) DeleteWithContext(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
// deleteWithTx 使用指定事务删除帖子
|
||||
func (r *PostRepository) deleteWithTx(tx *gorm.DB, id string) error {
|
||||
func (r *postRepository) deleteWithTx(tx *gorm.DB, id string) error {
|
||||
// 删除帖子图片
|
||||
if err := tx.Where("post_id = ?", id).Delete(&model.PostImage{}).Error; err != nil {
|
||||
return err
|
||||
@@ -742,7 +787,7 @@ type AdminPostListQuery struct {
|
||||
}
|
||||
|
||||
// AdminList 管理端分页获取帖子列表(支持多条件筛选)
|
||||
func (r *PostRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int64, error) {
|
||||
func (r *postRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
@@ -793,7 +838,7 @@ func (r *PostRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int
|
||||
}
|
||||
|
||||
// GetByIDForAdmin 管理端根据ID获取帖子(包含所有状态)
|
||||
func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) {
|
||||
func (r *postRepository) GetByIDForAdmin(id string) (*model.Post, error) {
|
||||
var post model.Post
|
||||
err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -803,7 +848,7 @@ func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) {
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除帖子(使用单次事务批量删除,避免 N+1 问题)
|
||||
func (r *PostRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
func (r *postRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -855,7 +900,7 @@ func (r *PostRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新帖子审核状态(使用单条 SQL 批量更新,避免 N+1 问题)
|
||||
func (r *PostRepository) BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error) {
|
||||
func (r *postRepository) BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -900,7 +945,7 @@ func (r *PostRepository) BatchUpdateStatus(ids []string, status model.PostStatus
|
||||
}
|
||||
|
||||
// UpdatePinStatus 更新帖子置顶状态
|
||||
func (r *PostRepository) UpdatePinStatus(postID string, isPinned bool) error {
|
||||
func (r *postRepository) UpdatePinStatus(postID string, isPinned bool) error {
|
||||
// 置顶状态属于管理操作,不应影响帖子内容更新时间(updated_at)
|
||||
return r.db.Model(&model.Post{}).Where("id = ?", postID).
|
||||
UpdateColumns(map[string]any{
|
||||
@@ -910,7 +955,7 @@ func (r *PostRepository) UpdatePinStatus(postID string, isPinned bool) error {
|
||||
}
|
||||
|
||||
// UpdateFeatureStatus 更新帖子加精状态
|
||||
func (r *PostRepository) UpdateFeatureStatus(postID string, isFeatured bool) error {
|
||||
func (r *postRepository) UpdateFeatureStatus(postID string, isFeatured bool) error {
|
||||
// 加精状态属于管理操作,不应影响帖子内容更新时间(updated_at)
|
||||
return r.db.Model(&model.Post{}).Where("id = ?", postID).
|
||||
UpdateColumns(map[string]any{
|
||||
@@ -923,7 +968,7 @@ func (r *PostRepository) UpdateFeatureStatus(postID string, isFeatured bool) err
|
||||
|
||||
// GetPostsByCursor 游标分页获取帖子列表
|
||||
// includePending=true 时,仅在指定 userID 下额外返回 pending(用于作者查看自己待审核帖子)
|
||||
func (r *PostRepository) GetPostsByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
func (r *postRepository) GetPostsByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
db := r.getDB(ctx).WithContext(ctx)
|
||||
|
||||
// 构建基础查询
|
||||
@@ -993,7 +1038,7 @@ func (r *PostRepository) GetPostsByCursor(ctx context.Context, userID string, in
|
||||
}
|
||||
|
||||
// SearchPostsByCursor 游标分页搜索帖子
|
||||
func (r *PostRepository) SearchPostsByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
func (r *postRepository) SearchPostsByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
db := r.getDB(ctx).WithContext(ctx)
|
||||
|
||||
query := db.Model(&model.Post{}).Where("status = ?", model.PostStatusPublished)
|
||||
@@ -1059,7 +1104,7 @@ func (r *PostRepository) SearchPostsByCursor(ctx context.Context, keyword string
|
||||
}
|
||||
|
||||
// GetUserPostsByCursor 游标分页获取用户帖子
|
||||
func (r *PostRepository) GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
func (r *postRepository) GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
db := r.getDB(ctx).WithContext(ctx)
|
||||
|
||||
query := db.Model(&model.Post{}).Where("user_id = ?", userID)
|
||||
@@ -1119,7 +1164,7 @@ func (r *PostRepository) GetUserPostsByCursor(ctx context.Context, userID string
|
||||
}
|
||||
|
||||
// GetFollowingPostsByCursor 游标分页获取关注用户的帖子
|
||||
func (r *PostRepository) GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
func (r *postRepository) GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
db := r.getDB(ctx).WithContext(ctx)
|
||||
|
||||
// 子查询:获取当前用户关注的所有用户ID
|
||||
@@ -1175,7 +1220,7 @@ func (r *PostRepository) GetFollowingPostsByCursor(ctx context.Context, userID s
|
||||
}
|
||||
|
||||
// GetHotPostsByCursor 游标分页获取热门帖子
|
||||
func (r *PostRepository) GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
func (r *postRepository) GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
db := r.getDB(ctx).WithContext(ctx)
|
||||
|
||||
// 热门游标降级:与 GetHotPosts 一致,按最新发布排序
|
||||
|
||||
@@ -8,23 +8,41 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PushRecordRepository 推送记录仓储
|
||||
type PushRecordRepository struct {
|
||||
// PushRecordRepository 推送记录仓储接口
|
||||
type PushRecordRepository interface {
|
||||
Create(record *model.PushRecord) error
|
||||
GetByID(id int64) (*model.PushRecord, error)
|
||||
Update(record *model.PushRecord) error
|
||||
GetPendingPushes(limit int) ([]*model.PushRecord, error)
|
||||
GetByUserID(userID string, limit, offset int) ([]*model.PushRecord, error)
|
||||
GetByMessageID(messageID int64) ([]*model.PushRecord, error)
|
||||
GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error)
|
||||
BatchCreate(records []*model.PushRecord) error
|
||||
BatchUpdateStatus(ids []int64, status model.PushStatus) error
|
||||
UpdateStatus(id int64, status model.PushStatus) error
|
||||
MarkAsFailed(id int64, errMsg string) error
|
||||
MarkAsDelivered(id int64) error
|
||||
DeleteExpiredRecords() error
|
||||
GetStatsByUserID(userID int64) (map[model.PushStatus]int64, error)
|
||||
}
|
||||
|
||||
// pushRecordRepository 推送记录仓储实现
|
||||
type pushRecordRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPushRecordRepository 创建推送记录仓储
|
||||
func NewPushRecordRepository(db *gorm.DB) *PushRecordRepository {
|
||||
return &PushRecordRepository{db: db}
|
||||
func NewPushRecordRepository(db *gorm.DB) PushRecordRepository {
|
||||
return &pushRecordRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建推送记录
|
||||
func (r *PushRecordRepository) Create(record *model.PushRecord) error {
|
||||
func (r *pushRecordRepository) Create(record *model.PushRecord) error {
|
||||
return r.db.Create(record).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取推送记录
|
||||
func (r *PushRecordRepository) GetByID(id int64) (*model.PushRecord, error) {
|
||||
func (r *pushRecordRepository) GetByID(id int64) (*model.PushRecord, error) {
|
||||
var record model.PushRecord
|
||||
err := r.db.First(&record, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -34,12 +52,12 @@ func (r *PushRecordRepository) GetByID(id int64) (*model.PushRecord, error) {
|
||||
}
|
||||
|
||||
// Update 更新推送记录
|
||||
func (r *PushRecordRepository) Update(record *model.PushRecord) error {
|
||||
func (r *pushRecordRepository) Update(record *model.PushRecord) error {
|
||||
return r.db.Save(record).Error
|
||||
}
|
||||
|
||||
// GetPendingPushes 获取待推送记录
|
||||
func (r *PushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord, error) {
|
||||
func (r *pushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord, error) {
|
||||
var records []*model.PushRecord
|
||||
err := r.db.Where("push_status = ?", model.PushStatusPending).
|
||||
Where("expired_at IS NULL OR expired_at > ?", time.Now()).
|
||||
@@ -51,7 +69,7 @@ func (r *PushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord,
|
||||
|
||||
// GetByUserID 根据用户ID获取推送记录
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *PushRecordRepository) GetByUserID(userID string, limit, offset int) ([]*model.PushRecord, error) {
|
||||
func (r *pushRecordRepository) GetByUserID(userID string, limit, offset int) ([]*model.PushRecord, error) {
|
||||
var records []*model.PushRecord
|
||||
err := r.db.Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
@@ -62,7 +80,7 @@ func (r *PushRecordRepository) GetByUserID(userID string, limit, offset int) ([]
|
||||
}
|
||||
|
||||
// GetByMessageID 根据消息ID获取推送记录
|
||||
func (r *PushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRecord, error) {
|
||||
func (r *pushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRecord, error) {
|
||||
var records []*model.PushRecord
|
||||
err := r.db.Where("message_id = ?", messageID).
|
||||
Order("created_at DESC").
|
||||
@@ -71,7 +89,7 @@ func (r *PushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRec
|
||||
}
|
||||
|
||||
// GetFailedPushesForRetry 获取失败待重试的推送
|
||||
func (r *PushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error) {
|
||||
func (r *pushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error) {
|
||||
var records []*model.PushRecord
|
||||
err := r.db.Where("push_status = ?", model.PushStatusFailed).
|
||||
Where("retry_count < max_retry").
|
||||
@@ -83,7 +101,7 @@ func (r *PushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.Push
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建推送记录
|
||||
func (r *PushRecordRepository) BatchCreate(records []*model.PushRecord) error {
|
||||
func (r *pushRecordRepository) BatchCreate(records []*model.PushRecord) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -91,7 +109,7 @@ func (r *PushRecordRepository) BatchCreate(records []*model.PushRecord) error {
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新推送状态
|
||||
func (r *PushRecordRepository) BatchUpdateStatus(ids []int64, status model.PushStatus) error {
|
||||
func (r *pushRecordRepository) BatchUpdateStatus(ids []int64, status model.PushStatus) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -107,7 +125,7 @@ func (r *PushRecordRepository) BatchUpdateStatus(ids []int64, status model.PushS
|
||||
}
|
||||
|
||||
// UpdateStatus 更新单条记录状态
|
||||
func (r *PushRecordRepository) UpdateStatus(id int64, status model.PushStatus) error {
|
||||
func (r *pushRecordRepository) UpdateStatus(id int64, status model.PushStatus) error {
|
||||
updates := map[string]interface{}{
|
||||
"push_status": status,
|
||||
}
|
||||
@@ -120,7 +138,7 @@ func (r *PushRecordRepository) UpdateStatus(id int64, status model.PushStatus) e
|
||||
}
|
||||
|
||||
// MarkAsFailed 标记为失败
|
||||
func (r *PushRecordRepository) MarkAsFailed(id int64, errMsg string) error {
|
||||
func (r *pushRecordRepository) MarkAsFailed(id int64, errMsg string) error {
|
||||
return r.db.Model(&model.PushRecord{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
@@ -131,7 +149,7 @@ func (r *PushRecordRepository) MarkAsFailed(id int64, errMsg string) error {
|
||||
}
|
||||
|
||||
// MarkAsDelivered 标记为已送达
|
||||
func (r *PushRecordRepository) MarkAsDelivered(id int64) error {
|
||||
func (r *pushRecordRepository) MarkAsDelivered(id int64) error {
|
||||
return r.db.Model(&model.PushRecord{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
@@ -141,13 +159,13 @@ func (r *PushRecordRepository) MarkAsDelivered(id int64) error {
|
||||
}
|
||||
|
||||
// DeleteExpiredRecords 删除过期的推送记录(软删除)
|
||||
func (r *PushRecordRepository) DeleteExpiredRecords() error {
|
||||
func (r *pushRecordRepository) DeleteExpiredRecords() error {
|
||||
return r.db.Where("expired_at IS NOT NULL AND expired_at < ?", time.Now()).
|
||||
Delete(&model.PushRecord{}).Error
|
||||
}
|
||||
|
||||
// GetStatsByUserID 获取用户推送统计
|
||||
func (r *PushRecordRepository) GetStatsByUserID(userID int64) (map[model.PushStatus]int64, error) {
|
||||
func (r *pushRecordRepository) GetStatsByUserID(userID int64) (map[model.PushStatus]int64, error) {
|
||||
type statusCount struct {
|
||||
Status model.PushStatus
|
||||
Count int64
|
||||
|
||||
@@ -10,23 +10,38 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SystemNotificationRepository 系统通知仓储
|
||||
type SystemNotificationRepository struct {
|
||||
// SystemNotificationRepository 系统通知仓储接口
|
||||
type SystemNotificationRepository interface {
|
||||
Create(notification *model.SystemNotification) error
|
||||
GetByID(id int64) (*model.SystemNotification, error)
|
||||
GetByReceiverID(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error)
|
||||
GetUnreadByReceiverID(receiverID string, limit int) ([]*model.SystemNotification, error)
|
||||
GetUnreadCount(receiverID string) (int64, error)
|
||||
MarkAsRead(id int64, receiverID string) error
|
||||
MarkAllAsRead(receiverID string) error
|
||||
Delete(id int64, receiverID string) error
|
||||
GetByType(receiverID string, notifyType model.SystemNotificationType, page, pageSize int) ([]*model.SystemNotification, int64, error)
|
||||
GetByReceiverIDCursor(ctx context.Context, receiverID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.SystemNotification], error)
|
||||
GetByReceiverIDCursorLegacy(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error)
|
||||
}
|
||||
|
||||
// systemNotificationRepository 系统通知仓储实现
|
||||
type systemNotificationRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewSystemNotificationRepository 创建系统通知仓储
|
||||
func NewSystemNotificationRepository(db *gorm.DB) *SystemNotificationRepository {
|
||||
return &SystemNotificationRepository{db: db}
|
||||
func NewSystemNotificationRepository(db *gorm.DB) SystemNotificationRepository {
|
||||
return &systemNotificationRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建系统通知
|
||||
func (r *SystemNotificationRepository) Create(notification *model.SystemNotification) error {
|
||||
func (r *systemNotificationRepository) Create(notification *model.SystemNotification) error {
|
||||
return r.db.Create(notification).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取通知
|
||||
func (r *SystemNotificationRepository) GetByID(id int64) (*model.SystemNotification, error) {
|
||||
func (r *systemNotificationRepository) GetByID(id int64) (*model.SystemNotification, error) {
|
||||
var notification model.SystemNotification
|
||||
err := r.db.First(¬ification, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -36,7 +51,7 @@ func (r *SystemNotificationRepository) GetByID(id int64) (*model.SystemNotificat
|
||||
}
|
||||
|
||||
// GetByReceiverID 获取用户的通知列表
|
||||
func (r *SystemNotificationRepository) GetByReceiverID(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) {
|
||||
func (r *systemNotificationRepository) GetByReceiverID(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) {
|
||||
var notifications []*model.SystemNotification
|
||||
var total int64
|
||||
|
||||
@@ -53,7 +68,7 @@ func (r *SystemNotificationRepository) GetByReceiverID(receiverID string, page,
|
||||
}
|
||||
|
||||
// GetUnreadByReceiverID 获取用户的未读通知列表
|
||||
func (r *SystemNotificationRepository) GetUnreadByReceiverID(receiverID string, limit int) ([]*model.SystemNotification, error) {
|
||||
func (r *systemNotificationRepository) GetUnreadByReceiverID(receiverID string, limit int) ([]*model.SystemNotification, error) {
|
||||
var notifications []*model.SystemNotification
|
||||
err := r.db.Where("receiver_id = ? AND is_read = ?", receiverID, false).
|
||||
Order("created_at DESC").
|
||||
@@ -63,7 +78,7 @@ func (r *SystemNotificationRepository) GetUnreadByReceiverID(receiverID string,
|
||||
}
|
||||
|
||||
// GetUnreadCount 获取用户未读通知数量
|
||||
func (r *SystemNotificationRepository) GetUnreadCount(receiverID string) (int64, error) {
|
||||
func (r *systemNotificationRepository) GetUnreadCount(receiverID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.SystemNotification{}).
|
||||
Where("receiver_id = ? AND is_read = ?", receiverID, false).
|
||||
@@ -72,7 +87,7 @@ func (r *SystemNotificationRepository) GetUnreadCount(receiverID string) (int64,
|
||||
}
|
||||
|
||||
// MarkAsRead 标记单条通知为已读
|
||||
func (r *SystemNotificationRepository) MarkAsRead(id int64, receiverID string) error {
|
||||
func (r *systemNotificationRepository) MarkAsRead(id int64, receiverID string) error {
|
||||
now := model.SystemNotification{}.UpdatedAt
|
||||
return r.db.Model(&model.SystemNotification{}).
|
||||
Where("id = ? AND receiver_id = ?", id, receiverID).
|
||||
@@ -83,7 +98,7 @@ func (r *SystemNotificationRepository) MarkAsRead(id int64, receiverID string) e
|
||||
}
|
||||
|
||||
// MarkAllAsRead 标记用户所有通知为已读
|
||||
func (r *SystemNotificationRepository) MarkAllAsRead(receiverID string) error {
|
||||
func (r *systemNotificationRepository) MarkAllAsRead(receiverID string) error {
|
||||
now := model.SystemNotification{}.UpdatedAt
|
||||
return r.db.Model(&model.SystemNotification{}).
|
||||
Where("receiver_id = ? AND is_read = ?", receiverID, false).
|
||||
@@ -94,13 +109,13 @@ func (r *SystemNotificationRepository) MarkAllAsRead(receiverID string) error {
|
||||
}
|
||||
|
||||
// Delete 删除通知(软删除)
|
||||
func (r *SystemNotificationRepository) Delete(id int64, receiverID string) error {
|
||||
func (r *systemNotificationRepository) Delete(id int64, receiverID string) error {
|
||||
return r.db.Where("id = ? AND receiver_id = ?", id, receiverID).
|
||||
Delete(&model.SystemNotification{}).Error
|
||||
}
|
||||
|
||||
// GetByType 获取用户指定类型的通知
|
||||
func (r *SystemNotificationRepository) GetByType(receiverID string, notifyType model.SystemNotificationType, page, pageSize int) ([]*model.SystemNotification, int64, error) {
|
||||
func (r *systemNotificationRepository) GetByType(receiverID string, notifyType model.SystemNotificationType, page, pageSize int) ([]*model.SystemNotification, int64, error) {
|
||||
var notifications []*model.SystemNotification
|
||||
var total int64
|
||||
|
||||
@@ -119,7 +134,7 @@ func (r *SystemNotificationRepository) GetByType(receiverID string, notifyType m
|
||||
|
||||
// GetByReceiverIDCursor 游标分页获取用户的通知列表
|
||||
// 使用统一的 cursor 包进行游标分页
|
||||
func (r *SystemNotificationRepository) GetByReceiverIDCursor(ctx context.Context, receiverID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.SystemNotification], error) {
|
||||
func (r *systemNotificationRepository) GetByReceiverIDCursor(ctx context.Context, receiverID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.SystemNotification], error) {
|
||||
// 构建基础查询
|
||||
query := r.db.WithContext(ctx).Model(&model.SystemNotification{}).Where("receiver_id = ?", receiverID)
|
||||
|
||||
@@ -165,7 +180,7 @@ func (r *SystemNotificationRepository) GetByReceiverIDCursor(ctx context.Context
|
||||
|
||||
// GetByReceiverIDCursorLegacy 游标分页获取用户的通知列表(旧版兼容接口)
|
||||
// 已废弃:请使用 GetByReceiverIDCursor
|
||||
func (r *SystemNotificationRepository) GetByReceiverIDCursorLegacy(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) {
|
||||
func (r *systemNotificationRepository) GetByReceiverIDCursorLegacy(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) {
|
||||
// 构建基础查询
|
||||
query := r.db.Model(&model.SystemNotification{}).Where("receiver_id = ?", receiverID)
|
||||
|
||||
|
||||
@@ -36,21 +36,36 @@ type activityCache interface {
|
||||
Expire(ctx context.Context, key string, ttl time.Duration) error
|
||||
}
|
||||
|
||||
// UserActivityRepository 用户活跃数据仓储
|
||||
type UserActivityRepository struct {
|
||||
// UserActivityRepository 用户活跃数据仓储接口
|
||||
type UserActivityRepository interface {
|
||||
RecordActivity(ctx context.Context, userID, loginType, ip, userAgent string) error
|
||||
GetDAU(ctx context.Context, date time.Time) (int64, error)
|
||||
GetActiveUserIDs(ctx context.Context, date time.Time) ([]string, error)
|
||||
GetUserActivityCount(ctx context.Context, userID string, startDate, endDate time.Time) (int64, error)
|
||||
SaveStat(ctx context.Context, stat *model.UserActivityStat) error
|
||||
GetStat(ctx context.Context, statType string, date time.Time) (*model.UserActivityStat, error)
|
||||
RecordActivityToRedis(ctx context.Context, userID string) error
|
||||
GetDAUFromRedis(ctx context.Context, date time.Time) (int64, error)
|
||||
GetWAUFromRedis(ctx context.Context, year int, week int) (int64, error)
|
||||
GetMAUFromRedis(ctx context.Context, year int, month int) (int64, error)
|
||||
GetDAUUserIDsFromRedis(ctx context.Context, date time.Time) ([]string, error)
|
||||
}
|
||||
|
||||
// userActivityRepository 用户活跃数据仓储实现
|
||||
type userActivityRepository struct {
|
||||
db *gorm.DB
|
||||
cache activityCache
|
||||
}
|
||||
|
||||
// NewUserActivityRepository 创建用户活跃数据仓储
|
||||
func NewUserActivityRepository(db *gorm.DB, cache activityCache) *UserActivityRepository {
|
||||
return &UserActivityRepository{db: db, cache: cache}
|
||||
func NewUserActivityRepository(db *gorm.DB, cache activityCache) UserActivityRepository {
|
||||
return &userActivityRepository{db: db, cache: cache}
|
||||
}
|
||||
|
||||
// ==================== 数据库操作方法 ====================
|
||||
|
||||
// RecordActivity 记录用户活跃(写入数据库)
|
||||
func (r *UserActivityRepository) RecordActivity(ctx context.Context, userID, loginType, ip, userAgent string) error {
|
||||
func (r *userActivityRepository) RecordActivity(ctx context.Context, userID, loginType, ip, userAgent string) error {
|
||||
log := &model.UserActiveLog{
|
||||
UserID: userID,
|
||||
ActiveDate: time.Now(),
|
||||
@@ -62,7 +77,7 @@ func (r *UserActivityRepository) RecordActivity(ctx context.Context, userID, log
|
||||
}
|
||||
|
||||
// GetDAU 获取指定日期的日活用户数(从数据库)
|
||||
func (r *UserActivityRepository) GetDAU(ctx context.Context, date time.Time) (int64, error) {
|
||||
func (r *userActivityRepository) GetDAU(ctx context.Context, date time.Time) (int64, error) {
|
||||
var count int64
|
||||
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
endOfDay := startOfDay.Add(24 * time.Hour)
|
||||
@@ -77,7 +92,7 @@ func (r *UserActivityRepository) GetDAU(ctx context.Context, date time.Time) (in
|
||||
}
|
||||
|
||||
// GetActiveUserIDs 获取指定日期的活跃用户ID列表
|
||||
func (r *UserActivityRepository) GetActiveUserIDs(ctx context.Context, date time.Time) ([]string, error) {
|
||||
func (r *userActivityRepository) GetActiveUserIDs(ctx context.Context, date time.Time) ([]string, error) {
|
||||
var userIDs []string
|
||||
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
endOfDay := startOfDay.Add(24 * time.Hour)
|
||||
@@ -92,7 +107,7 @@ func (r *UserActivityRepository) GetActiveUserIDs(ctx context.Context, date time
|
||||
}
|
||||
|
||||
// GetUserActivityCount 获取用户在指定日期范围内的活跃天数
|
||||
func (r *UserActivityRepository) GetUserActivityCount(ctx context.Context, userID string, startDate, endDate time.Time) (int64, error) {
|
||||
func (r *userActivityRepository) GetUserActivityCount(ctx context.Context, userID string, startDate, endDate time.Time) (int64, error) {
|
||||
var count int64
|
||||
startOfDay := time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, 0, 0, startDate.Location())
|
||||
endOfDay := time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 0, endDate.Location())
|
||||
@@ -107,7 +122,7 @@ func (r *UserActivityRepository) GetUserActivityCount(ctx context.Context, userI
|
||||
}
|
||||
|
||||
// SaveStat 保存统计数据快照
|
||||
func (r *UserActivityRepository) SaveStat(ctx context.Context, stat *model.UserActivityStat) error {
|
||||
func (r *userActivityRepository) SaveStat(ctx context.Context, stat *model.UserActivityStat) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "stat_type"}, {Name: "stat_date"}},
|
||||
@@ -117,7 +132,7 @@ func (r *UserActivityRepository) SaveStat(ctx context.Context, stat *model.UserA
|
||||
}
|
||||
|
||||
// GetStat 获取统计数据快照
|
||||
func (r *UserActivityRepository) GetStat(ctx context.Context, statType string, date time.Time) (*model.UserActivityStat, error) {
|
||||
func (r *userActivityRepository) GetStat(ctx context.Context, statType string, date time.Time) (*model.UserActivityStat, error) {
|
||||
var stat model.UserActivityStat
|
||||
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
|
||||
@@ -134,7 +149,7 @@ func (r *UserActivityRepository) GetStat(ctx context.Context, statType string, d
|
||||
|
||||
// RecordActivityToRedis 记录用户活跃到 Redis Sorted Set
|
||||
// score 为时间戳,member 为用户ID
|
||||
func (r *UserActivityRepository) RecordActivityToRedis(ctx context.Context, userID string) error {
|
||||
func (r *userActivityRepository) RecordActivityToRedis(ctx context.Context, userID string) error {
|
||||
now := time.Now()
|
||||
score := float64(now.Unix())
|
||||
|
||||
@@ -164,7 +179,7 @@ func (r *UserActivityRepository) RecordActivityToRedis(ctx context.Context, user
|
||||
}
|
||||
|
||||
// GetDAUFromRedis 从 Redis 获取日活用户数
|
||||
func (r *UserActivityRepository) GetDAUFromRedis(ctx context.Context, date time.Time) (int64, error) {
|
||||
func (r *userActivityRepository) GetDAUFromRedis(ctx context.Context, date time.Time) (int64, error) {
|
||||
key := formatDAUKey(date)
|
||||
count, err := r.cache.ZCard(ctx, key)
|
||||
if err != nil {
|
||||
@@ -174,7 +189,7 @@ func (r *UserActivityRepository) GetDAUFromRedis(ctx context.Context, date time.
|
||||
}
|
||||
|
||||
// GetWAUFromRedis 从 Redis 获取周活用户数
|
||||
func (r *UserActivityRepository) GetWAUFromRedis(ctx context.Context, year int, week int) (int64, error) {
|
||||
func (r *userActivityRepository) GetWAUFromRedis(ctx context.Context, year int, week int) (int64, error) {
|
||||
key := formatWAUKey(year, week)
|
||||
count, err := r.cache.ZCard(ctx, key)
|
||||
if err != nil {
|
||||
@@ -184,7 +199,7 @@ func (r *UserActivityRepository) GetWAUFromRedis(ctx context.Context, year int,
|
||||
}
|
||||
|
||||
// GetMAUFromRedis 从 Redis 获取月活用户数
|
||||
func (r *UserActivityRepository) GetMAUFromRedis(ctx context.Context, year int, month int) (int64, error) {
|
||||
func (r *userActivityRepository) GetMAUFromRedis(ctx context.Context, year int, month int) (int64, error) {
|
||||
key := formatMAUKey(year, month)
|
||||
count, err := r.cache.ZCard(ctx, key)
|
||||
if err != nil {
|
||||
@@ -194,7 +209,7 @@ func (r *UserActivityRepository) GetMAUFromRedis(ctx context.Context, year int,
|
||||
}
|
||||
|
||||
// GetDAUUserIDsFromRedis 获取日活用户ID列表
|
||||
func (r *UserActivityRepository) GetDAUUserIDsFromRedis(ctx context.Context, date time.Time) ([]string, error) {
|
||||
func (r *userActivityRepository) GetDAUUserIDsFromRedis(ctx context.Context, date time.Time) ([]string, error) {
|
||||
key := formatDAUKey(date)
|
||||
// 获取所有成员(从最小分数到最大分数)
|
||||
members, err := r.cache.ZRangeByScore(ctx, key, "-inf", "+inf", 0, -1)
|
||||
|
||||
@@ -8,23 +8,60 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// UserRepository 用户仓储
|
||||
type UserRepository struct {
|
||||
// UserRepository 用户仓储接口
|
||||
type UserRepository interface {
|
||||
Create(user *model.User) error
|
||||
GetByID(id string) (*model.User, error)
|
||||
GetByUsername(username string) (*model.User, error)
|
||||
GetByEmail(email string) (*model.User, error)
|
||||
GetByPhone(phone string) (*model.User, error)
|
||||
Update(user *model.User) error
|
||||
Delete(id string) error
|
||||
List(page, pageSize int) ([]*model.User, int64, error)
|
||||
GetFollowers(userID string, page, pageSize int) ([]*model.User, int64, error)
|
||||
GetFollowing(userID string, page, pageSize int) ([]*model.User, int64, error)
|
||||
CreateFollow(follow *model.Follow) error
|
||||
DeleteFollow(followerID, followingID string) error
|
||||
IsFollowing(followerID, followingID string) (bool, error)
|
||||
IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error)
|
||||
IncrementFollowersCount(userID string) error
|
||||
DecrementFollowersCount(userID string) error
|
||||
IncrementFollowingCount(userID string) error
|
||||
DecrementFollowingCount(userID string) error
|
||||
RefreshFollowersCount(userID string) error
|
||||
GetPostsCount(userID string) (int64, error)
|
||||
GetPostsCountBatch(userIDs []string) (map[string]int64, error)
|
||||
RefreshFollowingCount(userID string) error
|
||||
IsBlocked(blockerID, blockedID string) (bool, error)
|
||||
IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error)
|
||||
IsBlockedEitherDirection(userA, userB string) (bool, error)
|
||||
BlockUserAndCleanupRelations(blockerID, blockedID string) error
|
||||
UnblockUser(blockerID, blockedID string) error
|
||||
GetBlockedUsers(blockerID string, page, pageSize int) ([]*model.User, int64, error)
|
||||
Search(keyword string, page, pageSize int) ([]*model.User, int64, error)
|
||||
GetMutualFollowStatus(currentUserID string, targetUserIDs []string) (map[string][2]bool, error)
|
||||
AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error)
|
||||
GetCommentsCount(userID string) (int64, error)
|
||||
UpdateStatus(userID string, status model.UserStatus) error
|
||||
}
|
||||
|
||||
// userRepository 用户仓储实现
|
||||
type userRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserRepository 创建用户仓储
|
||||
func NewUserRepository(db *gorm.DB) *UserRepository {
|
||||
return &UserRepository{db: db}
|
||||
func NewUserRepository(db *gorm.DB) UserRepository {
|
||||
return &userRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
func (r *UserRepository) Create(user *model.User) error {
|
||||
func (r *userRepository) Create(user *model.User) error {
|
||||
return r.db.Create(user).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户
|
||||
func (r *UserRepository) GetByID(id string) (*model.User, error) {
|
||||
func (r *userRepository) GetByID(id string) (*model.User, error) {
|
||||
var user model.User
|
||||
err := r.db.First(&user, "id = ?", id).Error
|
||||
if err != nil {
|
||||
@@ -34,7 +71,7 @@ func (r *UserRepository) GetByID(id string) (*model.User, error) {
|
||||
}
|
||||
|
||||
// GetByUsername 根据用户名获取用户
|
||||
func (r *UserRepository) GetByUsername(username string) (*model.User, error) {
|
||||
func (r *userRepository) GetByUsername(username string) (*model.User, error) {
|
||||
var user model.User
|
||||
err := r.db.First(&user, "username = ?", username).Error
|
||||
if err != nil {
|
||||
@@ -44,7 +81,7 @@ func (r *UserRepository) GetByUsername(username string) (*model.User, error) {
|
||||
}
|
||||
|
||||
// GetByEmail 根据邮箱获取用户
|
||||
func (r *UserRepository) GetByEmail(email string) (*model.User, error) {
|
||||
func (r *userRepository) GetByEmail(email string) (*model.User, error) {
|
||||
var user model.User
|
||||
err := r.db.First(&user, "email = ?", email).Error
|
||||
if err != nil {
|
||||
@@ -54,7 +91,7 @@ func (r *UserRepository) GetByEmail(email string) (*model.User, error) {
|
||||
}
|
||||
|
||||
// GetByPhone 根据手机号获取用户
|
||||
func (r *UserRepository) GetByPhone(phone string) (*model.User, error) {
|
||||
func (r *userRepository) GetByPhone(phone string) (*model.User, error) {
|
||||
var user model.User
|
||||
err := r.db.First(&user, "phone = ?", phone).Error
|
||||
if err != nil {
|
||||
@@ -64,17 +101,17 @@ func (r *UserRepository) GetByPhone(phone string) (*model.User, error) {
|
||||
}
|
||||
|
||||
// Update 更新用户
|
||||
func (r *UserRepository) Update(user *model.User) error {
|
||||
func (r *userRepository) Update(user *model.User) error {
|
||||
return r.db.Save(user).Error
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (r *UserRepository) Delete(id string) error {
|
||||
func (r *userRepository) Delete(id string) error {
|
||||
return r.db.Delete(&model.User{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// List 分页获取用户列表
|
||||
func (r *UserRepository) List(page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (r *userRepository) List(page, pageSize int) ([]*model.User, int64, error) {
|
||||
var users []*model.User
|
||||
var total int64
|
||||
|
||||
@@ -87,7 +124,7 @@ func (r *UserRepository) List(page, pageSize int) ([]*model.User, int64, error)
|
||||
}
|
||||
|
||||
// GetFollowers 获取用户粉丝
|
||||
func (r *UserRepository) GetFollowers(userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (r *userRepository) GetFollowers(userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
var users []*model.User
|
||||
var total int64
|
||||
|
||||
@@ -104,7 +141,7 @@ func (r *UserRepository) GetFollowers(userID string, page, pageSize int) ([]*mod
|
||||
}
|
||||
|
||||
// GetFollowing 获取用户关注
|
||||
func (r *UserRepository) GetFollowing(userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (r *userRepository) GetFollowing(userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
var users []*model.User
|
||||
var total int64
|
||||
|
||||
@@ -121,17 +158,17 @@ func (r *UserRepository) GetFollowing(userID string, page, pageSize int) ([]*mod
|
||||
}
|
||||
|
||||
// CreateFollow 创建关注关系
|
||||
func (r *UserRepository) CreateFollow(follow *model.Follow) error {
|
||||
func (r *userRepository) CreateFollow(follow *model.Follow) error {
|
||||
return r.db.Create(follow).Error
|
||||
}
|
||||
|
||||
// DeleteFollow 删除关注关系
|
||||
func (r *UserRepository) DeleteFollow(followerID, followingID string) error {
|
||||
func (r *userRepository) DeleteFollow(followerID, followingID string) error {
|
||||
return r.db.Where("follower_id = ? AND following_id = ?", followerID, followingID).Delete(&model.Follow{}).Error
|
||||
}
|
||||
|
||||
// IsFollowing 检查是否关注了某用户
|
||||
func (r *UserRepository) IsFollowing(followerID, followingID string) (bool, error) {
|
||||
func (r *userRepository) IsFollowing(followerID, followingID string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Follow{}).Where("follower_id = ? AND following_id = ?", followerID, followingID).Count(&count).Error
|
||||
if err != nil {
|
||||
@@ -142,7 +179,7 @@ func (r *UserRepository) IsFollowing(followerID, followingID string) (bool, erro
|
||||
|
||||
// IsFollowingBatch 批量检查当前用户是否关注了目标用户(解决 N+1 问题)
|
||||
// 返回 map[targetUserID]bool
|
||||
func (r *UserRepository) IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error) {
|
||||
func (r *userRepository) IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error) {
|
||||
result := make(map[string]bool)
|
||||
if len(targetUserIDs) == 0 || followerID == "" {
|
||||
return result, nil
|
||||
@@ -171,31 +208,31 @@ func (r *UserRepository) IsFollowingBatch(followerID string, targetUserIDs []str
|
||||
}
|
||||
|
||||
// IncrementFollowersCount 增加用户粉丝数
|
||||
func (r *UserRepository) IncrementFollowersCount(userID string) error {
|
||||
func (r *userRepository) IncrementFollowersCount(userID string) error {
|
||||
return r.db.Model(&model.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("followers_count", gorm.Expr("followers_count + 1")).Error
|
||||
}
|
||||
|
||||
// DecrementFollowersCount 减少用户粉丝数
|
||||
func (r *UserRepository) DecrementFollowersCount(userID string) error {
|
||||
func (r *userRepository) DecrementFollowersCount(userID string) error {
|
||||
return r.db.Model(&model.User{}).Where("id = ? AND followers_count > 0", userID).
|
||||
UpdateColumn("followers_count", gorm.Expr("followers_count - 1")).Error
|
||||
}
|
||||
|
||||
// IncrementFollowingCount 增加用户关注数
|
||||
func (r *UserRepository) IncrementFollowingCount(userID string) error {
|
||||
func (r *userRepository) IncrementFollowingCount(userID string) error {
|
||||
return r.db.Model(&model.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("following_count", gorm.Expr("following_count + 1")).Error
|
||||
}
|
||||
|
||||
// DecrementFollowingCount 减少用户关注数
|
||||
func (r *UserRepository) DecrementFollowingCount(userID string) error {
|
||||
func (r *userRepository) DecrementFollowingCount(userID string) error {
|
||||
return r.db.Model(&model.User{}).Where("id = ? AND following_count > 0", userID).
|
||||
UpdateColumn("following_count", gorm.Expr("following_count - 1")).Error
|
||||
}
|
||||
|
||||
// RefreshFollowersCount 刷新用户粉丝数(通过实际计数)
|
||||
func (r *UserRepository) RefreshFollowersCount(userID string) error {
|
||||
func (r *userRepository) RefreshFollowersCount(userID string) error {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Follow{}).Where("following_id = ?", userID).Count(&count).Error
|
||||
if err != nil {
|
||||
@@ -206,7 +243,7 @@ func (r *UserRepository) RefreshFollowersCount(userID string) error {
|
||||
}
|
||||
|
||||
// GetPostsCount 获取用户帖子数(实时计算)
|
||||
func (r *UserRepository) GetPostsCount(userID string) (int64, error) {
|
||||
func (r *userRepository) GetPostsCount(userID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Post{}).
|
||||
Where("user_id = ? AND status = ?", userID, model.PostStatusPublished).
|
||||
@@ -216,7 +253,7 @@ func (r *UserRepository) GetPostsCount(userID string) (int64, error) {
|
||||
|
||||
// GetPostsCountBatch 批量获取用户帖子数(实时计算)
|
||||
// 返回 map[userID]postsCount
|
||||
func (r *UserRepository) GetPostsCountBatch(userIDs []string) (map[string]int64, error) {
|
||||
func (r *userRepository) GetPostsCountBatch(userIDs []string) (map[string]int64, error) {
|
||||
result := make(map[string]int64)
|
||||
if len(userIDs) == 0 {
|
||||
return result, nil
|
||||
@@ -251,7 +288,7 @@ func (r *UserRepository) GetPostsCountBatch(userIDs []string) (map[string]int64,
|
||||
}
|
||||
|
||||
// RefreshFollowingCount 刷新用户关注数(通过实际计数)
|
||||
func (r *UserRepository) RefreshFollowingCount(userID string) error {
|
||||
func (r *userRepository) RefreshFollowingCount(userID string) error {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Follow{}).Where("follower_id = ?", userID).Count(&count).Error
|
||||
if err != nil {
|
||||
@@ -262,7 +299,7 @@ func (r *UserRepository) RefreshFollowingCount(userID string) error {
|
||||
}
|
||||
|
||||
// IsBlocked 检查拉黑关系是否存在(blocker -> blocked)
|
||||
func (r *UserRepository) IsBlocked(blockerID, blockedID string) (bool, error) {
|
||||
func (r *userRepository) IsBlocked(blockerID, blockedID string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.UserBlock{}).
|
||||
Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID).
|
||||
@@ -275,7 +312,7 @@ func (r *UserRepository) IsBlocked(blockerID, blockedID string) (bool, error) {
|
||||
|
||||
// IsBlockedBatch 批量检查拉黑关系(解决 N+1 问题)
|
||||
// 返回 map[blockedUserID]bool
|
||||
func (r *UserRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error) {
|
||||
func (r *userRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error) {
|
||||
result := make(map[string]bool)
|
||||
if len(blockedIDs) == 0 || blockerID == "" {
|
||||
return result, nil
|
||||
@@ -304,7 +341,7 @@ func (r *UserRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (
|
||||
}
|
||||
|
||||
// IsBlockedEitherDirection 检查是否任一方向存在拉黑
|
||||
func (r *UserRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) {
|
||||
func (r *userRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.UserBlock{}).
|
||||
Where("(blocker_id = ? AND blocked_id = ?) OR (blocker_id = ? AND blocked_id = ?)",
|
||||
@@ -317,7 +354,7 @@ func (r *UserRepository) IsBlockedEitherDirection(userA, userB string) (bool, er
|
||||
}
|
||||
|
||||
// BlockUserAndCleanupRelations 拉黑用户并清理双向关注关系(事务)
|
||||
func (r *UserRepository) BlockUserAndCleanupRelations(blockerID, blockedID string) error {
|
||||
func (r *userRepository) BlockUserAndCleanupRelations(blockerID, blockedID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
block := &model.UserBlock{
|
||||
BlockerID: blockerID,
|
||||
@@ -364,13 +401,13 @@ func (r *UserRepository) BlockUserAndCleanupRelations(blockerID, blockedID strin
|
||||
}
|
||||
|
||||
// UnblockUser 取消拉黑
|
||||
func (r *UserRepository) UnblockUser(blockerID, blockedID string) error {
|
||||
func (r *userRepository) UnblockUser(blockerID, blockedID string) error {
|
||||
return r.db.Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID).
|
||||
Delete(&model.UserBlock{}).Error
|
||||
}
|
||||
|
||||
// GetBlockedUsers 获取用户黑名单列表
|
||||
func (r *UserRepository) GetBlockedUsers(blockerID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (r *userRepository) GetBlockedUsers(blockerID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
var users []*model.User
|
||||
var total int64
|
||||
|
||||
@@ -388,7 +425,7 @@ func (r *UserRepository) GetBlockedUsers(blockerID string, page, pageSize int) (
|
||||
}
|
||||
|
||||
// Search 搜索用户
|
||||
func (r *UserRepository) Search(keyword string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (r *userRepository) Search(keyword string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
var users []*model.User
|
||||
var total int64
|
||||
|
||||
@@ -421,7 +458,7 @@ func (r *UserRepository) Search(keyword string, page, pageSize int) ([]*model.Us
|
||||
|
||||
// GetMutualFollowStatus 批量获取双向关注状态
|
||||
// 返回 map[userID][isFollowing, isFollowingMe]
|
||||
func (r *UserRepository) GetMutualFollowStatus(currentUserID string, targetUserIDs []string) (map[string][2]bool, error) {
|
||||
func (r *userRepository) GetMutualFollowStatus(currentUserID string, targetUserIDs []string) (map[string][2]bool, error) {
|
||||
result := make(map[string][2]bool)
|
||||
|
||||
if len(targetUserIDs) == 0 {
|
||||
@@ -465,7 +502,7 @@ func (r *UserRepository) GetMutualFollowStatus(currentUserID string, targetUserI
|
||||
}
|
||||
|
||||
// AdminList 管理端分页获取用户列表(支持关键词和状态筛选)
|
||||
func (r *UserRepository) AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error) {
|
||||
func (r *userRepository) AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error) {
|
||||
var users []*model.User
|
||||
var total int64
|
||||
|
||||
@@ -501,7 +538,7 @@ func (r *UserRepository) AdminList(page, pageSize int, keyword, status string) (
|
||||
}
|
||||
|
||||
// GetCommentsCount 获取用户评论数
|
||||
func (r *UserRepository) GetCommentsCount(userID string) (int64, error) {
|
||||
func (r *userRepository) GetCommentsCount(userID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Comment{}).
|
||||
Where("user_id = ?", userID).
|
||||
@@ -510,6 +547,6 @@ func (r *UserRepository) GetCommentsCount(userID string) (int64, error) {
|
||||
}
|
||||
|
||||
// UpdateStatus 更新用户状态
|
||||
func (r *UserRepository) UpdateStatus(userID string, status model.UserStatus) error {
|
||||
func (r *userRepository) UpdateStatus(userID string, status model.UserStatus) error {
|
||||
return r.db.Model(&model.User{}).Where("id = ?", userID).Update("status", status).Error
|
||||
}
|
||||
|
||||
@@ -7,18 +7,29 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// VoteRepository 投票仓储
|
||||
type VoteRepository struct {
|
||||
// VoteRepository 投票仓储接口
|
||||
type VoteRepository interface {
|
||||
CreateOptions(postID string, options []string) error
|
||||
GetOptionsByPostID(postID string) ([]model.VoteOption, error)
|
||||
Vote(postID, userID, optionID string) error
|
||||
Unvote(postID, userID string) error
|
||||
GetUserVote(postID, userID string) (*model.UserVote, error)
|
||||
UpdateOption(optionID, content string) error
|
||||
DeleteOptionsByPostID(postID string) error
|
||||
}
|
||||
|
||||
// voteRepository 投票仓储实现
|
||||
type voteRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewVoteRepository 创建投票仓储
|
||||
func NewVoteRepository(db *gorm.DB) *VoteRepository {
|
||||
return &VoteRepository{db: db}
|
||||
func NewVoteRepository(db *gorm.DB) VoteRepository {
|
||||
return &voteRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateOptions 批量创建投票选项(使用批量插入,避免 N+1 问题)
|
||||
func (r *VoteRepository) CreateOptions(postID string, options []string) error {
|
||||
func (r *voteRepository) CreateOptions(postID string, options []string) error {
|
||||
if len(options) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -38,14 +49,14 @@ func (r *VoteRepository) CreateOptions(postID string, options []string) error {
|
||||
}
|
||||
|
||||
// GetOptionsByPostID 获取帖子的所有投票选项
|
||||
func (r *VoteRepository) GetOptionsByPostID(postID string) ([]model.VoteOption, error) {
|
||||
func (r *voteRepository) GetOptionsByPostID(postID string) ([]model.VoteOption, error) {
|
||||
var options []model.VoteOption
|
||||
err := r.db.Where("post_id = ?", postID).Order("sort_order ASC").Find(&options).Error
|
||||
return options, err
|
||||
}
|
||||
|
||||
// Vote 用户投票
|
||||
func (r *VoteRepository) Vote(postID, userID, optionID string) error {
|
||||
func (r *voteRepository) Vote(postID, userID, optionID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 检查用户是否已投票
|
||||
var existing model.UserVote
|
||||
@@ -83,7 +94,7 @@ func (r *VoteRepository) Vote(postID, userID, optionID string) error {
|
||||
}
|
||||
|
||||
// Unvote 取消投票
|
||||
func (r *VoteRepository) Unvote(postID, userID string) error {
|
||||
func (r *voteRepository) Unvote(postID, userID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 获取用户的投票记录
|
||||
var userVote model.UserVote
|
||||
@@ -112,7 +123,7 @@ func (r *VoteRepository) Unvote(postID, userID string) error {
|
||||
}
|
||||
|
||||
// GetUserVote 获取用户在指定帖子的投票
|
||||
func (r *VoteRepository) GetUserVote(postID, userID string) (*model.UserVote, error) {
|
||||
func (r *voteRepository) GetUserVote(postID, userID string) (*model.UserVote, error) {
|
||||
var userVote model.UserVote
|
||||
err := r.db.Where("post_id = ? AND user_id = ?", postID, userID).First(&userVote).Error
|
||||
if err != nil {
|
||||
@@ -125,13 +136,13 @@ func (r *VoteRepository) GetUserVote(postID, userID string) (*model.UserVote, er
|
||||
}
|
||||
|
||||
// UpdateOption 更新选项内容
|
||||
func (r *VoteRepository) UpdateOption(optionID, content string) error {
|
||||
func (r *voteRepository) UpdateOption(optionID, content string) error {
|
||||
return r.db.Model(&model.VoteOption{}).Where("id = ?", optionID).
|
||||
Update("content", content).Error
|
||||
}
|
||||
|
||||
// DeleteOptionsByPostID 删除帖子的所有投票选项
|
||||
func (r *VoteRepository) DeleteOptionsByPostID(postID string) error {
|
||||
func (r *voteRepository) DeleteOptionsByPostID(postID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 删除关联的用户投票记录
|
||||
if err := tx.Where("post_id = ?", postID).Delete(&model.UserVote{}).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user