Files
EchoChat/backend/go-service/app/contact/dao/friendship_dao.go

397 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package dao 提供 contact 模块的数据库访问操作
package dao
import (
"context"
"time"
authModel "github.com/echochat/backend/app/auth/model"
"github.com/echochat/backend/app/constants"
"github.com/echochat/backend/app/contact/model"
"github.com/echochat/backend/pkg/logs"
"go.uber.org/zap"
"gorm.io/gorm"
)
// FriendshipDAO 好友关系数据访问对象
type FriendshipDAO struct {
db *gorm.DB
}
// NewFriendshipDAO 创建 FriendshipDAO 实例
func NewFriendshipDAO(db *gorm.DB) *FriendshipDAO {
return &FriendshipDAO{db: db}
}
// CreateRequest 创建好友申请(单向 A→Bstatus=0
func (d *FriendshipDAO) CreateRequest(ctx context.Context, userID, friendID int64, message string) (*model.Friendship, error) {
funcName := "dao.friendship_dao.CreateRequest"
logs.Info(ctx, funcName, "创建好友申请",
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
f := &model.Friendship{
UserID: userID,
FriendID: friendID,
Status: constants.FriendshipStatusPending,
Message: message,
}
err := d.db.WithContext(ctx).Create(f).Error
if err != nil {
logs.Error(ctx, funcName, "创建好友申请失败", zap.Error(err))
}
return f, err
}
// AcceptRequest 接受好友申请(事务内:更新 A→B 为 accepted + 创建或更新 B→A 为 accepted
func (d *FriendshipDAO) AcceptRequest(ctx context.Context, requestID, userID int64) error {
funcName := "dao.friendship_dao.AcceptRequest"
logs.Info(ctx, funcName, "接受好友申请",
zap.Int64("request_id", requestID), zap.Int64("user_id", userID))
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var req model.Friendship
if err := tx.First(&req, requestID).Error; err != nil {
return err
}
if req.FriendID != userID {
return gorm.ErrRecordNotFound
}
now := time.Now()
if err := tx.Model(&req).Updates(map[string]interface{}{
"status": constants.FriendshipStatusAccepted,
"updated_at": now,
}).Error; err != nil {
return err
}
var existing model.Friendship
err := tx.Where("user_id = ? AND friend_id = ?", userID, req.UserID).First(&existing).Error
if err == nil {
return tx.Model(&existing).Updates(map[string]interface{}{
"status": constants.FriendshipStatusAccepted,
"updated_at": now,
}).Error
}
reverse := &model.Friendship{
UserID: userID,
FriendID: req.UserID,
Status: constants.FriendshipStatusAccepted,
}
return tx.Create(reverse).Error
})
}
// RejectRequest 拒绝好友申请
func (d *FriendshipDAO) RejectRequest(ctx context.Context, requestID, userID int64) error {
funcName := "dao.friendship_dao.RejectRequest"
logs.Info(ctx, funcName, "拒绝好友申请",
zap.Int64("request_id", requestID), zap.Int64("user_id", userID))
result := d.db.WithContext(ctx).
Model(&model.Friendship{}).
Where("id = ? AND friend_id = ? AND status = ?", requestID, userID, constants.FriendshipStatusPending).
Update("status", constants.FriendshipStatusRejected)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
// GetFriendList 获取好友列表status=1 的记录JOIN 用户表获取详情)
func (d *FriendshipDAO) GetFriendList(ctx context.Context, userID int64, groupID *int64) ([]FriendWithUser, error) {
funcName := "dao.friendship_dao.GetFriendList"
logs.Debug(ctx, funcName, "查询好友列表", zap.Int64("user_id", userID))
var results []FriendWithUser
query := d.db.WithContext(ctx).
Table("contact_friendships f").
Select("f.id, f.friend_id as user_id, u.username, u.nickname, u.avatar, f.remark, f.group_id, f.created_at").
Joins("JOIN auth_users u ON u.id = f.friend_id").
Where("f.user_id = ? AND f.status = ?", userID, constants.FriendshipStatusAccepted)
if groupID != nil {
query = query.Where("f.group_id = ?", *groupID)
}
err := query.Order("u.nickname ASC").Scan(&results).Error
if err != nil {
logs.Error(ctx, funcName, "查询好友列表失败", zap.Error(err))
}
return results, err
}
// FriendWithUser 好友列表查询结果JOIN 用户表)
type FriendWithUser struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Remark string `json:"remark"`
GroupID *int64 `json:"group_id"`
CreatedAt time.Time `json:"created_at"`
}
// GetPendingRequests 获取收到的待处理好友申请
func (d *FriendshipDAO) GetPendingRequests(ctx context.Context, userID int64) ([]RequestWithUser, error) {
funcName := "dao.friendship_dao.GetPendingRequests"
logs.Debug(ctx, funcName, "查询待处理申请", zap.Int64("user_id", userID))
var results []RequestWithUser
err := d.db.WithContext(ctx).
Table("contact_friendships f").
Select("f.id, f.user_id, u.username, u.nickname, u.avatar, f.message, f.status, f.created_at").
Joins("JOIN auth_users u ON u.id = f.user_id").
Where("f.friend_id = ? AND f.status = ?", userID, constants.FriendshipStatusPending).
Order("f.created_at DESC").
Scan(&results).Error
if err != nil {
logs.Error(ctx, funcName, "查询待处理申请失败", zap.Error(err))
}
return results, err
}
// RequestWithUser 好友申请查询结果JOIN 申请方用户表)
type RequestWithUser struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Message string `json:"message"`
Status int `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
// DeleteFriend 删除好友(事务内双向删除)
func (d *FriendshipDAO) DeleteFriend(ctx context.Context, userID, friendID int64) error {
funcName := "dao.friendship_dao.DeleteFriend"
logs.Info(ctx, funcName, "删除好友",
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Where("user_id = ? AND friend_id = ?", userID, friendID).
Delete(&model.Friendship{}).Error; err != nil {
return err
}
return tx.Where("user_id = ? AND friend_id = ?", friendID, userID).
Delete(&model.Friendship{}).Error
})
}
// UpdateRemark 设置好友备注
func (d *FriendshipDAO) UpdateRemark(ctx context.Context, userID, friendID int64, remark string) error {
funcName := "dao.friendship_dao.UpdateRemark"
logs.Info(ctx, funcName, "更新好友备注",
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
return d.db.WithContext(ctx).
Model(&model.Friendship{}).
Where("user_id = ? AND friend_id = ? AND status = ?", userID, friendID, constants.FriendshipStatusAccepted).
Update("remark", remark).Error
}
// BlockUser 拉黑用户(事务内:删除双向好友 + 创建单向拉黑记录)
func (d *FriendshipDAO) BlockUser(ctx context.Context, userID, targetID int64) error {
funcName := "dao.friendship_dao.BlockUser"
logs.Info(ctx, funcName, "拉黑用户",
zap.Int64("user_id", userID), zap.Int64("target_id", targetID))
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Where("(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
userID, targetID, targetID, userID).
Delete(&model.Friendship{}).Error; err != nil {
return err
}
block := &model.Friendship{
UserID: userID,
FriendID: targetID,
Status: constants.FriendshipStatusBlocked,
}
return tx.Create(block).Error
})
}
// UnblockUser 取消拉黑
func (d *FriendshipDAO) UnblockUser(ctx context.Context, userID, targetID int64) error {
funcName := "dao.friendship_dao.UnblockUser"
logs.Info(ctx, funcName, "取消拉黑",
zap.Int64("user_id", userID), zap.Int64("target_id", targetID))
return d.db.WithContext(ctx).
Where("user_id = ? AND friend_id = ? AND status = ?", userID, targetID, constants.FriendshipStatusBlocked).
Delete(&model.Friendship{}).Error
}
// GetBlockList 获取黑名单列表
func (d *FriendshipDAO) GetBlockList(ctx context.Context, userID int64) ([]FriendWithUser, error) {
funcName := "dao.friendship_dao.GetBlockList"
logs.Debug(ctx, funcName, "查询黑名单", zap.Int64("user_id", userID))
var results []FriendWithUser
err := d.db.WithContext(ctx).
Table("contact_friendships f").
Select("f.id, f.friend_id as user_id, u.username, u.nickname, u.avatar, f.remark, f.group_id, f.created_at").
Joins("JOIN auth_users u ON u.id = f.friend_id").
Where("f.user_id = ? AND f.status = ?", userID, constants.FriendshipStatusBlocked).
Scan(&results).Error
return results, err
}
// IsBlocked 检查 userID 是否被 targetID 拉黑
func (d *FriendshipDAO) IsBlocked(ctx context.Context, userID, targetID int64) (bool, error) {
var count int64
err := d.db.WithContext(ctx).
Model(&model.Friendship{}).
Where("user_id = ? AND friend_id = ? AND status = ?", targetID, userID, constants.FriendshipStatusBlocked).
Count(&count).Error
return count > 0, err
}
// IsFriend 检查是否互为好友
func (d *FriendshipDAO) IsFriend(ctx context.Context, userID, friendID int64) (bool, error) {
var count int64
err := d.db.WithContext(ctx).
Model(&model.Friendship{}).
Where("user_id = ? AND friend_id = ? AND status = ?", userID, friendID, constants.FriendshipStatusAccepted).
Count(&count).Error
return count > 0, err
}
// HasPendingRequest 检查是否已有待处理的申请A→B 或 B→A
func (d *FriendshipDAO) HasPendingRequest(ctx context.Context, userID, friendID int64) (bool, error) {
var count int64
err := d.db.WithContext(ctx).
Model(&model.Friendship{}).
Where("((user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)) AND status = ?",
userID, friendID, friendID, userID, constants.FriendshipStatusPending).
Count(&count).Error
return count > 0, err
}
// ReactivateRejectedRequest 将已拒绝的申请重新激活为待处理状态
// 返回是否找到并更新了已拒绝的记录
func (d *FriendshipDAO) ReactivateRejectedRequest(ctx context.Context, userID, friendID int64, message string) (bool, error) {
funcName := "dao.friendship_dao.ReactivateRejectedRequest"
logs.Info(ctx, funcName, "重新激活已拒绝的好友申请",
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
result := d.db.WithContext(ctx).
Model(&model.Friendship{}).
Where("user_id = ? AND friend_id = ? AND status = ?", userID, friendID, constants.FriendshipStatusRejected).
Updates(map[string]interface{}{
"status": constants.FriendshipStatusPending,
"message": message,
})
if result.Error != nil {
logs.Error(ctx, funcName, "重新激活好友申请失败", zap.Error(result.Error))
return false, result.Error
}
return result.RowsAffected > 0, nil
}
// GetRequestByID 根据 ID 获取好友申请
func (d *FriendshipDAO) GetRequestByID(ctx context.Context, id int64) (*model.Friendship, error) {
var f model.Friendship
err := d.db.WithContext(ctx).First(&f, id).Error
return &f, err
}
// GetCommonFriends 获取共同好友 ID 列表
func (d *FriendshipDAO) GetCommonFriends(ctx context.Context, userID, targetID int64) ([]int64, error) {
funcName := "dao.friendship_dao.GetCommonFriends"
logs.Debug(ctx, funcName, "查询共同好友",
zap.Int64("user_id", userID), zap.Int64("target_id", targetID))
var ids []int64
err := d.db.WithContext(ctx).
Raw(`SELECT a.friend_id FROM contact_friendships a
JOIN contact_friendships b ON a.friend_id = b.friend_id
WHERE a.user_id = ? AND a.status = ? AND b.user_id = ? AND b.status = ?`,
userID, constants.FriendshipStatusAccepted, targetID, constants.FriendshipStatusAccepted).
Scan(&ids).Error
return ids, err
}
// SearchUsers 搜索用户(按用户名或昵称模糊匹配,排除自己)
func (d *FriendshipDAO) SearchUsers(ctx context.Context, keyword string, excludeUserID int64, page, pageSize int) ([]authModel.User, int64, error) {
funcName := "dao.friendship_dao.SearchUsers"
logs.Debug(ctx, funcName, "搜索用户", zap.String("keyword", keyword))
var users []authModel.User
var total int64
query := d.db.WithContext(ctx).
Model(&authModel.User{}).
Where("id != ? AND status = 1 AND (username LIKE ? OR nickname LIKE ?)",
excludeUserID, "%"+keyword+"%", "%"+keyword+"%")
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
offset := (page - 1) * pageSize
err := query.Select("id, username, nickname, avatar").
Offset(offset).Limit(pageSize).
Find(&users).Error
return users, total, err
}
// GetFriendIDs 获取用户的所有好友 ID 列表
func (d *FriendshipDAO) GetFriendIDs(ctx context.Context, userID int64) ([]int64, error) {
var ids []int64
err := d.db.WithContext(ctx).
Model(&model.Friendship{}).
Where("user_id = ? AND status = ?", userID, constants.FriendshipStatusAccepted).
Pluck("friend_id", &ids).Error
return ids, err
}
// GetUsersByIDs 批量查询用户信息(仅返回 id/username/nickname/avatar
func (d *FriendshipDAO) GetUsersByIDs(ctx context.Context, userIDs []int64) ([]authModel.User, error) {
funcName := "dao.friendship_dao.GetUsersByIDs"
logs.Debug(ctx, funcName, "批量查询用户信息", zap.Int("count", len(userIDs)))
var users []authModel.User
err := d.db.WithContext(ctx).
Model(&authModel.User{}).
Select("id, username, nickname, avatar").
Where("id IN ?", userIDs).
Find(&users).Error
if err != nil {
logs.Error(ctx, funcName, "批量查询用户信息失败", zap.Error(err))
}
return users, err
}
// CountFriendsByGroup 统计每个分组的好友数
func (d *FriendshipDAO) CountFriendsByGroup(ctx context.Context, userID int64) (map[int64]int, error) {
type GroupCount struct {
GroupID int64 `gorm:"column:group_id"`
Count int `gorm:"column:cnt"`
}
var counts []GroupCount
err := d.db.WithContext(ctx).
Model(&model.Friendship{}).
Select("group_id, COUNT(*) as cnt").
Where("user_id = ? AND status = ? AND group_id IS NOT NULL", userID, constants.FriendshipStatusAccepted).
Group("group_id").
Scan(&counts).Error
result := make(map[int64]int)
for _, c := range counts {
result[c.GroupID] = c.Count
}
return result, err
}