diff --git a/src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryCommentController.java b/src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryCommentController.java index e8b1a89..63643da 100644 --- a/src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryCommentController.java +++ b/src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryCommentController.java @@ -59,7 +59,7 @@ public class AdoptionDiaryCommentController { } @Operation(summary = "分页查询领养日记一级评论列表接口") - @PostMapping(value = "firstLevel/list") + @GetMapping(value = "firstLevel/list") @Log(module = LogModuleEnum.ADOPTION_DIARY_COMMENT, value = ActionTypeEnum.LIST) public Result> getFirstLevelCommentList( @Valid @RequestBody DiaryFirstLevelCommentQueryParam queryParam @@ -70,7 +70,7 @@ public class AdoptionDiaryCommentController { } @Operation(summary = "分页查询领养日记二级评论列表接口") - @PostMapping(value = "secondLevel/list") + @GetMapping(value = "secondLevel/list") @Log(module = LogModuleEnum.ADOPTION_DIARY_COMMENT, value = ActionTypeEnum.LIST) public Result> getSecondLevelCommentList( @Valid @RequestBody DiarySecondLevelCommentQueryParam queryParam diff --git a/src/main/java/com/youlai/boot/mini/controller/StrayAnimalNoteCommentController.java b/src/main/java/com/youlai/boot/mini/controller/StrayAnimalNoteCommentController.java index e37d6d9..14c697a 100644 --- a/src/main/java/com/youlai/boot/mini/controller/StrayAnimalNoteCommentController.java +++ b/src/main/java/com/youlai/boot/mini/controller/StrayAnimalNoteCommentController.java @@ -57,10 +57,10 @@ public class StrayAnimalNoteCommentController { } @Operation(summary = "分页查询笔记一级评论列表接口") - @PostMapping(value = "firstLevel/list") + @GetMapping(value = "firstLevel/list") @Log(module = LogModuleEnum.STRAY_ANIMAL_NOTE_COMMENT, value = ActionTypeEnum.LIST) public Result> getFirstLevelCommentList( - @Valid @RequestBody AnimalNoteFirstLevelCommentQueryParam queryParam + @Valid AnimalNoteFirstLevelCommentQueryParam queryParam ) { Long userId = SecurityUtils.getUserId(); IPage result = strayAnimalNoteCommentService.getFirstLevelCommentList(queryParam, userId); @@ -68,10 +68,10 @@ public class StrayAnimalNoteCommentController { } @Operation(summary = "分页查询笔记二级评论列表接口") - @PostMapping(value = "secondLevel/list") + @GetMapping(value = "secondLevel/list") @Log(module = LogModuleEnum.STRAY_ANIMAL_NOTE_COMMENT, value = ActionTypeEnum.LIST) public Result> getSecondLevelCommentList( - @Valid @RequestBody AnimalNoteSecondLevelCommentQueryParam queryParam + @Valid AnimalNoteSecondLevelCommentQueryParam queryParam ) { Long userId = SecurityUtils.getUserId(); IPage result = strayAnimalNoteCommentService.getSecondLevelCommentList(queryParam, userId); diff --git a/src/main/java/com/youlai/boot/mini/controller/UserPostCommentController.java b/src/main/java/com/youlai/boot/mini/controller/UserPostCommentController.java new file mode 100644 index 0000000..9ea6822 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/controller/UserPostCommentController.java @@ -0,0 +1,94 @@ +package com.youlai.boot.mini.controller; + +import com.youlai.boot.common.annotation.Log; +import com.youlai.boot.common.annotation.RepeatSubmit; +import com.youlai.boot.common.enums.ActionTypeEnum; +import com.youlai.boot.common.enums.LogModuleEnum; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.youlai.boot.common.result.Result; +import com.youlai.boot.framework.security.util.SecurityUtils; +import com.youlai.boot.mini.model.form.DeleteUserPostCommentForm; +import com.youlai.boot.mini.model.form.PostCommentLikeForm; +import com.youlai.boot.mini.model.form.UserPostCommentForm; +import com.youlai.boot.mini.model.query.PostFirstLevelCommentQueryParam; +import com.youlai.boot.mini.model.query.PostSecondLevelCommentQueryParam; +import com.youlai.boot.mini.model.vo.PostFirstLevelCommentVO; +import com.youlai.boot.mini.model.vo.PostSecondLevelCommentVO; +import com.youlai.boot.mini.model.vo.UserPostCommentVO; +import com.youlai.boot.mini.service.UserPostCommentService; +import java.util.Map; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "用户作品评论的相关接口") +@RestController +@RequestMapping("/api/v1/mini/postComment") +@RequiredArgsConstructor +public class UserPostCommentController { + + private final UserPostCommentService userPostCommentService; + + @Operation(summary = "用户作品评论接口") + @PostMapping(value = "add") + @RepeatSubmit + @Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.INSERT) + public Result addUserPostComment( + @Valid @RequestBody UserPostCommentForm formData + ) { + UserPostCommentVO vo = userPostCommentService.addUserPostComment(formData); + return Result.success(vo); + } + + @Operation(summary = "删除用户作品评论接口") + @PostMapping(value = "delete") + @PreAuthorize("isAuthenticated()") + @RepeatSubmit + @Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.DELETE) + public Result deleteUserPostComment( + @Valid @RequestBody DeleteUserPostCommentForm formData + ) { + Long userId = SecurityUtils.getUserId(); + userPostCommentService.deleteUserPostComment(formData, userId); + return Result.success(); + } + + @Operation(summary = "分页查询用户作品一级评论列表接口") + @GetMapping(value = "firstLevel/list") + @Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.LIST) + public Result> getFirstLevelCommentList( + @Valid PostFirstLevelCommentQueryParam queryParam + ) { + Long userId = SecurityUtils.getUserId(); + IPage result = userPostCommentService.getFirstLevelCommentList(queryParam, userId); + return Result.success(result); + } + + @Operation(summary = "分页查询用户作品二级评论列表接口") + @GetMapping(value = "secondLevel/list") + @Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.LIST) + public Result> getSecondLevelCommentList( + @Valid PostSecondLevelCommentQueryParam queryParam + ) { + Long userId = SecurityUtils.getUserId(); + IPage result = userPostCommentService.getSecondLevelCommentList(queryParam, userId); + return Result.success(result); + } + + @Operation(summary = "评论点赞/取消点赞接口") + @PostMapping(value = "like/toggle") + @PreAuthorize("isAuthenticated()") + @RepeatSubmit(expire = 1) + @Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.UPDATE) + public Result> toggleLike( + @Valid @RequestBody PostCommentLikeForm form + ) { + Long userId = SecurityUtils.getUserId(); + Map result = userPostCommentService.toggleLike(form, userId); + return Result.success(result); + } + +} diff --git a/src/main/java/com/youlai/boot/mini/controller/UserPostController.java b/src/main/java/com/youlai/boot/mini/controller/UserPostController.java index 3ab5c7f..0f56b34 100644 --- a/src/main/java/com/youlai/boot/mini/controller/UserPostController.java +++ b/src/main/java/com/youlai/boot/mini/controller/UserPostController.java @@ -121,7 +121,8 @@ public class UserPostController { @Operation(summary = "获取用户作品详情") @RequestMapping(value = "/getDetails/{postUuid}", method = RequestMethod.GET) public Result getDetails(@PathVariable String postUuid) { - return Result.success(userPostService.getDetails(postUuid)); + Long userId = SecurityUtils.getUserId(); + return Result.success(userPostService.getDetails(postUuid, userId)); } @Operation(summary = "获取某个用户创建的作品列表") diff --git a/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentLikeMapper.java b/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentLikeMapper.java index 1aed72f..f0752d7 100644 --- a/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentLikeMapper.java +++ b/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentLikeMapper.java @@ -2,13 +2,20 @@ package com.youlai.boot.mini.mapper; import com.youlai.boot.mini.model.entity.MiniUserPostCommentLike; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; /** * 用户作品评论点赞表 Mapper 接口 * * @author jwy -* @since +* @since */ public interface MiniUserPostCommentLikeMapper extends BaseMapper { + Integer selectUserLikeCount(@Param("commentId") Long commentId, @Param("userId") Long userId); + + int insertOrUpdateLike(MiniUserPostCommentLike like); + + int deleteLike(@Param("commentId") Long commentId, @Param("userId") Long userId, @Param("currentTime") Long currentTime); + } diff --git a/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentMapper.java b/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentMapper.java index f199e28..6de8e3a 100644 --- a/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentMapper.java +++ b/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentMapper.java @@ -1,14 +1,48 @@ package com.youlai.boot.mini.mapper; -import com.youlai.boot.mini.model.entity.MiniUserPostComment; +import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.youlai.boot.mini.model.entity.MiniUserPostComment; +import com.youlai.boot.mini.model.entity.MiniUserPostCommentLike; +import com.youlai.boot.mini.model.query.PostFirstLevelCommentQueryParam; +import com.youlai.boot.mini.model.query.PostSecondLevelCommentQueryParam; +import com.youlai.boot.mini.model.vo.PostFirstLevelCommentVO; +import com.youlai.boot.mini.model.vo.PostSecondLevelCommentVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** * 用户作品评论表 Mapper 接口 * * @author jwy -* @since +* @since */ public interface MiniUserPostCommentMapper extends BaseMapper { + Long selectIdByUuid(String uuid); + + MiniUserPostComment selectParentCommentInfoByUuid(String uuid); + + String selectUuidById(Long id); + + int incrementCommentCount(Long postId); + + int decrementCommentCount(Long postId); + + int deleteCommentWithPermissionCheck(@Param("postUuid") String postUuid, @Param("commentUuid") String commentUuid, @Param("userId") Long userId); + + IPage getFirstLevelComment(IPage page, @Param("query") PostFirstLevelCommentQueryParam query); + + List batchGetUserCommentLikes(@Param("commentIds") List commentIds, @Param("userId") Long userId); + + IPage getSecondLevelComment(IPage page, @Param("query") PostSecondLevelCommentQueryParam query); + + int incrementLikeCount(@Param("commentId") Long commentId); + + int decrementLikeCount(@Param("commentId") Long commentId); + + Long selectLikeCount(@Param("commentId") Long commentId); + } + diff --git a/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostViewMapper.java b/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostViewMapper.java index 54bbf05..eb2e0b3 100644 --- a/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostViewMapper.java +++ b/src/main/java/com/youlai/boot/mini/mapper/MiniUserPostViewMapper.java @@ -16,4 +16,6 @@ public interface MiniUserPostViewMapper extends BaseMapper { List selectViewedPostIdsByUserAndTime(@Param("userId") Long userId, @Param("startTime") Long startTime); + int insertOrUpdateView(MiniUserPostView view); + } diff --git a/src/main/java/com/youlai/boot/mini/model/form/DeleteUserPostCommentForm.java b/src/main/java/com/youlai/boot/mini/model/form/DeleteUserPostCommentForm.java new file mode 100644 index 0000000..f62443f --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/form/DeleteUserPostCommentForm.java @@ -0,0 +1,18 @@ +package com.youlai.boot.mini.model.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "删除用户作品评论请求参数") +public class DeleteUserPostCommentForm { + + @NotBlank(message = "用户作品UUID不能为空") + @Schema(description = "用户作品UUID", requiredMode = Schema.RequiredMode.REQUIRED) + private String postUuid; + + @NotBlank(message = "评论UUID不能为空") + @Schema(description = "评论UUID", requiredMode = Schema.RequiredMode.REQUIRED) + private String commentUuid; +} diff --git a/src/main/java/com/youlai/boot/mini/model/form/PostCommentLikeForm.java b/src/main/java/com/youlai/boot/mini/model/form/PostCommentLikeForm.java new file mode 100644 index 0000000..f2bdfc9 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/form/PostCommentLikeForm.java @@ -0,0 +1,14 @@ +package com.youlai.boot.mini.model.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "用户作品评论点赞请求参数") +public class PostCommentLikeForm { + + @NotBlank(message = "评论UUID不能为空") + @Schema(description = "评论UUID", requiredMode = Schema.RequiredMode.REQUIRED) + private String commentUuid; +} diff --git a/src/main/java/com/youlai/boot/mini/model/form/UserPostCommentForm.java b/src/main/java/com/youlai/boot/mini/model/form/UserPostCommentForm.java new file mode 100644 index 0000000..8e13a08 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/form/UserPostCommentForm.java @@ -0,0 +1,27 @@ +package com.youlai.boot.mini.model.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.validator.constraints.Length; + +@Schema(description = "用户作品评论表单对象") +@Getter +@Setter +public class UserPostCommentForm { + + @NotBlank(message = "用户作品uuid不能为空") + @Schema(type = "string", description = "用户作品uuid", required = true) + private String postUuid; + + @NotBlank(message = "评论内容不能为空") + @Length(max = 255, message = "评论不能超过255个字符") + @Schema(description = "评论内容") + private String content; + + @NotBlank(message = "父评论uuid不能为空") + @Schema(type = "string", description = "父评论ID,0为一级评论", required = true) + private String parentUuId; + +} diff --git a/src/main/java/com/youlai/boot/mini/model/query/PostFirstLevelCommentQueryParam.java b/src/main/java/com/youlai/boot/mini/model/query/PostFirstLevelCommentQueryParam.java new file mode 100644 index 0000000..01a91fe --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/query/PostFirstLevelCommentQueryParam.java @@ -0,0 +1,15 @@ +package com.youlai.boot.mini.model.query; + +import com.youlai.boot.common.base.BaseQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "用户作品一级评论分页查询参数") +public class PostFirstLevelCommentQueryParam extends BaseQuery { + + @NotBlank(message = "用户作品UUID不能为空") + @Schema(description = "用户作品UUID", requiredMode = Schema.RequiredMode.REQUIRED) + private String postUuid; +} diff --git a/src/main/java/com/youlai/boot/mini/model/query/PostSecondLevelCommentQueryParam.java b/src/main/java/com/youlai/boot/mini/model/query/PostSecondLevelCommentQueryParam.java new file mode 100644 index 0000000..ee0d990 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/query/PostSecondLevelCommentQueryParam.java @@ -0,0 +1,19 @@ +package com.youlai.boot.mini.model.query; + +import com.youlai.boot.common.base.BaseQuery; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "用户作品二级评论分页查询参数") +public class PostSecondLevelCommentQueryParam extends BaseQuery { + + @NotBlank(message = "用户作品UUID不能为空") + @Schema(description = "用户作品UUID", requiredMode = Schema.RequiredMode.REQUIRED) + private String postUuid; + + @NotBlank(message = "根评论UUID不能为空") + @Schema(description = "根评论(一级评论)UUID", requiredMode = Schema.RequiredMode.REQUIRED) + private String rootUuid; +} diff --git a/src/main/java/com/youlai/boot/mini/model/vo/PostFirstLevelCommentVO.java b/src/main/java/com/youlai/boot/mini/model/vo/PostFirstLevelCommentVO.java new file mode 100644 index 0000000..d89c1b0 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/vo/PostFirstLevelCommentVO.java @@ -0,0 +1,44 @@ +package com.youlai.boot.mini.model.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "用户作品一级评论VO") +public class PostFirstLevelCommentVO { + + @JsonIgnore + @Schema(description = "评论自增id", hidden = true) + private Long commentId; + + @Schema(description = "评论UUID") + private String uuid; + + @Schema(description = "评论用户UUID") + private String appUserId; + + @Schema(description = "评论用户头像") + private String avatarUrl; + + @Schema(description = "评论用户昵称") + private String nickname; + + @Schema(description = "评论内容") + private String content; + + @Schema(description = "点赞数") + private Integer likeCount; + + @Schema(description = "二级评论数") + private Integer replyCount; + + @Schema(description = "创建时间戳") + private Long createTimestamp; + + @Schema(description = "评论IP归属地") + private String province; + + @Schema(description = "当前用户是否点赞") + private Boolean isLiked; +} diff --git a/src/main/java/com/youlai/boot/mini/model/vo/PostSecondLevelCommentVO.java b/src/main/java/com/youlai/boot/mini/model/vo/PostSecondLevelCommentVO.java new file mode 100644 index 0000000..b003bdf --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/vo/PostSecondLevelCommentVO.java @@ -0,0 +1,51 @@ +package com.youlai.boot.mini.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "用户作品二级评论VO") +public class PostSecondLevelCommentVO { + + @Schema(description = "评论内部ID") + private Long commentId; + + @Schema(description = "评论UUID") + private String uuid; + + @Schema(description = "评论用户UUID") + private String appUserId; + + @Schema(description = "评论用户头像") + private String avatarUrl; + + @Schema(description = "评论用户昵称") + private String nickname; + + @Schema(description = "评论内容") + private String content; + + @Schema(description = "父评论UUID") + private String parentUuid; + + @Schema(description = "被回复的用户ID") + private String replyToUserId; + + @Schema(description = "被回复用户头像") + private String replyToAvatarUrl; + + @Schema(description = "被回复用户昵称") + private String replyToNickname; + + @Schema(description = "点赞数") + private Integer likeCount; + + @Schema(description = "创建时间戳") + private Long createTimestamp; + + @Schema(description = "评论IP归属地") + private String province; + + @Schema(description = "当前用户是否点赞") + private Boolean isLiked; +} diff --git a/src/main/java/com/youlai/boot/mini/model/vo/UserPostCommentVO.java b/src/main/java/com/youlai/boot/mini/model/vo/UserPostCommentVO.java new file mode 100644 index 0000000..7932ee3 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/vo/UserPostCommentVO.java @@ -0,0 +1,55 @@ +package com.youlai.boot.mini.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "用户作品评论VO") +public class UserPostCommentVO { + + @Schema(description = "用户作品uuid") + private String postUuId; + + @Schema(description = "用户作品评论uuid") + private String uuid; + + @Schema(description = "评论用户uuid") + private String appUserId; + + @Schema(description = "评论用户头像") + private String avatarUrl; + + @Schema(description = "评论用户昵称") + private String nickname = "用户已注销"; + + @Schema(description = "评论内容") + private String content; + + @Schema(description = "父评论uuid,0为一级评论") + private String parentUuId; + + @Schema(description = "根评论ID,一级评论为自身ID,二级及以上为所属一级评论ID") + private String rootId; + + @Schema(description = "被回复的用户ID") + private String replyToUserId; + + @Schema(description = "被回复用户头像") + private String replyToAvatarUrl; + + @Schema(description = "被回复用户昵称") + private String replyToNickname; + + @Schema(description = "点赞数") + private Integer likeCount; + + @Schema(description = "创建时间") + private Long createdAt; + + @Schema(description = "当前用户是否点赞评论,true点赞,false未点赞") + private Boolean isLiked; + + @Schema(description = "所在省") + private String province; + +} diff --git a/src/main/java/com/youlai/boot/mini/service/UserPostCommentService.java b/src/main/java/com/youlai/boot/mini/service/UserPostCommentService.java new file mode 100644 index 0000000..83aeddf --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/service/UserPostCommentService.java @@ -0,0 +1,28 @@ +package com.youlai.boot.mini.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.youlai.boot.mini.model.entity.MiniUserPostComment; +import com.youlai.boot.mini.model.form.PostCommentLikeForm; +import com.youlai.boot.mini.model.form.UserPostCommentForm; +import com.youlai.boot.mini.model.query.PostFirstLevelCommentQueryParam; +import com.youlai.boot.mini.model.query.PostSecondLevelCommentQueryParam; +import com.youlai.boot.mini.model.vo.PostFirstLevelCommentVO; +import com.youlai.boot.mini.model.vo.PostSecondLevelCommentVO; +import com.youlai.boot.mini.model.vo.UserPostCommentVO; + +import java.util.Map; + +public interface UserPostCommentService extends IService { + + UserPostCommentVO addUserPostComment(UserPostCommentForm formData); + + void deleteUserPostComment(com.youlai.boot.mini.model.form.DeleteUserPostCommentForm formData, Long userId); + + IPage getFirstLevelCommentList(PostFirstLevelCommentQueryParam queryParam, Long userId); + + IPage getSecondLevelCommentList(PostSecondLevelCommentQueryParam queryParam, Long userId); + + Map toggleLike(PostCommentLikeForm form, Long userId); + +} diff --git a/src/main/java/com/youlai/boot/mini/service/UserPostService.java b/src/main/java/com/youlai/boot/mini/service/UserPostService.java index 1ffe2ea..d366464 100644 --- a/src/main/java/com/youlai/boot/mini/service/UserPostService.java +++ b/src/main/java/com/youlai/boot/mini/service/UserPostService.java @@ -37,7 +37,7 @@ public interface UserPostService extends IService { IPage getSelfCreatedPage(OwnUserPostQuery queryParams); - UserPostDetailsVO getDetails(String postUuid); + UserPostDetailsVO getDetails(String postUuid, Long userId); IPage getOthersCreatedPage(String authorUuid, OwnUserPostQuery queryParams); diff --git a/src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java new file mode 100644 index 0000000..3a64649 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java @@ -0,0 +1,318 @@ +package com.youlai.boot.mini.service.impl; + +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.common.exception.MsgException; +import com.youlai.boot.common.util.IPUtils; +import com.youlai.boot.common.util.HttpContext; +import com.youlai.boot.framework.security.util.SecurityUtils; +import com.youlai.boot.mini.mapper.MiniUserPostCommentLikeMapper; +import com.youlai.boot.mini.mapper.MiniUserPostCommentMapper; +import com.youlai.boot.mini.mapper.MiniUserPostMapper; +import com.youlai.boot.mini.model.entity.MiniUserPostComment; +import com.youlai.boot.mini.model.entity.MiniUserPostCommentLike; +import com.youlai.boot.mini.model.form.DeleteUserPostCommentForm; +import com.youlai.boot.mini.model.form.PostCommentLikeForm; +import com.youlai.boot.mini.model.form.UserPostCommentForm; +import com.youlai.boot.mini.model.query.PostFirstLevelCommentQueryParam; +import com.youlai.boot.mini.model.query.PostSecondLevelCommentQueryParam; +import com.youlai.boot.mini.model.vo.PostFirstLevelCommentVO; +import com.youlai.boot.mini.model.vo.PostSecondLevelCommentVO; +import com.youlai.boot.mini.model.vo.UserPostCommentVO; +import com.youlai.boot.mini.service.UserPostCommentService; +import com.youlai.boot.system.mapper.UserMapper; +import com.youlai.boot.system.model.entity.SysUser; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserPostCommentServiceImpl extends ServiceImpl implements UserPostCommentService { + + private final MiniUserPostMapper miniUserPostMapper; + private final UserMapper userMapper; + private final MiniUserPostCommentLikeMapper miniUserPostCommentLikeMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public UserPostCommentVO addUserPostComment(UserPostCommentForm formData) { + Long userId = SecurityUtils.getUserId(); + if (userId == null) { + throw new MsgException("请先登录"); + } + return saveCommentInfo(formData, userId); + } + + private UserPostCommentVO saveCommentInfo(UserPostCommentForm formData, Long appUserId) { + // 1. 根据作品UUID查询ID + Long postId = miniUserPostMapper.selectIdByUuid(formData.getPostUuid()); + if (postId == null) { + throw new MsgException("作品不存在或已删除"); + } + + // 2. 获取父评论信息 + Long parentCommentId = 0L; + MiniUserPostComment parentComment = null; + SysUser replyToUser = null; + if (!"0".equals(formData.getParentUuId())) { + parentComment = baseMapper.selectParentCommentInfoByUuid(formData.getParentUuId()); + if (parentComment == null) { + throw new MsgException("父评论已消失"); + } + if (!parentComment.getPostId().equals(postId)) { + throw new MsgException("父评论不属于当前作品"); + } + parentCommentId = parentComment.getId(); + replyToUser = userMapper.selectById(parentComment.getMiniUserId()); + } + + // 3. 获取用户IP位置 + String userIp = IPUtils.getIpAddr(HttpContext.getRequest()); + String ipArr = IPUtils.getRegion(userIp); + String provinceShortName = "未知"; + try { + if (ipArr != null) { + String[] ipInfo = ipArr.split("\\|"); + if (ipInfo.length >= 3 && !"0".equals(ipInfo[2])) { + provinceShortName = ipInfo[2]; + } + } + } catch (Exception e) { + log.error("获取用户ip归属地失败", e); + } + + // 4. 保存评论 + long currentTimestamp = System.currentTimeMillis(); + MiniUserPostComment comment = new MiniUserPostComment(); + comment.setUuid(IdUtil.fastSimpleUUID()); + comment.setPostId(postId); + comment.setMiniUserId(appUserId); + comment.setContent(formData.getContent()); + comment.setParentId(parentCommentId); + comment.setRootId(0L); + if (replyToUser != null) { + comment.setReplyToUserId(replyToUser.getId()); + } + comment.setLikeCount(0); + comment.setProvince(provinceShortName); + comment.setCreateTimestamp(currentTimestamp); + comment.setCreateTime(new Date(currentTimestamp)); + comment.setCreateBy(appUserId); + baseMapper.insert(comment); + + // 5. 一级评论的rootId是自己,二级评论是父评论的rootId + Long rootId = parentCommentId == 0 ? comment.getId() : parentComment.getRootId(); + comment.setRootId(rootId); + baseMapper.updateById(comment); + + // 6. 构建返回VO + UserPostCommentVO vo = new UserPostCommentVO(); + SysUser appUser = userMapper.selectById(appUserId); + vo.setNickname(appUser.getNickname()); + vo.setAvatarUrl(appUser.getAvatar()); + + vo.setPostUuId(formData.getPostUuid()); + vo.setUuid(comment.getUuid()); + vo.setAppUserId(appUser.getUuid()); + vo.setContent(comment.getContent()); + vo.setParentUuId(formData.getParentUuId()); + + String rootUuid = baseMapper.selectUuidById(comment.getRootId()); + if (rootUuid == null) { + throw new MsgException("评论所属楼层已删除"); + } + vo.setRootId(rootUuid); + + if (comment.getReplyToUserId() != null && replyToUser != null) { + if (replyToUser.getIsDeleted() == 1) { + vo.setReplyToNickname("用户已注销"); + } else { + vo.setReplyToUserId(replyToUser.getUuid()); + vo.setReplyToNickname(replyToUser.getNickname()); + vo.setReplyToAvatarUrl(replyToUser.getAvatar()); + } + } + + vo.setCreatedAt(comment.getCreateTimestamp()); + vo.setIsLiked(false); + vo.setLikeCount(0); + vo.setProvince(comment.getProvince()); + + // 7. 更新作品评论数 + baseMapper.incrementCommentCount(postId); + + return vo; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteUserPostComment(DeleteUserPostCommentForm formData, Long userId) { + // 1. 执行原子删除(带权限校验、存在性校验) + int affectRows = baseMapper.deleteCommentWithPermissionCheck( + formData.getPostUuid(), + formData.getCommentUuid(), + userId + ); + + // 2. 删除成功,扣减评论数 + if (affectRows == 1) { + Long postId = miniUserPostMapper.selectIdByUuid(formData.getPostUuid()); + baseMapper.decrementCommentCount(postId); + return; + } + + // 3. 删除失败,查询具体错误原因 + Long postId = miniUserPostMapper.selectIdByUuid(formData.getPostUuid()); + if (postId == null) { + throw new MsgException("作品不存在或已删除"); + } + + MiniUserPostComment comment = baseMapper.selectParentCommentInfoByUuid(formData.getCommentUuid()); + if (comment == null || !comment.getPostId().equals(postId)) { + throw new MsgException("评论不存在或已删除"); + } + + throw new MsgException("无权限删除该评论"); + } + + @Override + @Transactional(readOnly = true, rollbackFor = Exception.class) + public IPage getFirstLevelCommentList(PostFirstLevelCommentQueryParam queryParam, Long userId) { + // 1. 构造分页参数 + Page page = new Page<>(queryParam.getPageNum(), queryParam.getPageSize()); + + // 2. 查询一级评论分页 + IPage result = baseMapper.getFirstLevelComment(page, queryParam); + List commentList = result.getRecords(); + if (commentList.isEmpty() || userId == null) { + return result; + } + + // 3. 提取一级评论内部主键ID + List firstLevelCommentIds = commentList.stream() + .map(PostFirstLevelCommentVO::getCommentId) + .collect(Collectors.toList()); + + // 4. 批量查询登录用户的点赞状态 + Map likeStatusMap = Collections.emptyMap(); + try { + List likeList = baseMapper.batchGetUserCommentLikes(firstLevelCommentIds, userId); + likeStatusMap = likeList.stream() + .collect(Collectors.toMap( + MiniUserPostCommentLike::getPostCommentId, + like -> Boolean.TRUE, + (v1, v2) -> v1 + )); + } catch (Exception e) { + log.error("批量查询评论点赞状态失败", e); + } + + // 5. 设置点赞状态 + for (PostFirstLevelCommentVO comment : commentList) { + comment.setIsLiked(likeStatusMap.getOrDefault(comment.getCommentId(), Boolean.FALSE)); + } + + return result; + } + + @Override + @Transactional(readOnly = true, rollbackFor = Exception.class) + public IPage getSecondLevelCommentList(PostSecondLevelCommentQueryParam queryParam, Long userId) { + // 1. 构造分页参数 + Page page = new Page<>(queryParam.getPageNum(), queryParam.getPageSize()); + + // 2. 查询二级评论分页 + IPage result = baseMapper.getSecondLevelComment(page, queryParam); + List commentList = result.getRecords(); + if (commentList.isEmpty() || userId == null) { + return result; + } + + // 3. 提取二级评论内部主键ID + List secondLevelCommentIds = commentList.stream() + .map(PostSecondLevelCommentVO::getCommentId) + .collect(Collectors.toList()); + + // 4. 批量查询登录用户的点赞状态 + Map likeStatusMap = Collections.emptyMap(); + try { + List likeList = baseMapper.batchGetUserCommentLikes(secondLevelCommentIds, userId); + likeStatusMap = likeList.stream() + .collect(Collectors.toMap( + MiniUserPostCommentLike::getPostCommentId, + like -> Boolean.TRUE, + (v1, v2) -> v1 + )); + } catch (Exception e) { + log.error("批量查询二级评论点赞状态失败", e); + } + + // 5. 设置点赞状态和已注销用户昵称 + for (PostSecondLevelCommentVO comment : commentList) { + comment.setIsLiked(likeStatusMap.getOrDefault(comment.getCommentId(), Boolean.FALSE)); + if (comment.getReplyToUserId() != null && comment.getReplyToNickname() == null) { + comment.setReplyToNickname("用户已注销"); + } + } + + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Map toggleLike(PostCommentLikeForm form, Long userId) { + // 1. 校验评论是否存在 + Long commentId = baseMapper.selectIdByUuid(form.getCommentUuid()); + if (commentId == null) { + throw new MsgException("评论不存在或已删除"); + } + + // 2. 查询用户是否已经点赞 + Integer likeCount = miniUserPostCommentLikeMapper.selectUserLikeCount(commentId, userId); + boolean isLiked = likeCount != null && likeCount > 0; + boolean targetLike = !isLiked; + long currentTime = System.currentTimeMillis(); + + if (targetLike) { + // 3. 点赞:新增或更新点赞记录 + MiniUserPostCommentLike like = new MiniUserPostCommentLike(); + like.setUuid(IdWorker.get32UUID()); + like.setPostCommentId(commentId); + like.setMiniUserId(userId); + like.setCreateTimestamp(currentTime); + like.setCreateTime(new Date(currentTime)); + like.setCreateBy(userId); + miniUserPostCommentLikeMapper.insertOrUpdateLike(like); + + // 4. 原子增加点赞数 + baseMapper.incrementLikeCount(commentId); + } else { + // 5. 取消点赞:逻辑删除点赞记录 + miniUserPostCommentLikeMapper.deleteLike(commentId, userId, currentTime); + + // 6. 原子减少点赞数 + baseMapper.decrementLikeCount(commentId); + } + + // 7. 查询最新点赞数 + Long latestLikeCount = baseMapper.selectLikeCount(commentId); + Map result = new HashMap<>(); + result.put("isLiked", targetLike); + result.put("likeCount", latestLikeCount != null ? latestLikeCount : 0); + return result; + } + +} diff --git a/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java index 818b5fc..48d70aa 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java @@ -27,6 +27,7 @@ import com.youlai.boot.mini.model.entity.MiniUserPost; import com.youlai.boot.mini.model.entity.MiniUserPostCollect; import com.youlai.boot.mini.model.entity.MiniUserPostLike; import com.youlai.boot.mini.model.entity.MiniUserPostMedia; +import com.youlai.boot.mini.model.entity.MiniUserPostView; import com.youlai.boot.mini.model.enums.AnimalNoteMediaTypeEnum; import com.youlai.boot.mini.model.form.UserPostCollectForm; import com.youlai.boot.mini.model.form.UserPostForm; @@ -513,7 +514,7 @@ public class UserPostServiceImpl extends ServiceImpl bloomFilter = redissonClient.getBloomFilter(bloomKey); + if (!bloomFilter.isExists()) { + bloomFilter.tryInit(bloomExpectedInsertions, bloomFpp); + if (bloomExpireDays != null && bloomExpireDays > 0) { + bloomFilter.expire(bloomExpireDays, TimeUnit.DAYS); + } + } + bloomFilter.add(postId); + } catch (Exception e) { + log.error("记录浏览历史到布隆失败,userId:{}, postId:{}", userId, postId, e); + } + } + return detailsVO; } diff --git a/src/main/resources/mapper/mini/MiniUserPostCommentLikeMapper.xml b/src/main/resources/mapper/mini/MiniUserPostCommentLikeMapper.xml index b356d3f..bb7c347 100644 --- a/src/main/resources/mapper/mini/MiniUserPostCommentLikeMapper.xml +++ b/src/main/resources/mapper/mini/MiniUserPostCommentLikeMapper.xml @@ -5,5 +5,35 @@ + + + + INSERT INTO mini_user_post_comment_like + (uuid, post_comment_id, mini_user_id, create_timestamp, create_time, create_by, is_deleted) + VALUES + (#{uuid}, #{postCommentId}, #{miniUserId}, #{createTimestamp}, #{createTime}, #{createBy}, 0) + ON DUPLICATE KEY UPDATE + is_deleted = 0, + update_timestamp = #{createTimestamp}, + update_time = #{createTime}, + update_by = #{createBy} + + + + UPDATE mini_user_post_comment_like + SET is_deleted = 1, + update_time = NOW(), + update_timestamp = #{currentTime}, + update_by = #{userId} + WHERE post_comment_id = #{commentId} + AND mini_user_id = #{userId} + AND is_deleted = 0 + diff --git a/src/main/resources/mapper/mini/MiniUserPostCommentMapper.xml b/src/main/resources/mapper/mini/MiniUserPostCommentMapper.xml index dcec0a4..10036f1 100644 --- a/src/main/resources/mapper/mini/MiniUserPostCommentMapper.xml +++ b/src/main/resources/mapper/mini/MiniUserPostCommentMapper.xml @@ -5,5 +5,123 @@ + + + + + + + + UPDATE mini_user_post SET comment_count = comment_count + 1 WHERE id = #{postId} + + + + UPDATE mini_user_post + SET comment_count = comment_count - 1 + WHERE id = #{postId} + AND comment_count > 0 + + + + UPDATE mini_user_post_comment c + INNER JOIN mini_user_post p ON c.post_id = p.id + SET c.is_deleted = 1, + c.update_by = #{userId}, + c.update_time = NOW(), + c.update_timestamp = UNIX_TIMESTAMP(NOW(3)) * 1000 + WHERE p.uuid = #{postUuid} + AND c.uuid = #{commentUuid} + AND c.create_by = #{userId} + AND c.is_deleted = 0 + + + + + + + + + + UPDATE mini_user_post_comment + SET like_count = like_count + 1 + WHERE id = #{commentId} + AND is_deleted = 0 + + + + UPDATE mini_user_post_comment + SET like_count = like_count - 1 + WHERE id = #{commentId} + AND is_deleted = 0 + AND like_count > 0 + + + diff --git a/src/main/resources/mapper/mini/MiniUserPostViewMapper.xml b/src/main/resources/mapper/mini/MiniUserPostViewMapper.xml index 2eab95b..a4136fc 100644 --- a/src/main/resources/mapper/mini/MiniUserPostViewMapper.xml +++ b/src/main/resources/mapper/mini/MiniUserPostViewMapper.xml @@ -5,6 +5,17 @@ + + INSERT INTO mini_user_post_view + (uuid, mini_user_id, post_id, create_timestamp, create_time, create_by, is_deleted) + VALUES + (#{uuid}, #{miniUserId}, #{postId}, #{createTimestamp}, #{createTime}, #{createBy}, 0) + ON DUPLICATE KEY UPDATE + create_timestamp = VALUES(create_timestamp), + create_time = VALUES(create_time), + is_deleted = 0 + +