From 3c409023e5d4d354fa7551ef32f5a905e3be5275 Mon Sep 17 00:00:00 2001 From: xiongxiaoyang <1179705413@qq.com> Date: Sat, 12 Jul 2025 13:33:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(novel-front):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E7=82=B9=E8=B5=9E/=E7=82=B9=E8=B8=A9?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../novel/entity/BookCommentReply.java | 97 ++++++++ .../BookCommentReplyDynamicSqlSupport.java | 54 +++++ .../novel/mapper/BookCommentReplyMapper.java | 208 ++++++++++++++++++ .../novel/controller/BookController.java | 51 +++++ .../java2nb/novel/service/LikeService.java | 69 ++++++ .../novel/service/impl/BookServiceImpl.java | 18 +- .../novel/service/impl/LikeServiceImpl.java | 94 ++++++++ .../java2nb/novel/vo/BookCommentReplyVO.java | 4 + .../com/java2nb/novel/vo/BookCommentVO.java | 4 + .../templates/book/book_comment.html | 46 +++- .../templates/book/book_comment_reply.html | 47 +++- .../resources/templates/book/book_detail.html | 53 ++++- 12 files changed, 733 insertions(+), 12 deletions(-) create mode 100644 novel-common/src/main/java/com/java2nb/novel/entity/BookCommentReply.java create mode 100644 novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyDynamicSqlSupport.java create mode 100644 novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyMapper.java create mode 100644 novel-front/src/main/java/com/java2nb/novel/service/LikeService.java create mode 100644 novel-front/src/main/java/com/java2nb/novel/service/impl/LikeServiceImpl.java diff --git a/novel-common/src/main/java/com/java2nb/novel/entity/BookCommentReply.java b/novel-common/src/main/java/com/java2nb/novel/entity/BookCommentReply.java new file mode 100644 index 0000000..77ea87b --- /dev/null +++ b/novel-common/src/main/java/com/java2nb/novel/entity/BookCommentReply.java @@ -0,0 +1,97 @@ +package com.java2nb.novel.entity; + +import java.util.Date; +import javax.annotation.Generated; + +public class BookCommentReply { + @Generated("org.mybatis.generator.api.MyBatisGenerator") + private Long id; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + private Long commentId; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + private String replyContent; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + private String location; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + private Byte auditStatus; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + private Date createTime; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + private Long createUserId; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public Long getId() { + return id; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public void setId(Long id) { + this.id = id; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public Long getCommentId() { + return commentId; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public void setCommentId(Long commentId) { + this.commentId = commentId; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public String getReplyContent() { + return replyContent; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public void setReplyContent(String replyContent) { + this.replyContent = replyContent == null ? null : replyContent.trim(); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public String getLocation() { + return location; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public void setLocation(String location) { + this.location = location == null ? null : location.trim(); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public Byte getAuditStatus() { + return auditStatus; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public void setAuditStatus(Byte auditStatus) { + this.auditStatus = auditStatus; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public Date getCreateTime() { + return createTime; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public Long getCreateUserId() { + return createUserId; + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public void setCreateUserId(Long createUserId) { + this.createUserId = createUserId; + } +} \ No newline at end of file diff --git a/novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyDynamicSqlSupport.java b/novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyDynamicSqlSupport.java new file mode 100644 index 0000000..7373ddd --- /dev/null +++ b/novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyDynamicSqlSupport.java @@ -0,0 +1,54 @@ +package com.java2nb.novel.mapper; + +import java.sql.JDBCType; +import java.util.Date; +import javax.annotation.Generated; +import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.SqlTable; + +public final class BookCommentReplyDynamicSqlSupport { + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final BookCommentReply bookCommentReply = new BookCommentReply(); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final SqlColumn id = bookCommentReply.id; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final SqlColumn commentId = bookCommentReply.commentId; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final SqlColumn replyContent = bookCommentReply.replyContent; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final SqlColumn location = bookCommentReply.location; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final SqlColumn auditStatus = bookCommentReply.auditStatus; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final SqlColumn createTime = bookCommentReply.createTime; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final SqlColumn createUserId = bookCommentReply.createUserId; + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + public static final class BookCommentReply extends SqlTable { + public final SqlColumn id = column("id", JDBCType.BIGINT); + + public final SqlColumn commentId = column("comment_id", JDBCType.BIGINT); + + public final SqlColumn replyContent = column("reply_content", JDBCType.VARCHAR); + + public final SqlColumn location = column("location", JDBCType.VARCHAR); + + public final SqlColumn auditStatus = column("audit_status", JDBCType.TINYINT); + + public final SqlColumn createTime = column("create_time", JDBCType.TIMESTAMP); + + public final SqlColumn createUserId = column("create_user_id", JDBCType.BIGINT); + + public BookCommentReply() { + super("book_comment_reply"); + } + } +} \ No newline at end of file diff --git a/novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyMapper.java b/novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyMapper.java new file mode 100644 index 0000000..d2ad5b0 --- /dev/null +++ b/novel-common/src/main/java/com/java2nb/novel/mapper/BookCommentReplyMapper.java @@ -0,0 +1,208 @@ +package com.java2nb.novel.mapper; + +import static com.java2nb.novel.mapper.BookCommentReplyDynamicSqlSupport.*; +import static org.mybatis.dynamic.sql.SqlBuilder.*; + +import com.java2nb.novel.entity.BookCommentReply; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import javax.annotation.Generated; +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.ResultMap; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.UpdateProvider; +import org.apache.ibatis.type.JdbcType; +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter; +import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; +import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; +import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider; +import org.mybatis.dynamic.sql.select.CountDSLCompleter; +import org.mybatis.dynamic.sql.select.SelectDSLCompleter; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.update.UpdateDSL; +import org.mybatis.dynamic.sql.update.UpdateDSLCompleter; +import org.mybatis.dynamic.sql.update.UpdateModel; +import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider; +import org.mybatis.dynamic.sql.util.SqlProviderAdapter; +import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; + +@Mapper +public interface BookCommentReplyMapper { + @Generated("org.mybatis.generator.api.MyBatisGenerator") + BasicColumn[] selectList = BasicColumn.columnList(id, commentId, replyContent, location, auditStatus, createTime, createUserId); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + @SelectProvider(type=SqlProviderAdapter.class, method="select") + long count(SelectStatementProvider selectStatement); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + @DeleteProvider(type=SqlProviderAdapter.class, method="delete") + int delete(DeleteStatementProvider deleteStatement); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + @InsertProvider(type=SqlProviderAdapter.class, method="insert") + int insert(InsertStatementProvider insertStatement); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + @InsertProvider(type=SqlProviderAdapter.class, method="insertMultiple") + int insertMultiple(MultiRowInsertStatementProvider multipleInsertStatement); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + @SelectProvider(type=SqlProviderAdapter.class, method="select") + @ResultMap("BookCommentReplyResult") + Optional selectOne(SelectStatementProvider selectStatement); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + @SelectProvider(type=SqlProviderAdapter.class, method="select") + @Results(id="BookCommentReplyResult", value = { + @Result(column="id", property="id", jdbcType=JdbcType.BIGINT, id=true), + @Result(column="comment_id", property="commentId", jdbcType=JdbcType.BIGINT), + @Result(column="reply_content", property="replyContent", jdbcType=JdbcType.VARCHAR), + @Result(column="location", property="location", jdbcType=JdbcType.VARCHAR), + @Result(column="audit_status", property="auditStatus", jdbcType=JdbcType.TINYINT), + @Result(column="create_time", property="createTime", jdbcType=JdbcType.TIMESTAMP), + @Result(column="create_user_id", property="createUserId", jdbcType=JdbcType.BIGINT) + }) + List selectMany(SelectStatementProvider selectStatement); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + @UpdateProvider(type=SqlProviderAdapter.class, method="update") + int update(UpdateStatementProvider updateStatement); + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default long count(CountDSLCompleter completer) { + return MyBatis3Utils.countFrom(this::count, bookCommentReply, completer); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int delete(DeleteDSLCompleter completer) { + return MyBatis3Utils.deleteFrom(this::delete, bookCommentReply, completer); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int deleteByPrimaryKey(Long id_) { + return delete(c -> + c.where(id, isEqualTo(id_)) + ); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int insert(BookCommentReply record) { + return MyBatis3Utils.insert(this::insert, record, bookCommentReply, c -> + c.map(id).toProperty("id") + .map(commentId).toProperty("commentId") + .map(replyContent).toProperty("replyContent") + .map(location).toProperty("location") + .map(auditStatus).toProperty("auditStatus") + .map(createTime).toProperty("createTime") + .map(createUserId).toProperty("createUserId") + ); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int insertMultiple(Collection records) { + return MyBatis3Utils.insertMultiple(this::insertMultiple, records, bookCommentReply, c -> + c.map(id).toProperty("id") + .map(commentId).toProperty("commentId") + .map(replyContent).toProperty("replyContent") + .map(location).toProperty("location") + .map(auditStatus).toProperty("auditStatus") + .map(createTime).toProperty("createTime") + .map(createUserId).toProperty("createUserId") + ); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int insertSelective(BookCommentReply record) { + return MyBatis3Utils.insert(this::insert, record, bookCommentReply, c -> + c.map(id).toPropertyWhenPresent("id", record::getId) + .map(commentId).toPropertyWhenPresent("commentId", record::getCommentId) + .map(replyContent).toPropertyWhenPresent("replyContent", record::getReplyContent) + .map(location).toPropertyWhenPresent("location", record::getLocation) + .map(auditStatus).toPropertyWhenPresent("auditStatus", record::getAuditStatus) + .map(createTime).toPropertyWhenPresent("createTime", record::getCreateTime) + .map(createUserId).toPropertyWhenPresent("createUserId", record::getCreateUserId) + ); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default Optional selectOne(SelectDSLCompleter completer) { + return MyBatis3Utils.selectOne(this::selectOne, selectList, bookCommentReply, completer); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default List select(SelectDSLCompleter completer) { + return MyBatis3Utils.selectList(this::selectMany, selectList, bookCommentReply, completer); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default List selectDistinct(SelectDSLCompleter completer) { + return MyBatis3Utils.selectDistinct(this::selectMany, selectList, bookCommentReply, completer); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default Optional selectByPrimaryKey(Long id_) { + return selectOne(c -> + c.where(id, isEqualTo(id_)) + ); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int update(UpdateDSLCompleter completer) { + return MyBatis3Utils.update(this::update, bookCommentReply, completer); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + static UpdateDSL updateAllColumns(BookCommentReply record, UpdateDSL dsl) { + return dsl.set(id).equalTo(record::getId) + .set(commentId).equalTo(record::getCommentId) + .set(replyContent).equalTo(record::getReplyContent) + .set(location).equalTo(record::getLocation) + .set(auditStatus).equalTo(record::getAuditStatus) + .set(createTime).equalTo(record::getCreateTime) + .set(createUserId).equalTo(record::getCreateUserId); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + static UpdateDSL updateSelectiveColumns(BookCommentReply record, UpdateDSL dsl) { + return dsl.set(id).equalToWhenPresent(record::getId) + .set(commentId).equalToWhenPresent(record::getCommentId) + .set(replyContent).equalToWhenPresent(record::getReplyContent) + .set(location).equalToWhenPresent(record::getLocation) + .set(auditStatus).equalToWhenPresent(record::getAuditStatus) + .set(createTime).equalToWhenPresent(record::getCreateTime) + .set(createUserId).equalToWhenPresent(record::getCreateUserId); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int updateByPrimaryKey(BookCommentReply record) { + return update(c -> + c.set(commentId).equalTo(record::getCommentId) + .set(replyContent).equalTo(record::getReplyContent) + .set(location).equalTo(record::getLocation) + .set(auditStatus).equalTo(record::getAuditStatus) + .set(createTime).equalTo(record::getCreateTime) + .set(createUserId).equalTo(record::getCreateUserId) + .where(id, isEqualTo(record::getId)) + ); + } + + @Generated("org.mybatis.generator.api.MyBatisGenerator") + default int updateByPrimaryKeySelective(BookCommentReply record) { + return update(c -> + c.set(commentId).equalToWhenPresent(record::getCommentId) + .set(replyContent).equalToWhenPresent(record::getReplyContent) + .set(location).equalToWhenPresent(record::getLocation) + .set(auditStatus).equalToWhenPresent(record::getAuditStatus) + .set(createTime).equalToWhenPresent(record::getCreateTime) + .set(createUserId).equalToWhenPresent(record::getCreateUserId) + .where(id, isEqualTo(record::getId)) + ); + } +} \ No newline at end of file diff --git a/novel-front/src/main/java/com/java2nb/novel/controller/BookController.java b/novel-front/src/main/java/com/java2nb/novel/controller/BookController.java index 3eea52e..1be8dc3 100644 --- a/novel-front/src/main/java/com/java2nb/novel/controller/BookController.java +++ b/novel-front/src/main/java/com/java2nb/novel/controller/BookController.java @@ -7,6 +7,7 @@ import com.java2nb.novel.entity.*; import com.java2nb.novel.service.BookContentService; import com.java2nb.novel.service.BookService; import com.java2nb.novel.service.IpLocationService; +import com.java2nb.novel.service.LikeService; import com.java2nb.novel.vo.*; import io.github.xxyopen.model.page.PageBean; import io.github.xxyopen.model.page.builder.pagehelper.PageBuilder; @@ -35,6 +36,8 @@ public class BookController extends BaseController { private final IpLocationService ipLocationService; + private final LikeService likeService; + /** * 查询首页小说设置列表数据 */ @@ -171,6 +174,30 @@ public class BookController extends BaseController { return RestResult.ok(); } + /** + * 评价点赞/取消点赞 + */ + @PostMapping("toggleCommentLike") + public RestResult toggleCommentLike(Long commentId, HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + return RestResult.ok(likeService.toggleCommentLike(commentId, userDetails.getId())); + } + + /** + * 评价点踩/取消点踩 + */ + @PostMapping("toggleCommentUnLike") + public RestResult toggleCommentUnLike(Long commentId, HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + return RestResult.ok(likeService.toggleCommentUnLike(commentId, userDetails.getId())); + } + /** * 新增回复 */ @@ -185,6 +212,30 @@ public class BookController extends BaseController { return RestResult.ok(); } + /** + * 回复点赞/取消点赞 + */ + @PostMapping("toggleReplyLike") + public RestResult toggleReplyLike(Long replyId, HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + return RestResult.ok(likeService.toggleReplyLike(replyId, userDetails.getId())); + } + + /** + * 回复点赞/取消点赞 + */ + @PostMapping("toggleReplyUnLike") + public RestResult toggleReplyUnLike(Long replyId, HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + return RestResult.ok(likeService.toggleReplyUnLike(replyId, userDetails.getId())); + } + /** * 根据小说ID查询小说前十条最新更新目录集合 */ diff --git a/novel-front/src/main/java/com/java2nb/novel/service/LikeService.java b/novel-front/src/main/java/com/java2nb/novel/service/LikeService.java new file mode 100644 index 0000000..823852f --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/service/LikeService.java @@ -0,0 +1,69 @@ +package com.java2nb.novel.service; + + +/** + * @author 11797 + */ +public interface LikeService { + + /** + * 评论点赞或取消点赞 + * @param commentId 被点赞的评论ID + * @param userId 用户ID + * @return 返回点赞数量 + */ + public Long toggleCommentLike(Long commentId, Long userId); + + /** + * 评论点踩或取消点踩 + * @param commentId 被点踩的评论ID + * @param userId 用户ID + * @return 返回点踩数量 + */ + public Long toggleCommentUnLike(Long commentId, Long userId); + + /** + * 获取评论的点赞数量 + * @param commentId 评论ID + * @return 点赞数 + */ + public Long getCommentLikesCount(Long commentId); + + /** + * 获取评论的点踩赞数量 + * @param commentId 评论ID + * @return 点踩数 + */ + public Long getCommentUnLikesCount(Long commentId); + + /** + * 回复点赞或取消点赞 + * @param replyId 被点赞的回复ID + * @param userId 用户ID + * @return 返回点赞数量 + */ + public Long toggleReplyLike(Long replyId, Long userId); + + /** + * 回复点踩或取消点踩 + * @param replyId 被点踩的回复ID + * @param userId 用户ID + * @return 返回点踩数量 + */ + public Long toggleReplyUnLike(Long replyId, Long userId); + + /** + * 获取回复的点赞数量 + * @param replyId 回复ID + * @return 点赞数 + */ + public Long getReplyLikesCount(Long replyId); + + /** + * 获取回复的点踩数量 + * @param replyId 回复ID + * @return 点踩数 + */ + public Long getReplyUnLikesCount(Long replyId); + +} diff --git a/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java b/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java index 1dc3c80..a322497 100644 --- a/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java +++ b/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java @@ -15,6 +15,7 @@ import com.java2nb.novel.mapper.*; import com.java2nb.novel.service.AuthorService; import com.java2nb.novel.service.BookService; import com.java2nb.novel.service.FileService; +import com.java2nb.novel.service.LikeService; import com.java2nb.novel.vo.*; import io.github.xxyopen.model.page.PageBean; import io.github.xxyopen.model.page.builder.pagehelper.PageBuilder; @@ -94,6 +95,8 @@ public class BookServiceImpl implements BookService { private final FileService fileService; + private final LikeService likeService; + private final BookPriceProperties bookPriceConfig; private final OpenAiImageModel openAiImageModel; @@ -390,7 +393,12 @@ public class BookServiceImpl implements BookService { @Override public PageBean listCommentByPage(Long userId, Long bookId, int page, int pageSize) { PageHelper.startPage(page, pageSize); - return PageBuilder.build(bookCommentMapper.listCommentByPage(userId, bookId)); + PageBean pageBean = PageBuilder.build(bookCommentMapper.listCommentByPage(userId, bookId)); + for (BookCommentVO bookCommentVO : pageBean.getList()) { + bookCommentVO.setLikesCount(likeService.getCommentLikesCount(bookCommentVO.getId())); + bookCommentVO.setUnLikesCount(likeService.getCommentUnLikesCount(bookCommentVO.getId())); + } + return pageBean; } @Transactional(rollbackFor = Exception.class) @@ -901,7 +909,13 @@ public class BookServiceImpl implements BookService { @Override public PageBean listCommentReplyByPage(Long userId, Long commentId, int page, int pageSize) { PageHelper.startPage(page, pageSize); - return PageBuilder.build(bookCommentReplyMapper.listCommentReplyByPage(userId, commentId)); + PageBean pageBean = PageBuilder.build( + bookCommentReplyMapper.listCommentReplyByPage(userId, commentId)); + pageBean.getList().forEach(commentReply -> { + commentReply.setLikesCount(likeService.getReplyLikesCount(commentReply.getId())); + commentReply.setUnLikesCount(likeService.getReplyUnLikesCount(commentReply.getId())); + }); + return pageBean; } @Override diff --git a/novel-front/src/main/java/com/java2nb/novel/service/impl/LikeServiceImpl.java b/novel-front/src/main/java/com/java2nb/novel/service/impl/LikeServiceImpl.java new file mode 100644 index 0000000..23498d6 --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/service/impl/LikeServiceImpl.java @@ -0,0 +1,94 @@ +package com.java2nb.novel.service.impl; + +import com.java2nb.novel.service.LikeService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.scripting.support.StaticScriptSource; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +/** + * @author xiongxiaoyang + * @date 2025/7/12 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class LikeServiceImpl implements LikeService { + + private final RedisTemplate redisTemplate; + + private DefaultRedisScript toggleLikeScript; + + private static final String COMMENT_LIKE_KEY_PREFIX = "like:comment:"; + private static final String COMMENT_REPLY_LIKE_KEY_PREFIX = "like:comment:reply:"; + private static final String COMMENT_UN_LIKE_KEY_PREFIX = "unlike:comment:"; + private static final String COMMENT_REPLY_UN_LIKE_KEY_PREFIX = "unlike:comment:reply:"; + + @PostConstruct + public void init() { + // Lua 脚本保证原子性操作 + String script = """ + local key = KEYS[1] + local userId = ARGV[1] + + local isLiked = redis.call('SISMEMBER', key, userId) + + if isLiked == 1 then + redis.call('SREM', key, userId) + else + redis.call('SADD', key, userId) + end + + return redis.call('SCARD', key) + """; + + toggleLikeScript = new DefaultRedisScript<>(); + toggleLikeScript.setScriptSource(new StaticScriptSource(script)); + toggleLikeScript.setResultType(Long.class); + } + + public Long toggleCommentLike(Long commentId, Long userId) { + return executeToggle(COMMENT_LIKE_KEY_PREFIX + commentId, userId); + } + + @Override + public Long toggleCommentUnLike(Long commentId, Long userId) { + return executeToggle(COMMENT_UN_LIKE_KEY_PREFIX + commentId, userId); + } + + public Long getCommentLikesCount(Long commentId) { + return redisTemplate.opsForSet().size(COMMENT_LIKE_KEY_PREFIX + commentId); + } + + @Override + public Long getCommentUnLikesCount(Long commentId) { + return redisTemplate.opsForSet().size(COMMENT_UN_LIKE_KEY_PREFIX + commentId); + } + + public Long toggleReplyLike(Long replyId, Long userId) { + return executeToggle(COMMENT_REPLY_LIKE_KEY_PREFIX + replyId, userId); + } + + @Override + public Long toggleReplyUnLike(Long replyId, Long userId) { + return executeToggle(COMMENT_REPLY_UN_LIKE_KEY_PREFIX + replyId, userId); + } + + public Long getReplyLikesCount(Long replyId) { + return redisTemplate.opsForSet().size(COMMENT_REPLY_LIKE_KEY_PREFIX + replyId); + } + + @Override + public Long getReplyUnLikesCount(Long replyId) { + return redisTemplate.opsForSet().size(COMMENT_REPLY_UN_LIKE_KEY_PREFIX + replyId); + } + + private Long executeToggle(String key, Long userId) { + return redisTemplate.execute(toggleLikeScript, Collections.singletonList(key), userId); + } +} diff --git a/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentReplyVO.java b/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentReplyVO.java index 8ff7890..42d75be 100644 --- a/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentReplyVO.java +++ b/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentReplyVO.java @@ -23,6 +23,10 @@ public class BookCommentReplyVO extends BookCommentReply { @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; + private Long likesCount; + + private Long unLikesCount; + @Override public String toString() { return super.toString(); diff --git a/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentVO.java b/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentVO.java index 1d553cf..893d616 100644 --- a/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentVO.java +++ b/novel-front/src/main/java/com/java2nb/novel/vo/BookCommentVO.java @@ -22,6 +22,10 @@ public class BookCommentVO extends BookComment { @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; + private Long likesCount; + + private Long unLikesCount; + @Override public String toString() { return super.toString(); diff --git a/novel-front/src/main/resources/templates/book/book_comment.html b/novel-front/src/main/resources/templates/book/book_comment.html index 3ab30d2..40f7c8e 100644 --- a/novel-front/src/main/resources/templates/book/book_comment.html +++ b/novel-front/src/main/resources/templates/book/book_comment.html @@ -139,8 +139,8 @@ comment.commentContent+ "
  • " + ""+comment.createTime+"" + - "(0)" + - "(0)" + + "("+comment.unLikesCount+")" + + "("+comment.likesCount+")" + "回复("+comment.replyCount+ ")" + "
  • \t\t\t"); @@ -188,6 +188,48 @@ }) } + + function toggleCommentLike(commentId) { + $.ajax({ + type: "post", + url: "/book/toggleCommentLike", + data: {'commentId': commentId}, + dataType: "json", + success: function (data) { + if (data.code == 200) { + $("#likeCount"+commentId).text("("+data.data+")") + } else { + layer.alert(data.msg); + } + + }, + error: function () { + layer.alert('网络异常'); + } + }) + + } + + function toggleCommentUnLike(commentId) { + $.ajax({ + type: "post", + url: "/book/toggleCommentUnLike", + data: {'commentId': commentId}, + dataType: "json", + success: function (data) { + if (data.code == 200) { + $("#unLikeCount"+commentId).text("("+data.data+")") + } else { + layer.alert(data.msg); + } + + }, + error: function () { + layer.alert('网络异常'); + } + }) + + } diff --git a/novel-front/src/main/resources/templates/book/book_comment_reply.html b/novel-front/src/main/resources/templates/book/book_comment_reply.html index d8c1d9c..1ee4423 100644 --- a/novel-front/src/main/resources/templates/book/book_comment_reply.html +++ b/novel-front/src/main/resources/templates/book/book_comment_reply.html @@ -114,8 +114,9 @@ "
  • " + "" + (data.data.total - ((curr - 1) * limit + i)) + "楼" + "" + comment.createTime + "" + - "(0)" + - "
  • \t\t\t"); + "("+comment.unLikesCount+")" + + "("+comment.likesCount+")" + + "\t\t\t"); } $("#commentPanel").html(commentListHtml); @@ -160,6 +161,48 @@ }) } + + function toggleCommentLike(replyId) { + $.ajax({ + type: "post", + url: "/book/toggleReplyLike", + data: {'replyId': replyId}, + dataType: "json", + success: function (data) { + if (data.code == 200) { + $("#likeCount"+replyId).text("("+data.data+")") + } else { + layer.alert(data.msg); + } + + }, + error: function () { + layer.alert('网络异常'); + } + }) + + } + + function toggleCommentUnLike(replyId) { + $.ajax({ + type: "post", + url: "/book/toggleReplyUnLike", + data: {'replyId': replyId}, + dataType: "json", + success: function (data) { + if (data.code == 200) { + $("#unLikeCount"+replyId).text("("+data.data+")") + } else { + layer.alert(data.msg); + } + + }, + error: function () { + layer.alert('网络异常'); + } + }) + + } diff --git a/novel-front/src/main/resources/templates/book/book_detail.html b/novel-front/src/main/resources/templates/book/book_detail.html index a6a335d..c15d5ec 100644 --- a/novel-front/src/main/resources/templates/book/book_detail.html +++ b/novel-front/src/main/resources/templates/book/book_detail.html @@ -115,10 +115,10 @@
  • (0)(0)([[${comment.unLikesCount}]])([[${comment.likesCount}]])回复([[${comment.replyCount}]])
  • @@ -350,8 +350,8 @@ comment.commentContent + "
  • " + "" + comment.createTime + "" + - "(0)" + - "(0)" + + "("+comment.unLikesCount+")" + + "("+comment.likesCount+")" + "回复("+comment.replyCount+ ")" + "
  • \t\t\t" @@ -380,6 +380,47 @@ } }) } + function toggleCommentLike(commentId) { + $.ajax({ + type: "post", + url: "/book/toggleCommentLike", + data: {'commentId': commentId}, + dataType: "json", + success: function (data) { + if (data.code == 200) { + $("#likeCount"+commentId).text("("+data.data+")") + } else { + layer.alert(data.msg); + } + + }, + error: function () { + layer.alert('网络异常'); + } + }) + + } + + function toggleCommentUnLike(commentId) { + $.ajax({ + type: "post", + url: "/book/toggleCommentUnLike", + data: {'commentId': commentId}, + dataType: "json", + success: function (data) { + if (data.code == 200) { + $("#unLikeCount"+commentId).text("("+data.data+")") + } else { + layer.alert(data.msg); + } + + }, + error: function () { + layer.alert('网络异常'); + } + }) + + }