refactor: update repository interfaces and improve dependency injection
All checks were successful
Build Backend / build (push) Successful in 12m45s
Build Backend / build-docker (push) Successful in 2m40s

- 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:
lafay
2026-03-26 18:14:16 +08:00
parent 7b41dfeb00
commit c6848aba06
50 changed files with 1034 additions and 663 deletions

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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).

View File

@@ -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").

View File

@@ -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 &notificationRepository{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(&notification, "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)

View File

@@ -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

View File

@@ -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 一致,按最新发布排序

View File

@@ -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

View File

@@ -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(&notification, "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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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 {