616 lines
16 KiB
Go
616 lines
16 KiB
Go
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, files []*user.MomentFile) error {
|
||
db := DB
|
||
// 使用事务确保数据一致性
|
||
return db.Transaction(func(tx *gorm.DB) error {
|
||
// 保存动态
|
||
if err := tx.Create(moment).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
// 保存图片(如果有)
|
||
if len(files) > 0 {
|
||
images := make([]model.MomentFile, 0, len(files))
|
||
for _, url := range files {
|
||
images = append(images, model.MomentFile{
|
||
MomentID: moment.ID,
|
||
FileURL: url.FileURL,
|
||
FileType: url.FileType,
|
||
})
|
||
}
|
||
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列表
|
||
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.MomentFile, error) {
|
||
db := DB
|
||
var images []model.MomentFile
|
||
var data []*user.MomentFile
|
||
err := db.Model(&model.MomentFile{}).Where("moment_id = ?", ids).Find(&images).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for _, v := range images {
|
||
data = append(data, &user.MomentFile{
|
||
ID: int64(v.ID),
|
||
MomentID: int64(v.MomentID),
|
||
FileURL: v.FileURL,
|
||
FileType: v.FileType,
|
||
SortOrder: int8(v.SortOrder),
|
||
})
|
||
}
|
||
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 := GetUsersByIdMap(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 _, 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[v.UserID],
|
||
Content: v.Content,
|
||
Location: v.Location,
|
||
Status: user.ContentStatus(int64(v.Status)),
|
||
LikeCount: int32(v.LikeCount),
|
||
CommentCount: int32(v.CommentCount),
|
||
Files: 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),
|
||
Files: 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.MomentFile{}).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
|
||
}
|