Files
IUQT/acquaintances/biz/dal/mysql/friend_moments.go

614 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

package mysql
import (
"acquaintances/biz/model"
"acquaintances/biz/model/user"
"errors"
"fmt"
"github.com/cloudwego/hertz/pkg/common/hlog"
"gorm.io/gorm"
)
// SaveMomentWithImages 新建动态
func SaveMomentWithImages(moment *model.Moment, imageURLs []string) error {
db := DB
// 使用事务确保数据一致性
return db.Transaction(func(tx *gorm.DB) error {
// 保存动态
if err := tx.Create(moment).Error; err != nil {
return err
}
// 保存图片(如果有)
if len(imageURLs) > 0 {
images := make([]model.MomentImage, 0, len(imageURLs))
for _, url := range imageURLs {
images = append(images, model.MomentImage{
MomentID: moment.ID,
ImageURL: url,
})
}
if err := tx.Create(&images).Error; err != nil {
return err
}
}
return nil
})
}
// CheckUserExists 检查用户是否存在
func CheckUserExists(userID string) (bool, error) {
db := DB
// 实际实现中应查询数据库验证用户是否存在
// 这里仅为示例
var count int64
if err := db.Model(&model.User{}).Where("user_id = ?", userID).Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
// 获取当前用户的好友ID列表
// 获取当前用户的好友ID列表
func getFriendUserIDs(userID string) ([]string, error) {
var friends []model.FriendRelationship
// 一次查询所有与当前用户相关的已通过好友关系
err := DB.Model(model.FriendRelationship{}).
Where("(applicant_id = ? OR target_user_id = ?) AND status = 1", userID, userID).
Find(&friends).Error
if err != nil {
return nil, fmt.Errorf("查询好友列表失败: %w", err)
}
// 使用map去重
friendMap := make(map[string]struct{})
for _, rel := range friends {
// 根据关系方向添加对应的好友ID
if rel.ApplicantID == userID {
friendMap[rel.TargetUserID] = struct{}{}
} else {
friendMap[rel.ApplicantID] = struct{}{}
}
}
// 转换map为切片
friendUserIDs := make([]string, 0, len(friendMap))
for id := range friendMap {
friendUserIDs = append(friendUserIDs, id)
}
return friendUserIDs, nil
}
// 将用户模型转换为DTO
func convertUserModelToDTO(u model.User) user.UserInfoReq {
userInfo := user.UserInfoReq{
UserID: u.UserID,
UserName: u.UserName,
Gender: u.Gender,
Age: int64(u.Age),
Introduce: u.Introduce,
AvatarImageURL: u.AvatarImageURL,
}
// 可选字段处理
if u.Birthday != nil {
userInfo.Birthday = u.Birthday.Format("2006-01-02")
}
return userInfo
}
// GetMomentsImage 获取指定动态的图像列表
func GetMomentsImage(ids uint) ([]*user.MomentImage, error) {
db := DB
var images []model.MomentImage
var data []*user.MomentImage
err := db.Model(&model.MomentImage{}).Where("moment_id = ?", ids).Find(&images).Error
if err != nil {
return nil, err
}
for _, v := range images {
data = append(data, &user.MomentImage{
ID: int64(v.ID),
MomentID: int64(v.MomentID),
ImageURL: v.ImageURL,
})
}
return data, nil
}
// GetFriendsMoments 获取好友动态
func GetFriendsMoments(req user.ListMomentsRequest) ([]*user.Moment, int32, error) {
db := DB
//获取好友id列表
friends, err := getFriendUserIDs(req.UserID)
if err != nil {
return nil, 0, err
}
// 校验分页参数
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 {
req.PageSize = 10
}
// 查询总数
var total int64
query := db.Model(&model.Moment{}).Where("user_id = ?", req.UserID)
if len(friends) > 0 {
query = query.Or("user_id IN (?) AND visibility != ?", friends, model.VisibilitySelfOnly)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
//获取朋友圈内容信息
var moments []model.Moment
query = db.Model(&model.Moment{}).Where("user_id = ?", req.UserID)
if len(friends) > 0 {
query = query.Or("user_id IN (?) AND visibility != ?", friends, model.VisibilitySelfOnly)
}
err = query.Limit(int(req.PageSize)).Offset(int(req.PageSize * (req.Page - 1))).Find(&moments).Error
if err != nil {
return nil, 0, err
}
//批量查询用户信息
var userIDs []string
for _, v := range moments {
userIDs = append(userIDs, v.UserID)
}
userInfo, err := GetUsersById(userIDs...) // 自定义批量查询函数
if err != nil {
hlog.Errorf("批量查询用户失败: %v", err)
return nil, 0, err
}
var data []*user.Moment
query = db.Model(&model.Moment{}).Where("user_id = ?", req.UserID)
if len(friends) > 0 {
query = query.Or("user_id IN (?) AND visibility != ?", friends, model.VisibilitySelfOnly)
}
err = query.Limit(int(req.PageSize)).Offset(int(req.PageSize * (req.Page - 1))).Find(&moments).Error
if err != nil {
return nil, 0, err
}
for k, v := range moments {
images, err := GetMomentsImage(v.ID)
if err != nil {
continue
}
data = append(data, &user.Moment{
ID: int64(v.ID),
UserID: v.UserID,
User: userInfo[k],
Content: v.Content,
Location: v.Location,
Status: user.ContentStatus(int64(v.Status)),
LikeCount: int32(v.LikeCount),
CommentCount: int32(v.CommentCount),
Images: images,
CreatedAt: v.CreatedAt.Format("2006-01-02 15:04"),
})
}
return data, int32(total), nil
}
// GetMomentsAppoint 获取指定好友动态
func GetMomentsAppoint(req user.ListMomentsAppointRequest) ([]*user.Moment, int32, error) {
db := DB
// 校验分页参数
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 {
req.PageSize = 10
}
// 查询总数
var total int64
query := db.Model(&model.Moment{}).Where("user_id = ?", req.UserID)
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
//获取朋友圈内容信息
var moments []model.Moment
query = db.Model(&model.Moment{}).Where("user_id = ?", req.UserID)
err := query.Limit(int(req.PageSize)).Offset(int(req.PageSize * (req.Page - 1))).Find(&moments).Error
if err != nil {
return nil, 0, err
}
var data []*user.Moment
query = db.Model(&model.Moment{}).Where("user_id = ?", req.UserID).Limit(int(req.PageSize)).Offset(int(req.PageSize * (req.Page - 1))).Find(&moments)
if db.Error != nil {
return nil, 0, err
}
for _, v := range moments {
images, err := GetMomentsImage(v.ID)
if err != nil {
continue
}
data = append(data, &user.Moment{
ID: int64(v.ID),
UserID: v.UserID,
Content: v.Content,
Location: v.Location,
Status: user.ContentStatus(int64(v.Status)),
LikeCount: int32(v.LikeCount),
CommentCount: int32(v.CommentCount),
Images: images,
CreatedAt: v.CreatedAt.Format("2006-01-02 15:04"),
})
}
return data, int32(total), nil
}
// DeleteMoment 删除朋友圈动态(级联删除相关图片、评论和点赞)
func DeleteMoment(req user.DeleteMomentRequest) error {
// 参数验证
if req.MomentID <= 0 {
return errors.New("动态ID无效")
}
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
db := DB
// 使用事务确保数据一致性
return db.Transaction(func(tx *gorm.DB) error {
// 1. 先检查动态是否存在且属于当前用户
var moment model.Moment
if err := tx.Where("id = ?", req.MomentID).First(&moment).Error; err != nil {
return fmt.Errorf("查询动态失败: %w", err)
}
// 验证权限(只有发布者可以删除)
if moment.UserID != req.UserID {
return errors.New("没有权限删除该动态")
}
// 2. 删除动态图片
if err := tx.Where("moment_id = ?", req.MomentID).Delete(&model.MomentImage{}).Error; err != nil {
return fmt.Errorf("删除动态图片失败: %w", err)
}
// 3. 先获取所有评论
if err := tx.Where("moment_id = ?", req.MomentID).Delete(&model.MomentComment{}).Error; err != nil {
return fmt.Errorf("删除动态评论失败: %w", err)
}
// 5. 删除该动态的所有点赞记录
if err := tx.Where("moment_id = ?", req.MomentID).Delete(&model.MomentLike{}).Error; err != nil {
return fmt.Errorf("删除点赞记录失败: %w", err)
}
// 6. 最后删除动态本身
if err := tx.Delete(&model.Moment{}, req.MomentID).Error; err != nil {
return fmt.Errorf("删除动态失败: %w", err)
}
return nil
})
}
// LikeMoment 点赞朋友圈动态
func LikeMoment(req user.LikeMomentRequest) error {
db := DB
// 使用事务确保数据一致性
return db.Transaction(func(tx *gorm.DB) error {
// 检查是否已经点赞
var existingLike model.MomentLike
err := tx.Model(&model.MomentLike{}).
Where("user_id = ? AND moment_id = ?", req.UserID, req.MomentID).
First(&existingLike).Error
// 如果不是"未找到"错误,说明查询异常
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("检查点赞记录失败: %w", err)
}
// 如果已经点赞,返回错误
if err == nil {
return errors.New("已经点赞过该动态")
}
// 新增点赞记录
if err := tx.Model(&model.MomentLike{}).Create(&model.MomentLike{
UserID: req.UserID,
MomentID: uint(req.MomentID),
}).Error; err != nil {
return fmt.Errorf("创建点赞记录失败: %w", err)
}
// 点赞数原子增加1避免并发问题
result := tx.Model(model.Moment{}).
Where("id = ?", req.MomentID).
Update("like_count", gorm.Expr("like_count + 1"))
if result.Error != nil {
return fmt.Errorf("更新点赞数失败: %w", result.Error)
}
// 检查动态是否存在
if result.RowsAffected == 0 {
return errors.New("动态不存在")
}
return nil
})
}
// UnlikeMoment 取消点赞朋友圈动态
func UnlikeMoment(req user.UnlikeMomentRequest) error {
db := DB
// 使用事务确保数据一致性
return db.Transaction(func(tx *gorm.DB) error {
// 检查是否存在点赞记录
var like model.MomentLike
err := tx.Model(&model.MomentLike{}).
Where("user_id = ? AND moment_id = ?", req.UserID, req.MomentID).
First(&like).Error
// 如果不是"未找到"错误,说明查询异常
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("检查点赞记录失败: %w", err)
}
// 如果没有点赞记录,返回错误
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("未点赞该动态,无法取消")
}
// 删除点赞记录
if err := tx.Model(&model.MomentLike{}).
Where("user_id = ? AND moment_id = ?", req.UserID, req.MomentID).
Delete(&model.MomentLike{}).Error; err != nil {
return fmt.Errorf("删除点赞记录失败: %w", err)
}
// 点赞数原子减少1
result := tx.Model(model.Moment{}).
Where("id = ? AND like_count > 0", req.MomentID). // 确保点赞数不会小于0
Update("like_count", gorm.Expr("like_count - 1"))
if result.Error != nil {
return fmt.Errorf("更新点赞数失败: %w", result.Error)
}
// 检查动态是否存在
if result.RowsAffected == 0 {
return errors.New("动态不存在或点赞数已为0")
}
return nil
})
}
// GetLikeUserList 获取点赞人员列表
func GetLikeUserList(req user.ListMomentLikesRequest) ([]*user.UserInfoReq, int32, error) {
db := DB
var ids []string
if err := db.Model(&model.MomentLike{}).Where("moment_id = ?", req.MomentID).Pluck("user_id", &ids).Error; err != nil {
return nil, 0, err
}
data, err := GetUsersById(ids...)
if err != nil {
return nil, 0, err
}
return data, int32(len(ids)), nil
}
// CommentMoment 评论
func CommentMoment(req user.CommentMomentRequest) error {
// 参数验证
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
if req.MomentID <= 0 {
return errors.New("动态ID无效")
}
if req.Content == "" {
return errors.New("评论内容不能为空")
}
// 创建评论记录
comment := &model.MomentComment{
UserID: req.UserID,
Content: req.Content,
MomentID: uint(req.MomentID),
ParentID: uint(req.ParentID),
}
db := DB
if err := db.Create(comment).Error; err != nil {
return fmt.Errorf("创建评论失败: %v", err)
}
return nil
}
// ListMomentComments 获取评论列表
func ListMomentComments(req user.ListMomentCommentsRequest) ([]*user.MomentComment, int32, error) {
// 参数验证
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 {
req.PageSize = 10
}
if req.PageSize > 100 { // 限制最大分页大小,防止恶意请求
req.PageSize = 100
}
if req.MomentID <= 0 {
return nil, 0, errors.New("动态ID无效")
}
db := DB
var total int64
var comments []*model.MomentComment
var result []*user.MomentComment
// 先查询总数
if err := db.Model(&model.MomentComment{}).Where("moment_id = ?", req.MomentID).Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("查询评论总数失败: %v", err)
}
// 计算偏移量
offset := int(req.PageSize * (req.Page - 1))
// 查询评论数据
if err := db.Model(&model.MomentComment{}).
Where("moment_id = ?", req.MomentID).
Order("created_at DESC"). // 按创建时间倒序,最新的在前
Limit(int(req.PageSize)).
Offset(offset).
Find(&comments).Error; err != nil {
return nil, 0, fmt.Errorf("查询评论列表失败: %v", err)
}
// 提取用户ID
userIDMap := make(map[string]bool)
for _, comment := range comments {
userIDMap[comment.UserID] = true
}
// 去重用户ID
var userIDs []string
for id := range userIDMap {
userIDs = append(userIDs, id)
}
// 获取用户信息
userInfoMap, err := GetUsersByIdMap(userIDs...)
if err != nil {
return nil, 0, fmt.Errorf("获取用户信息失败: %v", err)
}
// 转换为响应格式
for _, comment := range comments {
result = append(result, &user.MomentComment{
ID: int64(comment.ID),
MomentID: int64(comment.MomentID),
UserID: comment.UserID,
Content: comment.Content,
ParentID: int64(comment.ParentID),
CreatedAt: comment.CreatedAt.Format("2006-01-02 15:04:05"),
User: userInfoMap[comment.UserID],
})
}
return result, int32(total), nil
}
// DeleteComment 删除评论(父评论同步删除,子评论异步删除)
func DeleteComment(req user.DeleteCommentRequest) error {
// 参数验证
if req.CommentID <= 0 {
return errors.New("评论ID无效")
}
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
db := DB
// 1. 查询评论是否存在以及是否属于当前用户
var comment model.MomentComment
if err := db.Where("id = ?", req.CommentID).First(&comment).Error; err != nil {
return fmt.Errorf("查询评论失败: %v", err)
}
// 2. 验证权限
if comment.UserID != req.UserID {
return errors.New("没有权限删除该评论")
}
// 3. 同步删除父评论
if err := db.Delete(&model.MomentComment{}, req.CommentID).Error; err != nil {
return fmt.Errorf("删除父评论失败: %v", err)
}
// 4. 异步删除所有子评论
go func(parentID int64, db *gorm.DB) {
// 使用带缓冲的channel处理可能的panic
panicChan := make(chan interface{}, 1)
defer func() {
if p := recover(); p != nil {
panicChan <- p
// 记录panic日志
hlog.Errorf("异步删除子评论发生panic: %v", p)
}
}()
// 执行异步删除
if err := asyncDeleteChildComments(db, parentID); err != nil {
// 记录错误日志,便于排查
hlog.Errorf("异步删除子评论失败: %v, 父评论ID: %d", err, parentID)
} else {
hlog.Info("异步删除子评论成功, 父评论ID: %d", parentID)
}
}(req.CommentID, db)
return nil
}
// 异步删除所有子评论(递归处理所有层级)
func asyncDeleteChildComments(db *gorm.DB, parentID int64) error {
// 开启新事务处理异步删除
tx := db.Begin()
if tx.Error != nil {
return fmt.Errorf("开启事务失败: %v", tx.Error)
}
defer tx.Rollback()
// 递归查找所有子评论ID
childIDs, err := findAllChildCommentIDs(tx, parentID)
if err != nil {
return fmt.Errorf("查询子评论ID失败: %v", err)
}
// 批量删除子评论
if len(childIDs) > 0 {
if err := tx.Where("id IN (?)", childIDs).Delete(&model.MomentComment{}).Error; err != nil {
return fmt.Errorf("删除子评论失败: %v", err)
}
}
// 提交事务
if err := tx.Commit().Error; err != nil {
return fmt.Errorf("提交事务失败: %v", err)
}
return nil
}
// 递归查找所有子评论ID
func findAllChildCommentIDs(tx *gorm.DB, parentID int64) ([]uint, error) {
var childComments []model.MomentComment
if err := tx.Select("id").Where("parent_id = ?", parentID).Find(&childComments).Error; err != nil {
return nil, err
}
var childIDs []uint
for _, child := range childComments {
childIDs = append(childIDs, child.ID)
// 递归查找子评论的子评论
grandChildIDs, err := findAllChildCommentIDs(tx, int64(child.ID))
if err != nil {
return nil, err
}
childIDs = append(childIDs, grandChildIDs...)
}
return childIDs, nil
}