2026-03-09 21:28:58 +08:00
|
|
|
|
package repository
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"carrot_bbs/internal/model"
|
2026-03-12 23:40:21 +08:00
|
|
|
|
"strings"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
"gorm.io/gorm/clause"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-26 18:14:16 +08:00
|
|
|
|
// 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 {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
db *gorm.DB
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewUserRepository 创建用户仓储
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func NewUserRepository(db *gorm.DB) UserRepository {
|
|
|
|
|
|
return &userRepository{db: db}
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create 创建用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) Create(user *model.User) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Create(user).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetByID 根据ID获取用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetByID(id string) (*model.User, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var user model.User
|
|
|
|
|
|
err := r.db.First(&user, "id = ?", id).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return &user, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetByUsername 根据用户名获取用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetByUsername(username string) (*model.User, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var user model.User
|
|
|
|
|
|
err := r.db.First(&user, "username = ?", username).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return &user, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetByEmail 根据邮箱获取用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetByEmail(email string) (*model.User, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var user model.User
|
|
|
|
|
|
err := r.db.First(&user, "email = ?", email).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return &user, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetByPhone 根据手机号获取用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetByPhone(phone string) (*model.User, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var user model.User
|
|
|
|
|
|
err := r.db.First(&user, "phone = ?", phone).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return &user, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update 更新用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) Update(user *model.User) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Save(user).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Delete 删除用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) Delete(id string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Delete(&model.User{}, "id = ?", id).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// List 分页获取用户列表
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) List(page, pageSize int) ([]*model.User, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var users []*model.User
|
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
|
|
r.db.Model(&model.User{}).Count(&total)
|
|
|
|
|
|
|
|
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
|
|
err := r.db.Order("created_at DESC, id DESC").Offset(offset).Limit(pageSize).Find(&users).Error
|
|
|
|
|
|
|
|
|
|
|
|
return users, total, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetFollowers 获取用户粉丝
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetFollowers(userID string, page, pageSize int) ([]*model.User, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var users []*model.User
|
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
|
|
subQuery := r.db.Model(&model.Follow{}).Where("following_id = ?", userID).Select("follower_id")
|
|
|
|
|
|
r.db.Model(&model.User{}).Where("id IN (?)", subQuery).Count(&total)
|
|
|
|
|
|
|
|
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
|
|
err := r.db.Where("id IN (?)", subQuery).
|
|
|
|
|
|
Order("created_at DESC, id DESC").
|
|
|
|
|
|
Offset(offset).Limit(pageSize).
|
|
|
|
|
|
Find(&users).Error
|
|
|
|
|
|
|
|
|
|
|
|
return users, total, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetFollowing 获取用户关注
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetFollowing(userID string, page, pageSize int) ([]*model.User, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var users []*model.User
|
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
|
|
subQuery := r.db.Model(&model.Follow{}).Where("follower_id = ?", userID).Select("following_id")
|
|
|
|
|
|
r.db.Model(&model.User{}).Where("id IN (?)", subQuery).Count(&total)
|
|
|
|
|
|
|
|
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
|
|
err := r.db.Where("id IN (?)", subQuery).
|
|
|
|
|
|
Order("created_at DESC, id DESC").
|
|
|
|
|
|
Offset(offset).Limit(pageSize).
|
|
|
|
|
|
Find(&users).Error
|
|
|
|
|
|
|
|
|
|
|
|
return users, total, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateFollow 创建关注关系
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) CreateFollow(follow *model.Follow) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Create(follow).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DeleteFollow 删除关注关系
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) DeleteFollow(followerID, followingID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Where("follower_id = ? AND following_id = ?", followerID, followingID).Delete(&model.Follow{}).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsFollowing 检查是否关注了某用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) IsFollowing(followerID, followingID string) (bool, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var count int64
|
|
|
|
|
|
err := r.db.Model(&model.Follow{}).Where("follower_id = ? AND following_id = ?", followerID, followingID).Count(&count).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return count > 0, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 01:50:54 +08:00
|
|
|
|
// IsFollowingBatch 批量检查当前用户是否关注了目标用户(解决 N+1 问题)
|
|
|
|
|
|
// 返回 map[targetUserID]bool
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error) {
|
2026-03-26 01:50:54 +08:00
|
|
|
|
result := make(map[string]bool)
|
|
|
|
|
|
if len(targetUserIDs) == 0 || followerID == "" {
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化所有目标用户为 false
|
|
|
|
|
|
for _, id := range targetUserIDs {
|
|
|
|
|
|
result[id] = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 一次性查询所有关注记录
|
|
|
|
|
|
var followingIDs []string
|
|
|
|
|
|
err := r.db.Model(&model.Follow{}).
|
|
|
|
|
|
Where("follower_id = ? AND following_id IN ?", followerID, targetUserIDs).
|
|
|
|
|
|
Pluck("following_id", &followingIDs).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新已关注的用户
|
|
|
|
|
|
for _, id := range followingIDs {
|
|
|
|
|
|
result[id] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// IncrementFollowersCount 增加用户粉丝数
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) IncrementFollowersCount(userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Model(&model.User{}).Where("id = ?", userID).
|
|
|
|
|
|
UpdateColumn("followers_count", gorm.Expr("followers_count + 1")).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DecrementFollowersCount 减少用户粉丝数
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) DecrementFollowersCount(userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Model(&model.User{}).Where("id = ? AND followers_count > 0", userID).
|
|
|
|
|
|
UpdateColumn("followers_count", gorm.Expr("followers_count - 1")).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IncrementFollowingCount 增加用户关注数
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) IncrementFollowingCount(userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Model(&model.User{}).Where("id = ?", userID).
|
|
|
|
|
|
UpdateColumn("following_count", gorm.Expr("following_count + 1")).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DecrementFollowingCount 减少用户关注数
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) DecrementFollowingCount(userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Model(&model.User{}).Where("id = ? AND following_count > 0", userID).
|
|
|
|
|
|
UpdateColumn("following_count", gorm.Expr("following_count - 1")).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RefreshFollowersCount 刷新用户粉丝数(通过实际计数)
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) RefreshFollowersCount(userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var count int64
|
|
|
|
|
|
err := r.db.Model(&model.Follow{}).Where("following_id = ?", userID).Count(&count).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return r.db.Model(&model.User{}).Where("id = ?", userID).
|
|
|
|
|
|
UpdateColumn("followers_count", count).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPostsCount 获取用户帖子数(实时计算)
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetPostsCount(userID string) (int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var count int64
|
2026-03-10 12:58:23 +08:00
|
|
|
|
err := r.db.Model(&model.Post{}).
|
|
|
|
|
|
Where("user_id = ? AND status = ?", userID, model.PostStatusPublished).
|
|
|
|
|
|
Count(&count).Error
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return count, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPostsCountBatch 批量获取用户帖子数(实时计算)
|
|
|
|
|
|
// 返回 map[userID]postsCount
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetPostsCountBatch(userIDs []string) (map[string]int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
result := make(map[string]int64)
|
|
|
|
|
|
if len(userIDs) == 0 {
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化所有用户ID的计数为0
|
|
|
|
|
|
for _, userID := range userIDs {
|
|
|
|
|
|
result[userID] = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 GROUP BY 一次性查询所有用户的帖子数
|
|
|
|
|
|
type CountResult struct {
|
|
|
|
|
|
UserID string
|
|
|
|
|
|
Count int64
|
|
|
|
|
|
}
|
|
|
|
|
|
var counts []CountResult
|
|
|
|
|
|
err := r.db.Model(&model.Post{}).
|
|
|
|
|
|
Select("user_id, count(*) as count").
|
2026-03-10 12:58:23 +08:00
|
|
|
|
Where("user_id IN ? AND status = ?", userIDs, model.PostStatusPublished).
|
2026-03-09 21:28:58 +08:00
|
|
|
|
Group("user_id").
|
|
|
|
|
|
Scan(&counts).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新查询结果
|
|
|
|
|
|
for _, c := range counts {
|
|
|
|
|
|
result[c.UserID] = c.Count
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RefreshFollowingCount 刷新用户关注数(通过实际计数)
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) RefreshFollowingCount(userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var count int64
|
|
|
|
|
|
err := r.db.Model(&model.Follow{}).Where("follower_id = ?", userID).Count(&count).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return r.db.Model(&model.User{}).Where("id = ?", userID).
|
|
|
|
|
|
UpdateColumn("following_count", count).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsBlocked 检查拉黑关系是否存在(blocker -> blocked)
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) IsBlocked(blockerID, blockedID string) (bool, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var count int64
|
|
|
|
|
|
err := r.db.Model(&model.UserBlock{}).
|
|
|
|
|
|
Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID).
|
|
|
|
|
|
Count(&count).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return count > 0, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 01:50:54 +08:00
|
|
|
|
// IsBlockedBatch 批量检查拉黑关系(解决 N+1 问题)
|
|
|
|
|
|
// 返回 map[blockedUserID]bool
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error) {
|
2026-03-26 01:50:54 +08:00
|
|
|
|
result := make(map[string]bool)
|
|
|
|
|
|
if len(blockedIDs) == 0 || blockerID == "" {
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化所有目标用户为 false
|
|
|
|
|
|
for _, id := range blockedIDs {
|
|
|
|
|
|
result[id] = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 一次性查询所有拉黑记录
|
|
|
|
|
|
var blockedUserIDs []string
|
|
|
|
|
|
err := r.db.Model(&model.UserBlock{}).
|
|
|
|
|
|
Where("blocker_id = ? AND blocked_id IN ?", blockerID, blockedIDs).
|
|
|
|
|
|
Pluck("blocked_id", &blockedUserIDs).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新已拉黑的用户
|
|
|
|
|
|
for _, id := range blockedUserIDs {
|
|
|
|
|
|
result[id] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// IsBlockedEitherDirection 检查是否任一方向存在拉黑
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var count int64
|
|
|
|
|
|
err := r.db.Model(&model.UserBlock{}).
|
|
|
|
|
|
Where("(blocker_id = ? AND blocked_id = ?) OR (blocker_id = ? AND blocked_id = ?)",
|
|
|
|
|
|
userA, userB, userB, userA).
|
|
|
|
|
|
Count(&count).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return count > 0, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// BlockUserAndCleanupRelations 拉黑用户并清理双向关注关系(事务)
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) BlockUserAndCleanupRelations(blockerID, blockedID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
block := &model.UserBlock{
|
|
|
|
|
|
BlockerID: blockerID,
|
|
|
|
|
|
BlockedID: blockedID,
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := tx.Clauses(clause.OnConflict{
|
|
|
|
|
|
Columns: []clause.Column{{Name: "blocker_id"}, {Name: "blocked_id"}},
|
|
|
|
|
|
DoNothing: true,
|
|
|
|
|
|
}).Create(block).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := tx.Where("follower_id = ? AND following_id = ?", blockerID, blockedID).
|
|
|
|
|
|
Delete(&model.Follow{}).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := tx.Where("follower_id = ? AND following_id = ?", blockedID, blockerID).
|
|
|
|
|
|
Delete(&model.Follow{}).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, uid := range []string{blockerID, blockedID} {
|
|
|
|
|
|
var followersCount int64
|
|
|
|
|
|
if err := tx.Model(&model.Follow{}).Where("following_id = ?", uid).Count(&followersCount).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := tx.Model(&model.User{}).Where("id = ?", uid).
|
|
|
|
|
|
UpdateColumn("followers_count", followersCount).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var followingCount int64
|
|
|
|
|
|
if err := tx.Model(&model.Follow{}).Where("follower_id = ?", uid).Count(&followingCount).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := tx.Model(&model.User{}).Where("id = ?", uid).
|
|
|
|
|
|
UpdateColumn("following_count", followingCount).Error; err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UnblockUser 取消拉黑
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) UnblockUser(blockerID, blockedID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return r.db.Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID).
|
|
|
|
|
|
Delete(&model.UserBlock{}).Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetBlockedUsers 获取用户黑名单列表
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetBlockedUsers(blockerID string, page, pageSize int) ([]*model.User, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var users []*model.User
|
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
|
|
subQuery := r.db.Model(&model.UserBlock{}).Where("blocker_id = ?", blockerID).Select("blocked_id")
|
|
|
|
|
|
r.db.Model(&model.User{}).Where("id IN (?)", subQuery).Count(&total)
|
|
|
|
|
|
|
|
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
|
|
err := r.db.Where("id IN (?)", subQuery).
|
|
|
|
|
|
Order("created_at DESC, id DESC").
|
|
|
|
|
|
Offset(offset).
|
|
|
|
|
|
Limit(pageSize).
|
|
|
|
|
|
Find(&users).Error
|
|
|
|
|
|
|
|
|
|
|
|
return users, total, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Search 搜索用户
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) Search(keyword string, page, pageSize int) ([]*model.User, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
var users []*model.User
|
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
|
|
query := r.db.Model(&model.User{})
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索用户名、昵称、简介
|
|
|
|
|
|
if keyword != "" {
|
|
|
|
|
|
if r.db.Dialector.Name() == "postgres" {
|
|
|
|
|
|
query = query.Where(
|
|
|
|
|
|
"to_tsvector('simple', COALESCE(username, '') || ' ' || COALESCE(nickname, '') || ' ' || COALESCE(bio, '')) @@ plainto_tsquery('simple', ?)",
|
|
|
|
|
|
keyword,
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
2026-03-12 23:40:21 +08:00
|
|
|
|
// SQLite: 使用 LOWER() 实现大小写不敏感的模糊搜索
|
|
|
|
|
|
lowerSearchPattern := "%" + strings.ToLower(keyword) + "%"
|
|
|
|
|
|
query = query.Where(
|
|
|
|
|
|
"LOWER(username) LIKE ? OR LOWER(nickname) LIKE ? OR LOWER(bio) LIKE ?",
|
|
|
|
|
|
lowerSearchPattern, lowerSearchPattern, lowerSearchPattern,
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
query.Count(&total)
|
|
|
|
|
|
|
|
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
|
|
err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&users).Error
|
|
|
|
|
|
|
|
|
|
|
|
return users, total, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetMutualFollowStatus 批量获取双向关注状态
|
|
|
|
|
|
// 返回 map[userID][isFollowing, isFollowingMe]
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetMutualFollowStatus(currentUserID string, targetUserIDs []string) (map[string][2]bool, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
result := make(map[string][2]bool)
|
|
|
|
|
|
|
|
|
|
|
|
if len(targetUserIDs) == 0 {
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化所有目标用户为未关注状态
|
|
|
|
|
|
for _, userID := range targetUserIDs {
|
|
|
|
|
|
result[userID] = [2]bool{false, false}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询当前用户关注了哪些目标用户 (isFollowing)
|
|
|
|
|
|
var followingIDs []string
|
|
|
|
|
|
err := r.db.Model(&model.Follow{}).
|
|
|
|
|
|
Where("follower_id = ? AND following_id IN ?", currentUserID, targetUserIDs).
|
|
|
|
|
|
Pluck("following_id", &followingIDs).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, id := range followingIDs {
|
|
|
|
|
|
status := result[id]
|
|
|
|
|
|
status[0] = true
|
|
|
|
|
|
result[id] = status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询哪些目标用户关注了当前用户 (isFollowingMe)
|
|
|
|
|
|
var followerIDs []string
|
|
|
|
|
|
err = r.db.Model(&model.Follow{}).
|
|
|
|
|
|
Where("follower_id IN ? AND following_id = ?", targetUserIDs, currentUserID).
|
|
|
|
|
|
Pluck("follower_id", &followerIDs).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, id := range followerIDs {
|
|
|
|
|
|
status := result[id]
|
|
|
|
|
|
status[1] = true
|
|
|
|
|
|
result[id] = status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
2026-03-14 18:01:55 +08:00
|
|
|
|
|
|
|
|
|
|
// AdminList 管理端分页获取用户列表(支持关键词和状态筛选)
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error) {
|
2026-03-14 18:01:55 +08:00
|
|
|
|
var users []*model.User
|
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
|
|
query := r.db.Model(&model.User{})
|
|
|
|
|
|
|
|
|
|
|
|
// 关键词搜索(用户名、昵称、邮箱)
|
|
|
|
|
|
if keyword != "" {
|
|
|
|
|
|
if r.db.Dialector.Name() == "postgres" {
|
|
|
|
|
|
query = query.Where(
|
|
|
|
|
|
"username ILIKE ? OR nickname ILIKE ? OR email ILIKE ?",
|
|
|
|
|
|
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%",
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
lowerSearchPattern := "%" + strings.ToLower(keyword) + "%"
|
|
|
|
|
|
query = query.Where(
|
|
|
|
|
|
"LOWER(username) LIKE ? OR LOWER(nickname) LIKE ? OR LOWER(email) LIKE ?",
|
|
|
|
|
|
lowerSearchPattern, lowerSearchPattern, lowerSearchPattern,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 状态筛选
|
|
|
|
|
|
if status != "" {
|
|
|
|
|
|
query = query.Where("status = ?", status)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
query.Count(&total)
|
|
|
|
|
|
|
|
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
|
|
err := query.Order("created_at DESC, id DESC").Offset(offset).Limit(pageSize).Find(&users).Error
|
|
|
|
|
|
|
|
|
|
|
|
return users, total, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetCommentsCount 获取用户评论数
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) GetCommentsCount(userID string) (int64, error) {
|
2026-03-14 18:01:55 +08:00
|
|
|
|
var count int64
|
|
|
|
|
|
err := r.db.Model(&model.Comment{}).
|
|
|
|
|
|
Where("user_id = ?", userID).
|
|
|
|
|
|
Count(&count).Error
|
|
|
|
|
|
return count, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdateStatus 更新用户状态
|
2026-03-26 18:14:16 +08:00
|
|
|
|
func (r *userRepository) UpdateStatus(userID string, status model.UserStatus) error {
|
2026-03-14 18:01:55 +08:00
|
|
|
|
return r.db.Model(&model.User{}).Where("id = ?", userID).Update("status", status).Error
|
|
|
|
|
|
}
|