refactor: 基于 novel 项目 & Spring Cloud 2022 & Spring Cloud Alibaba 2022 重构

This commit is contained in:
xiongxiaoyang
2023-03-30 16:15:56 +08:00
parent d68ce51c82
commit 3d098eea5e
505 changed files with 14127 additions and 24067 deletions

View File

@@ -0,0 +1,21 @@
package io.github.xxyopen.novel.book;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(scanBasePackages = {"io.github.xxyopen.novel"})
@MapperScan("io.github.xxyopen.novel.book.dao.mapper")
@EnableCaching
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"io.github.xxyopen.novel.user.feign"})
public class NovelBookApplication {
public static void main(String[] args) {
SpringApplication.run(NovelBookApplication.class, args);
}
}

View File

@@ -0,0 +1,33 @@
package io.github.xxyopen.novel.book.config;
import io.github.xxyopen.novel.common.constant.ApiRouterConsts;
import io.github.xxyopen.novel.config.interceptor.TokenParseInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Spring Web Mvc 相关配置不要加 @EnableWebMvc 注解,否则会导致 jackson 的全局配置失效。因为 @EnableWebMvc 注解会导致 WebMvcAutoConfiguration 自动配置失效
*
* @author xiongxiaoyang
* @date 2022/5/18
*/
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final TokenParseInterceptor tokenParseInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Token 解析拦截器
registry.addInterceptor(tokenParseInterceptor)
// 拦截小说内容查询接口,需要解析 token 以判断该用户是否有权阅读该章节(付费章节是否已购买)
.addPathPatterns(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX + "/content/*");
}
}

View File

@@ -0,0 +1,156 @@
package io.github.xxyopen.novel.book.controller.front;
import io.github.xxyopen.novel.book.dto.resp.*;
import io.github.xxyopen.novel.book.service.BookService;
import io.github.xxyopen.novel.common.constant.ApiRouterConsts;
import io.github.xxyopen.novel.common.resp.RestResp;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.security.NoSuchAlgorithmException;
import java.util.List;
/**
* 前台门户-小说模块 API 控制器
*
* @author xiongxiaoyang
* @date 2022/5/14
*/
@Tag(name = "FrontBookController", description = "前台门户-小说模块")
@RestController
@RequestMapping(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX)
@RequiredArgsConstructor
public class FrontBookController {
private final BookService bookService;
/**
* 小说分类列表查询接口
*/
@Operation(summary = "小说分类列表查询接口")
@GetMapping("category/list")
public RestResp<List<BookCategoryRespDto>> listCategory(
@Parameter(description = "作品方向", required = true) Integer workDirection) {
return bookService.listCategory(workDirection);
}
/**
* 小说信息查询接口
*/
@Operation(summary = "小说信息查询接口")
@GetMapping("{id}")
public RestResp<BookInfoRespDto> getBookById(
@Parameter(description = "小说 ID") @PathVariable("id") Long bookId) {
return bookService.getBookById(bookId);
}
/**
* 增加小说点击量接口
*/
@Operation(summary = "增加小说点击量接口")
@PostMapping("visit")
public RestResp<Void> addVisitCount(@Parameter(description = "小说ID") Long bookId) {
return bookService.addVisitCount(bookId);
}
/**
* 小说最新章节相关信息查询接口
*/
@Operation(summary = "小说最新章节相关信息查询接口")
@GetMapping("last_chapter/about")
public RestResp<BookChapterAboutRespDto> getLastChapterAbout(
@Parameter(description = "小说ID") Long bookId) {
return bookService.getLastChapterAbout(bookId);
}
/**
* 小说推荐列表查询接口
*/
@Operation(summary = "小说推荐列表查询接口")
@GetMapping("rec_list")
public RestResp<List<BookInfoRespDto>> listRecBooks(
@Parameter(description = "小说ID") Long bookId) throws NoSuchAlgorithmException {
return bookService.listRecBooks(bookId);
}
/**
* 小说章节列表查询接口
*/
@Operation(summary = "小说章节列表查询接口")
@GetMapping("chapter/list")
public RestResp<List<BookChapterRespDto>> listChapters(
@Parameter(description = "小说ID") Long bookId) {
return bookService.listChapters(bookId);
}
/**
* 小说内容相关信息查询接口
*/
@Operation(summary = "小说内容相关信息查询接口")
@GetMapping("content/{chapterId}")
public RestResp<BookContentAboutRespDto> getBookContentAbout(
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
return bookService.getBookContentAbout(chapterId);
}
/**
* 获取上一章节ID接口
*/
@Operation(summary = "获取上一章节ID接口")
@GetMapping("pre_chapter_id/{chapterId}")
public RestResp<Long> getPreChapterId(
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
return bookService.getPreChapterId(chapterId);
}
/**
* 获取下一章节ID接口
*/
@Operation(summary = "获取下一章节ID接口")
@GetMapping("next_chapter_id/{chapterId}")
public RestResp<Long> getNextChapterId(
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
return bookService.getNextChapterId(chapterId);
}
/**
* 小说点击榜查询接口
*/
@Operation(summary = "小说点击榜查询接口")
@GetMapping("visit_rank")
public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
return bookService.listVisitRankBooks();
}
/**
* 小说新书榜查询接口
*/
@Operation(summary = "小说新书榜查询接口")
@GetMapping("newest_rank")
public RestResp<List<BookRankRespDto>> listNewestRankBooks() {
return bookService.listNewestRankBooks();
}
/**
* 小说更新榜查询接口
*/
@Operation(summary = "小说更新榜查询接口")
@GetMapping("update_rank")
public RestResp<List<BookRankRespDto>> listUpdateRankBooks() {
return bookService.listUpdateRankBooks();
}
/**
* 小说最新评论查询接口
*/
@Operation(summary = "小说最新评论查询接口")
@GetMapping("comment/newest_list")
public RestResp<BookCommentRespDto> listNewestComments(
@Parameter(description = "小说ID") Long bookId) {
return bookService.listNewestComments(bookId);
}
}

View File

@@ -0,0 +1,116 @@
package io.github.xxyopen.novel.book.controller.inner;
import io.github.xxyopen.novel.book.dto.req.*;
import io.github.xxyopen.novel.book.dto.resp.BookChapterRespDto;
import io.github.xxyopen.novel.book.dto.resp.BookEsRespDto;
import io.github.xxyopen.novel.book.dto.resp.BookInfoRespDto;
import io.github.xxyopen.novel.book.service.BookService;
import io.github.xxyopen.novel.common.constant.ApiRouterConsts;
import io.github.xxyopen.novel.common.resp.PageRespDto;
import io.github.xxyopen.novel.common.resp.RestResp;
import io.github.xxyopen.novel.user.dto.resp.UserInfoRespDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 小说微服务内部调用接口
*
* @author xiongxiaoyang
* @date 2023/3/29
*/
@Tag(name = "InnerBookController", description = "内部调用-小说模块")
@RestController
@RequestMapping(ApiRouterConsts.API_INNER_BOOK_URL_PREFIX)
@RequiredArgsConstructor
public class InnerBookController {
private final BookService bookService;
/**
* 查询下一批保存到 ES 中的小说列表
*/
@Operation(summary = "查询下一批保存到 ES 中的小说列表")
@PostMapping("listNextEsBooks")
RestResp<List<BookEsRespDto>> listNextEsBooks(@Parameter(description = "已查询的最大小说ID") Long maxBookId) {
return bookService.listNextEsBooks(maxBookId);
}
/**
* 批量查询小说信息
*/
@Operation(summary = "批量查询小说信息")
@PostMapping("listBookInfoByIds")
RestResp<List<BookInfoRespDto>> listBookInfoByIds(@RequestBody List<Long> bookIds) {
return bookService.listBookInfoByIds(bookIds);
}
/**
* 发表评论接口
*/
@Operation(summary = "发表评论接口")
@PostMapping("publishComment")
public RestResp<Void> publishComment(@Valid @RequestBody BookCommentReqDto dto) {
return bookService.saveComment(dto);
}
/**
* 修改评论接口
*/
@Operation(summary = "修改评论接口")
@PostMapping("updateComment")
public RestResp<Void> updateComment(@Valid @RequestBody BookCommentReqDto dto) {
return bookService.updateComment(dto);
}
/**
* 删除评论接口
*/
@Operation(summary = "删除评论接口")
@PostMapping("deleteComment")
public RestResp<Void> deleteComment(@RequestBody BookCommentReqDto dto) {
return bookService.deleteComment(dto);
}
/**
* 小说发布接口
*/
@Operation(summary = "小说发布接口")
@PostMapping("publishBook")
public RestResp<Void> publishBook(@Valid @RequestBody BookAddReqDto dto) {
return bookService.saveBook(dto);
}
/**
* 小说发布列表查询接口
*/
@Operation(summary = "小说发布列表查询接口")
@PostMapping("listPublishBooks")
public RestResp<PageRespDto<BookInfoRespDto>> listPublishBooks(@RequestBody BookPageReqDto dto) {
return bookService.listAuthorBooks(dto);
}
/**
* 小说章节发布接口
*/
@Operation(summary = "小说章节发布接口")
@PostMapping("publishBookChapter")
public RestResp<Void> publishBookChapter(@Valid @RequestBody ChapterAddReqDto dto) {
return bookService.saveBookChapter(dto);
}
/**
* 小说章节发布列表查询接口
*/
@Operation(summary = "小说章节发布列表查询接口")
@PostMapping("listPublishBookChapters")
public RestResp<PageRespDto<BookChapterRespDto>> listPublishBookChapters(@RequestBody ChapterPageReqDto dto) {
return bookService.listBookChapters(dto);
}
}

View File

@@ -0,0 +1,111 @@
package io.github.xxyopen.novel.book.dao.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 小说类别
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/12
*/
@TableName("book_category")
public class BookCategory implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 作品方向;0-男频 1-女频
*/
private Integer workDirection;
/**
* 类别名
*/
private String name;
/**
* 排序
*/
private Integer sort;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getWorkDirection() {
return workDirection;
}
public void setWorkDirection(Integer workDirection) {
this.workDirection = workDirection;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BookCategory{" +
"id=" + id +
", workDirection=" + workDirection +
", name=" + name +
", sort=" + sort +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}

View File

@@ -0,0 +1,133 @@
package io.github.xxyopen.novel.book.dao.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 小说章节
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
@TableName("book_chapter")
public class BookChapter implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 小说ID
*/
private Long bookId;
/**
* 章节号
*/
private Integer chapterNum;
/**
* 章节名
*/
private String chapterName;
/**
* 章节字数
*/
private Integer wordCount;
/**
* 是否收费;1-收费 0-免费
*/
private Integer isVip;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
public Integer getChapterNum() {
return chapterNum;
}
public void setChapterNum(Integer chapterNum) {
this.chapterNum = chapterNum;
}
public String getChapterName() {
return chapterName;
}
public void setChapterName(String chapterName) {
this.chapterName = chapterName;
}
public Integer getWordCount() {
return wordCount;
}
public void setWordCount(Integer wordCount) {
this.wordCount = wordCount;
}
public Integer getIsVip() {
return isVip;
}
public void setIsVip(Integer isVip) {
this.isVip = isVip;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BookChapter{" +
"id=" + id +
", bookId=" + bookId +
", chapterNum=" + chapterNum +
", chapterName=" + chapterName +
", wordCount=" + wordCount +
", isVip=" + isVip +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}

View File

@@ -0,0 +1,142 @@
package io.github.xxyopen.novel.book.dao.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 小说评论
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
@TableName("book_comment")
public class BookComment implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 评论小说ID
*/
private Long bookId;
/**
* 评论用户ID
*/
private Long userId;
/**
* 评价内容
*/
private String commentContent;
/**
* 回复数量
*/
private Integer replyCount;
/**
* 审核状态;0-待审核 1-审核通过 2-审核不通过
*/
private Integer auditStatus;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getCommentContent() {
return commentContent;
}
public void setCommentContent(String commentContent) {
this.commentContent = commentContent;
}
public Integer getReplyCount() {
return replyCount;
}
public void setReplyCount(Integer replyCount) {
this.replyCount = replyCount;
}
public Integer getAuditStatus() {
return auditStatus;
}
public void setAuditStatus(Integer auditStatus) {
this.auditStatus = auditStatus;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BookComment{" +
"id=" + id +
", bookId=" + bookId +
", userId=" + userId +
", commentContent=" + commentContent +
", replyCount=" + replyCount +
", auditStatus=" + auditStatus +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}

View File

@@ -0,0 +1,142 @@
package io.github.xxyopen.novel.book.dao.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 小说评论
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
@TableName("book_comment_copy1")
public class BookCommentCopy1 implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 评论小说ID
*/
private Long bookId;
/**
* 评论用户ID
*/
private Long userId;
/**
* 评价内容
*/
private String commentContent;
/**
* 回复数量
*/
private Integer replyCount;
/**
* 审核状态;0-待审核 1-审核通过 2-审核不通过
*/
private Integer auditStatus;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getCommentContent() {
return commentContent;
}
public void setCommentContent(String commentContent) {
this.commentContent = commentContent;
}
public Integer getReplyCount() {
return replyCount;
}
public void setReplyCount(Integer replyCount) {
this.replyCount = replyCount;
}
public Integer getAuditStatus() {
return auditStatus;
}
public void setAuditStatus(Integer auditStatus) {
this.auditStatus = auditStatus;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BookCommentCopy1{" +
"id=" + id +
", bookId=" + bookId +
", userId=" + userId +
", commentContent=" + commentContent +
", replyCount=" + replyCount +
", auditStatus=" + auditStatus +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}

View File

@@ -0,0 +1,125 @@
package io.github.xxyopen.novel.book.dao.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 小说评论回复
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
@TableName("book_comment_reply")
public class BookCommentReply implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id;
/**
* 评论ID
*/
private Long commentId;
/**
* 回复用户ID
*/
private Long userId;
/**
* 回复内容
*/
private String replyContent;
/**
* 审核状态;0-待审核 1-审核通过 2-审核不通过
*/
private Integer auditStatus;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCommentId() {
return commentId;
}
public void setCommentId(Long commentId) {
this.commentId = commentId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getReplyContent() {
return replyContent;
}
public void setReplyContent(String replyContent) {
this.replyContent = replyContent;
}
public Integer getAuditStatus() {
return auditStatus;
}
public void setAuditStatus(Integer auditStatus) {
this.auditStatus = auditStatus;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BookCommentReply{" +
"id=" + id +
", commentId=" + commentId +
", userId=" + userId +
", replyContent=" + replyContent +
", auditStatus=" + auditStatus +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}

View File

@@ -0,0 +1,94 @@
package io.github.xxyopen.novel.book.dao.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 小说内容
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
@TableName("book_content")
public class BookContent implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 章节ID
*/
private Long chapterId;
/**
* 小说章节内容
*/
private String content;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getChapterId() {
return chapterId;
}
public void setChapterId(Long chapterId) {
this.chapterId = chapterId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BookContent{" +
"id=" + id +
", chapterId=" + chapterId +
", content=" + content +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}

View File

@@ -0,0 +1,310 @@
package io.github.xxyopen.novel.book.dao.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 小说信息
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
@TableName("book_info")
public class BookInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 作品方向;0-男频 1-女频
*/
private Integer workDirection;
/**
* 类别ID
*/
private Long categoryId;
/**
* 类别名
*/
private String categoryName;
/**
* 小说封面地址
*/
private String picUrl;
/**
* 小说名
*/
private String bookName;
/**
* 作家id
*/
private Long authorId;
/**
* 作家名
*/
private String authorName;
/**
* 书籍描述
*/
private String bookDesc;
/**
* 评分;总分:10 ,真实评分 = score/10
*/
private Integer score;
/**
* 书籍状态;0-连载中 1-已完结
*/
private Integer bookStatus;
/**
* 点击量
*/
private Long visitCount;
/**
* 总字数
*/
private Integer wordCount;
/**
* 评论数
*/
private Integer commentCount;
/**
* 最新章节ID
*/
private Long lastChapterId;
/**
* 最新章节名
*/
private String lastChapterName;
/**
* 最新章节更新时间
*/
private LocalDateTime lastChapterUpdateTime;
/**
* 是否收费;1-收费 0-免费
*/
private Integer isVip;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getWorkDirection() {
return workDirection;
}
public void setWorkDirection(Integer workDirection) {
this.workDirection = workDirection;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getPicUrl() {
return picUrl;
}
public void setPicUrl(String picUrl) {
this.picUrl = picUrl;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Long getAuthorId() {
return authorId;
}
public void setAuthorId(Long authorId) {
this.authorId = authorId;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
public String getBookDesc() {
return bookDesc;
}
public void setBookDesc(String bookDesc) {
this.bookDesc = bookDesc;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
public Integer getBookStatus() {
return bookStatus;
}
public void setBookStatus(Integer bookStatus) {
this.bookStatus = bookStatus;
}
public Long getVisitCount() {
return visitCount;
}
public void setVisitCount(Long visitCount) {
this.visitCount = visitCount;
}
public Integer getWordCount() {
return wordCount;
}
public void setWordCount(Integer wordCount) {
this.wordCount = wordCount;
}
public Integer getCommentCount() {
return commentCount;
}
public void setCommentCount(Integer commentCount) {
this.commentCount = commentCount;
}
public Long getLastChapterId() {
return lastChapterId;
}
public void setLastChapterId(Long lastChapterId) {
this.lastChapterId = lastChapterId;
}
public String getLastChapterName() {
return lastChapterName;
}
public void setLastChapterName(String lastChapterName) {
this.lastChapterName = lastChapterName;
}
public LocalDateTime getLastChapterUpdateTime() {
return lastChapterUpdateTime;
}
public void setLastChapterUpdateTime(LocalDateTime lastChapterUpdateTime) {
this.lastChapterUpdateTime = lastChapterUpdateTime;
}
public Integer getIsVip() {
return isVip;
}
public void setIsVip(Integer isVip) {
this.isVip = isVip;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BookInfo{" +
"id=" + id +
", workDirection=" + workDirection +
", categoryId=" + categoryId +
", categoryName=" + categoryName +
", picUrl=" + picUrl +
", bookName=" + bookName +
", authorId=" + authorId +
", authorName=" + authorName +
", bookDesc=" + bookDesc +
", score=" + score +
", bookStatus=" + bookStatus +
", visitCount=" + visitCount +
", wordCount=" + wordCount +
", commentCount=" + commentCount +
", lastChapterId=" + lastChapterId +
", lastChapterName=" + lastChapterName +
", lastChapterUpdateTime=" + lastChapterUpdateTime +
", isVip=" + isVip +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}

View File

@@ -0,0 +1,16 @@
package io.github.xxyopen.novel.book.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.xxyopen.novel.book.dao.entity.BookCategory;
/**
* <p>
* 小说类别 Mapper 接口
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/12
*/
public interface BookCategoryMapper extends BaseMapper<BookCategory> {
}

View File

@@ -0,0 +1,17 @@
package io.github.xxyopen.novel.book.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.xxyopen.novel.book.dao.entity.BookChapter;
/**
* <p>
* 小说章节 Mapper 接口
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
public interface BookChapterMapper extends BaseMapper<BookChapter> {
}

View File

@@ -0,0 +1,16 @@
package io.github.xxyopen.novel.book.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.xxyopen.novel.book.dao.entity.BookCommentCopy1;
/**
* <p>
* 小说评论 Mapper 接口
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
public interface BookCommentCopy1Mapper extends BaseMapper<BookCommentCopy1> {
}

View File

@@ -0,0 +1,16 @@
package io.github.xxyopen.novel.book.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.xxyopen.novel.book.dao.entity.BookComment;
/**
* <p>
* 小说评论 Mapper 接口
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
public interface BookCommentMapper extends BaseMapper<BookComment> {
}

View File

@@ -0,0 +1,16 @@
package io.github.xxyopen.novel.book.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.xxyopen.novel.book.dao.entity.BookCommentReply;
/**
* <p>
* 小说评论回复 Mapper 接口
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
public interface BookCommentReplyMapper extends BaseMapper<BookCommentReply> {
}

View File

@@ -0,0 +1,16 @@
package io.github.xxyopen.novel.book.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.xxyopen.novel.book.dao.entity.BookContent;
/**
* <p>
* 小说内容 Mapper 接口
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
public interface BookContentMapper extends BaseMapper<BookContent> {
}

View File

@@ -0,0 +1,37 @@
package io.github.xxyopen.novel.book.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.github.xxyopen.novel.book.dao.entity.BookInfo;
import io.github.xxyopen.novel.book.dto.req.BookSearchReqDto;
import io.github.xxyopen.novel.book.dto.resp.BookInfoRespDto;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* <p>
* 小说信息 Mapper 接口
* </p>
*
* @author xiongxiaoyang
* @date 2022/05/11
*/
public interface BookInfoMapper extends BaseMapper<BookInfo> {
/**
* 增加小说点击量
*
* @param bookId 小说ID
*/
void addVisitCount(@Param("bookId") Long bookId);
/**
* 小说搜索
* @param page mybatis-plus 分页对象
* @param condition 搜索条件
* @return 返回结果
* */
List<BookInfo> searchBooks(IPage<BookInfoRespDto> page, BookSearchReqDto condition);
}

View File

@@ -0,0 +1,42 @@
package io.github.xxyopen.novel.book.manager.cache;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.github.xxyopen.novel.book.dao.entity.BookCategory;
import io.github.xxyopen.novel.book.dao.mapper.BookCategoryMapper;
import io.github.xxyopen.novel.book.dto.resp.BookCategoryRespDto;
import io.github.xxyopen.novel.common.constant.CacheConsts;
import io.github.xxyopen.novel.common.constant.DatabaseConsts;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 小说分类 缓存管理类
*
* @author xiongxiaoyang
* @date 2022/5/12
*/
@Component
@RequiredArgsConstructor
public class BookCategoryCacheManager {
private final BookCategoryMapper bookCategoryMapper;
/**
* 根据作品方向查询小说分类列表,并放入缓存中
*/
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.BOOK_CATEGORY_LIST_CACHE_NAME)
public List<BookCategoryRespDto> listCategory(Integer workDirection) {
QueryWrapper<BookCategory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookCategoryTable.COLUMN_WORK_DIRECTION, workDirection);
return bookCategoryMapper.selectList(queryWrapper).stream().map(v ->
BookCategoryRespDto.builder()
.id(v.getId())
.name(v.getName())
.build()).toList();
}
}

View File

@@ -0,0 +1,41 @@
package io.github.xxyopen.novel.book.manager.cache;
import io.github.xxyopen.novel.book.dao.entity.BookChapter;
import io.github.xxyopen.novel.book.dao.mapper.BookChapterMapper;
import io.github.xxyopen.novel.book.dto.resp.BookChapterRespDto;
import io.github.xxyopen.novel.common.constant.CacheConsts;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
/**
* 小说章节 缓存管理类
*
* @author xiongxiaoyang
* @date 2022/5/12
*/
@Component
@RequiredArgsConstructor
public class BookChapterCacheManager {
private final BookChapterMapper bookChapterMapper;
/**
* 查询小说章节信息,并放入缓存中
*/
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.BOOK_CHAPTER_CACHE_NAME)
public BookChapterRespDto getChapter(Long chapterId) {
BookChapter bookChapter = bookChapterMapper.selectById(chapterId);
return BookChapterRespDto.builder()
.id(chapterId)
.bookId(bookChapter.getBookId())
.chapterNum(bookChapter.getChapterNum())
.chapterName(bookChapter.getChapterName())
.chapterWordCount(bookChapter.getWordCount())
.chapterUpdateTime(bookChapter.getUpdateTime())
.build();
}
}

View File

@@ -0,0 +1,38 @@
package io.github.xxyopen.novel.book.manager.cache;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.github.xxyopen.novel.book.dao.entity.BookContent;
import io.github.xxyopen.novel.book.dao.mapper.BookContentMapper;
import io.github.xxyopen.novel.common.constant.CacheConsts;
import io.github.xxyopen.novel.common.constant.DatabaseConsts;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
/**
* 小说内容 缓存管理类
*
* @author xiongxiaoyang
* @date 2022/5/12
*/
@Component
@RequiredArgsConstructor
public class BookContentCacheManager {
private final BookContentMapper bookContentMapper;
/**
* 查询小说内容,并放入缓存中
*/
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
value = CacheConsts.BOOK_CONTENT_CACHE_NAME)
public String getBookContent(Long chapterId) {
QueryWrapper<BookContent> contentQueryWrapper = new QueryWrapper<>();
contentQueryWrapper.eq(DatabaseConsts.BookContentTable.COLUMN_CHAPTER_ID, chapterId)
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
BookContent bookContent = bookContentMapper.selectOne(contentQueryWrapper);
return bookContent.getContent();
}
}

View File

@@ -0,0 +1,96 @@
package io.github.xxyopen.novel.book.manager.cache;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.github.xxyopen.novel.book.dao.entity.BookChapter;
import io.github.xxyopen.novel.book.dao.entity.BookInfo;
import io.github.xxyopen.novel.book.dao.mapper.BookChapterMapper;
import io.github.xxyopen.novel.book.dao.mapper.BookInfoMapper;
import io.github.xxyopen.novel.book.dto.resp.BookInfoRespDto;
import io.github.xxyopen.novel.common.constant.CacheConsts;
import io.github.xxyopen.novel.common.constant.DatabaseConsts;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 小说信息 缓存管理类
*
* @author xiongxiaoyang
* @date 2022/5/12
*/
@Component
@RequiredArgsConstructor
public class BookInfoCacheManager {
private final BookInfoMapper bookInfoMapper;
private final BookChapterMapper bookChapterMapper;
/**
* 从缓存中查询小说信息(先判断缓存中是否已存在,存在则直接从缓存中取,否则执行方法体中的逻辑后缓存结果)
*/
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.BOOK_INFO_CACHE_NAME)
public BookInfoRespDto getBookInfo(Long id) {
return cachePutBookInfo(id);
}
/**
* 缓存小说信息(不管缓存中是否存在都执行方法体中的逻辑,然后缓存起来)
*/
@CachePut(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.BOOK_INFO_CACHE_NAME)
public BookInfoRespDto cachePutBookInfo(Long id) {
// 查询基础信息
BookInfo bookInfo = bookInfoMapper.selectById(id);
// 查询首章ID
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, id)
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
BookChapter firstBookChapter = bookChapterMapper.selectOne(queryWrapper);
// 组装响应对象
return BookInfoRespDto.builder()
.id(bookInfo.getId())
.bookName(bookInfo.getBookName())
.bookDesc(bookInfo.getBookDesc())
.bookStatus(bookInfo.getBookStatus())
.authorId(bookInfo.getAuthorId())
.authorName(bookInfo.getAuthorName())
.categoryId(bookInfo.getCategoryId())
.categoryName(bookInfo.getCategoryName())
.commentCount(bookInfo.getCommentCount())
.firstChapterId(firstBookChapter.getId())
.lastChapterId(bookInfo.getLastChapterId())
.picUrl(bookInfo.getPicUrl())
.visitCount(bookInfo.getVisitCount())
.wordCount(bookInfo.getWordCount())
.build();
}
@CacheEvict(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.BOOK_INFO_CACHE_NAME)
public void evictBookInfoCache(Long ignoredId) {
// 调用此方法自动清除小说信息的缓存
}
/**
* 查询每个类别下最新更新的 500 个小说ID列表,并放入缓存中 1 个小时
*/
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME)
public List<Long> getLastUpdateIdList(Long categoryId) {
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookTable.COLUMN_CATEGORY_ID, categoryId)
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
.orderByDesc(DatabaseConsts.BookTable.COLUMN_LAST_CHAPTER_UPDATE_TIME)
.last(DatabaseConsts.SqlEnum.LIMIT_500.getSql());
return bookInfoMapper.selectList(queryWrapper).stream().map(BookInfo::getId).toList();
}
}

View File

@@ -0,0 +1,84 @@
package io.github.xxyopen.novel.book.manager.cache;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.github.xxyopen.novel.book.dao.entity.BookInfo;
import io.github.xxyopen.novel.book.dao.mapper.BookInfoMapper;
import io.github.xxyopen.novel.book.dto.resp.BookRankRespDto;
import io.github.xxyopen.novel.common.constant.CacheConsts;
import io.github.xxyopen.novel.common.constant.DatabaseConsts;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 小说排行榜 缓存管理类
*
* @author xiongxiaoyang
* @date 2022/5/12
*/
@Component
@RequiredArgsConstructor
public class BookRankCacheManager {
private final BookInfoMapper bookInfoMapper;
/**
* 查询小说点击榜列表,并放入缓存中
*/
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
value = CacheConsts.BOOK_VISIT_RANK_CACHE_NAME)
public List<BookRankRespDto> listVisitRankBooks() {
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
bookInfoQueryWrapper.orderByDesc(DatabaseConsts.BookTable.COLUMN_VISIT_COUNT);
return listRankBooks(bookInfoQueryWrapper);
}
/**
* 查询小说新书榜列表,并放入缓存中
*/
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.BOOK_NEWEST_RANK_CACHE_NAME)
public List<BookRankRespDto> listNewestRankBooks() {
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
bookInfoQueryWrapper
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
return listRankBooks(bookInfoQueryWrapper);
}
/**
* 查询小说更新榜列表,并放入缓存中
*/
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
value = CacheConsts.BOOK_UPDATE_RANK_CACHE_NAME)
public List<BookRankRespDto> listUpdateRankBooks() {
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
bookInfoQueryWrapper
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
.orderByDesc(DatabaseConsts.CommonColumnEnum.UPDATE_TIME.getName());
return listRankBooks(bookInfoQueryWrapper);
}
private List<BookRankRespDto> listRankBooks(QueryWrapper<BookInfo> bookInfoQueryWrapper) {
bookInfoQueryWrapper
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
return bookInfoMapper.selectList(bookInfoQueryWrapper).stream().map(v -> {
BookRankRespDto respDto = new BookRankRespDto();
respDto.setId(v.getId());
respDto.setCategoryId(v.getCategoryId());
respDto.setCategoryName(v.getCategoryName());
respDto.setBookName(v.getBookName());
respDto.setAuthorName(v.getAuthorName());
respDto.setPicUrl(v.getPicUrl());
respDto.setBookDesc(v.getBookDesc());
respDto.setLastChapterName(v.getLastChapterName());
respDto.setLastChapterUpdateTime(v.getLastChapterUpdateTime());
respDto.setWordCount(v.getWordCount());
return respDto;
}).toList();
}
}

View File

@@ -0,0 +1,36 @@
package io.github.xxyopen.novel.book.manager.feign;
import io.github.xxyopen.novel.common.constant.ErrorCodeEnum;
import io.github.xxyopen.novel.common.resp.RestResp;
import io.github.xxyopen.novel.user.dto.resp.UserInfoRespDto;
import io.github.xxyopen.novel.user.feign.UserFeign;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 用户微服务调用 Feign 客户端管理
*
* @author xiongxiaoyang
* @date 2023/3/29
*/
@Component
@AllArgsConstructor
public class UserFeignManager {
private final UserFeign userFeign;
public List<UserInfoRespDto> listUserInfoByIds(List<Long> userIds) {
RestResp<List<UserInfoRespDto>> resp = userFeign.listUserInfoByIds(userIds);
if (Objects.equals(ErrorCodeEnum.OK.getCode(), resp.getCode())) {
return resp.getData();
}
return new ArrayList<>(0);
}
}

View File

@@ -0,0 +1,51 @@
package io.github.xxyopen.novel.book.manager.mq;
import io.github.xxyopen.novel.common.constant.AmqpConsts;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* AMQP 消息管理类
*
* @author xiongxiaoyang
* @date 2022/5/25
*/
@Component
@RequiredArgsConstructor
public class AmqpMsgManager {
private final AmqpTemplate amqpTemplate;
@Value("${spring.amqp.enabled:false}")
private boolean amqpEnabled;
/**
* 发送小说信息改变消息
*/
public void sendBookChangeMsg(Long bookId) {
if (amqpEnabled) {
sendAmqpMessage(amqpTemplate, AmqpConsts.BookChangeMq.EXCHANGE_NAME, null, bookId);
}
}
private void sendAmqpMessage(AmqpTemplate amqpTemplate, String exchange, String routingKey,
Object message) {
// 如果在事务中则在事务执行完成后再发送,否则可以直接发送
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
amqpTemplate.convertAndSend(exchange, routingKey, message);
}
});
return;
}
amqpTemplate.convertAndSend(exchange, routingKey, message);
}
}

View File

@@ -0,0 +1,192 @@
package io.github.xxyopen.novel.book.service;
import io.github.xxyopen.novel.book.dto.req.*;
import io.github.xxyopen.novel.book.dto.resp.*;
import io.github.xxyopen.novel.common.resp.PageRespDto;
import io.github.xxyopen.novel.common.resp.RestResp;
import java.security.NoSuchAlgorithmException;
import java.util.List;
/**
* 小说模块 服务类
*
* @author xiongxiaoyang
* @date 2022/5/14
*/
public interface BookService {
/**
* 小说点击榜查询
*
* @return 小说点击排行列表
*/
RestResp<List<BookRankRespDto>> listVisitRankBooks();
/**
* 小说新书榜查询
*
* @return 小说新书排行列表
*/
RestResp<List<BookRankRespDto>> listNewestRankBooks();
/**
* 小说更新榜查询
*
* @return 小说更新排行列表
*/
RestResp<List<BookRankRespDto>> listUpdateRankBooks();
/**
* 小说信息查询
*
* @param bookId 小说ID
* @return 小说信息
*/
RestResp<BookInfoRespDto> getBookById(Long bookId);
/**
* 小说内容相关信息查询
*
* @param chapterId 章节ID
* @return 内容相关联的信息
*/
RestResp<BookContentAboutRespDto> getBookContentAbout(Long chapterId);
/**
* 小说最新章节相关信息查询
*
* @param bookId 小说ID
* @return 章节相关联的信息
*/
RestResp<BookChapterAboutRespDto> getLastChapterAbout(Long bookId);
/**
* 小说推荐列表查询
*
* @param bookId 小说ID
* @return 小说信息列表
*/
RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId) throws NoSuchAlgorithmException;
/**
* 增加小说点击量
*
* @param bookId 小说ID
* @return 成功状态
*/
RestResp<Void> addVisitCount(Long bookId);
/**
* 获取上一章节ID
*
* @param chapterId 章节ID
* @return 上一章节ID
*/
RestResp<Long> getPreChapterId(Long chapterId);
/**
* 获取下一章节ID
*
* @param chapterId 章节ID
* @return 下一章节ID
*/
RestResp<Long> getNextChapterId(Long chapterId);
/**
* 小说章节列表查询
*
* @param bookId 小说ID
* @return 小说章节列表
*/
RestResp<List<BookChapterRespDto>> listChapters(Long bookId);
/**
* 小说分类列表查询
*
* @param workDirection 作品方向;0-男频 1-女频
* @return 分类列表
*/
RestResp<List<BookCategoryRespDto>> listCategory(Integer workDirection);
/**
* 发表评论
*
* @param dto 评论相关 DTO
* @return void
*/
RestResp<Void> saveComment(BookCommentReqDto dto);
/**
* 小说最新评论查询
*
* @param bookId 小说ID
* @return 小说最新评论数据
*/
RestResp<BookCommentRespDto> listNewestComments(Long bookId);
/**
* 删除评论
*
* @param dto 评论相关 DTO
* @return void
*/
RestResp<Void> deleteComment(BookCommentReqDto dto);
/**
* 修改评论
*
* @param dto 评论相关 DTO
* @return void
*/
RestResp<Void> updateComment(BookCommentReqDto dto);
/**
* 小说信息保存
*
* @param dto 小说信息
* @return void
*/
RestResp<Void> saveBook(BookAddReqDto dto);
/**
* 小说章节信息保存
*
* @param dto 章节信息
* @return void
*/
RestResp<Void> saveBookChapter(ChapterAddReqDto dto);
/**
* 查询作家发布小说列表
*
* @param dto 分页请求参数
* @return 小说分页列表数据
*/
RestResp<PageRespDto<BookInfoRespDto>> listAuthorBooks(BookPageReqDto dto);
/**
* 查询小说发布章节列表
*
* @param dto 分页请求参数
* @return 章节分页列表数据
*/
RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(ChapterPageReqDto dto);
/**
* 查询下一批保存到 ES 中的小说列表
*
* @param maxBookId 已查询的最大小说ID
* @return 小说列表
*/
RestResp<List<BookEsRespDto>> listNextEsBooks(Long maxBookId);
/**
* 批量查询小说信息
*
* @param bookIds 小说ID列表
* @return 小说信息列表
*/
RestResp<List<BookInfoRespDto>> listBookInfoByIds(List<Long> bookIds);
}

View File

@@ -0,0 +1,472 @@
package io.github.xxyopen.novel.book.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.xxyopen.novel.book.dao.entity.BookChapter;
import io.github.xxyopen.novel.book.dao.entity.BookComment;
import io.github.xxyopen.novel.book.dao.entity.BookContent;
import io.github.xxyopen.novel.book.dao.entity.BookInfo;
import io.github.xxyopen.novel.book.dao.mapper.BookChapterMapper;
import io.github.xxyopen.novel.book.dao.mapper.BookCommentMapper;
import io.github.xxyopen.novel.book.dao.mapper.BookContentMapper;
import io.github.xxyopen.novel.book.dao.mapper.BookInfoMapper;
import io.github.xxyopen.novel.book.dto.req.*;
import io.github.xxyopen.novel.book.dto.resp.*;
import io.github.xxyopen.novel.book.manager.cache.*;
import io.github.xxyopen.novel.book.manager.feign.UserFeignManager;
import io.github.xxyopen.novel.book.manager.mq.AmqpMsgManager;
import io.github.xxyopen.novel.book.service.BookService;
import io.github.xxyopen.novel.common.auth.UserHolder;
import io.github.xxyopen.novel.common.constant.DatabaseConsts;
import io.github.xxyopen.novel.common.constant.ErrorCodeEnum;
import io.github.xxyopen.novel.common.resp.PageRespDto;
import io.github.xxyopen.novel.common.resp.RestResp;
import io.github.xxyopen.novel.config.annotation.Key;
import io.github.xxyopen.novel.config.annotation.Lock;
import io.github.xxyopen.novel.user.dto.resp.UserInfoRespDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 小说模块 服务实现类
*
* @author xiongxiaoyang
* @date 2022/5/14
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class BookServiceImpl implements BookService {
private final BookCategoryCacheManager bookCategoryCacheManager;
private final BookRankCacheManager bookRankCacheManager;
private final BookInfoCacheManager bookInfoCacheManager;
private final BookChapterCacheManager bookChapterCacheManager;
private final BookContentCacheManager bookContentCacheManager;
private final BookInfoMapper bookInfoMapper;
private final BookChapterMapper bookChapterMapper;
private final BookContentMapper bookContentMapper;
private final BookCommentMapper bookCommentMapper;
private final AmqpMsgManager amqpMsgManager;
private final UserFeignManager userFeignManager;
private static final Integer REC_BOOK_COUNT = 4;
@Override
public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
return RestResp.ok(bookRankCacheManager.listVisitRankBooks());
}
@Override
public RestResp<List<BookRankRespDto>> listNewestRankBooks() {
return RestResp.ok(bookRankCacheManager.listNewestRankBooks());
}
@Override
public RestResp<List<BookRankRespDto>> listUpdateRankBooks() {
return RestResp.ok(bookRankCacheManager.listUpdateRankBooks());
}
@Override
public RestResp<BookInfoRespDto> getBookById(Long bookId) {
return RestResp.ok(bookInfoCacheManager.getBookInfo(bookId));
}
@Override
public RestResp<BookChapterAboutRespDto> getLastChapterAbout(Long bookId) {
// 查询小说信息
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookId);
// 查询最新章节信息
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(
bookInfo.getLastChapterId());
// 查询章节内容
String content = bookContentCacheManager.getBookContent(bookInfo.getLastChapterId());
// 查询章节总数
QueryWrapper<BookChapter> chapterQueryWrapper = new QueryWrapper<>();
chapterQueryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId);
Long chapterTotal = bookChapterMapper.selectCount(chapterQueryWrapper);
// 组装数据并返回
return RestResp.ok(BookChapterAboutRespDto.builder()
.chapterInfo(bookChapter)
.chapterTotal(chapterTotal)
.contentSummary(content.substring(0, 30))
.build());
}
@Override
public RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId)
throws NoSuchAlgorithmException {
Long categoryId = bookInfoCacheManager.getBookInfo(bookId).getCategoryId();
List<Long> lastUpdateIdList = bookInfoCacheManager.getLastUpdateIdList(categoryId);
List<BookInfoRespDto> respDtoList = new ArrayList<>();
List<Integer> recIdIndexList = new ArrayList<>();
int count = 0;
Random rand = SecureRandom.getInstanceStrong();
while (count < REC_BOOK_COUNT) {
int recIdIndex = rand.nextInt(lastUpdateIdList.size());
if (!recIdIndexList.contains(recIdIndex)) {
recIdIndexList.add(recIdIndex);
bookId = lastUpdateIdList.get(recIdIndex);
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookId);
respDtoList.add(bookInfo);
count++;
}
}
return RestResp.ok(respDtoList);
}
@Override
public RestResp<Void> addVisitCount(Long bookId) {
bookInfoMapper.addVisitCount(bookId);
return RestResp.ok();
}
@Override
public RestResp<Long> getPreChapterId(Long chapterId) {
// 查询小说ID 和 章节号
BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
Long bookId = chapter.getBookId();
Integer chapterNum = chapter.getChapterNum();
// 查询上一章信息并返回章节ID
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
.lt(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM, chapterNum)
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
return RestResp.ok(
Optional.ofNullable(bookChapterMapper.selectOne(queryWrapper))
.map(BookChapter::getId)
.orElse(null)
);
}
@Override
public RestResp<Long> getNextChapterId(Long chapterId) {
// 查询小说ID 和 章节号
BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
Long bookId = chapter.getBookId();
Integer chapterNum = chapter.getChapterNum();
// 查询下一章信息并返回章节ID
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
.gt(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM, chapterNum)
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
return RestResp.ok(
Optional.ofNullable(bookChapterMapper.selectOne(queryWrapper))
.map(BookChapter::getId)
.orElse(null)
);
}
@Override
public RestResp<List<BookChapterRespDto>> listChapters(Long bookId) {
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
return RestResp.ok(bookChapterMapper.selectList(queryWrapper).stream()
.map(v -> BookChapterRespDto.builder()
.id(v.getId())
.chapterName(v.getChapterName())
.isVip(v.getIsVip())
.build()).toList());
}
@Override
public RestResp<List<BookCategoryRespDto>> listCategory(Integer workDirection) {
return RestResp.ok(bookCategoryCacheManager.listCategory(workDirection));
}
@Lock(prefix = "userComment")
@Override
public RestResp<Void> saveComment(
@Key(expr = "#{userId + '::' + bookId}") BookCommentReqDto dto) {
// 校验用户是否已发表评论
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, dto.getUserId())
.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, dto.getBookId());
if (bookCommentMapper.selectCount(queryWrapper) > 0) {
// 用户已发表评论
return RestResp.fail(ErrorCodeEnum.USER_COMMENTED);
}
BookComment bookComment = new BookComment();
bookComment.setBookId(dto.getBookId());
bookComment.setUserId(dto.getUserId());
bookComment.setCommentContent(dto.getCommentContent());
bookComment.setCreateTime(LocalDateTime.now());
bookComment.setUpdateTime(LocalDateTime.now());
bookCommentMapper.insert(bookComment);
return RestResp.ok();
}
@Override
public RestResp<BookCommentRespDto> listNewestComments(Long bookId) {
// 查询评论总数
QueryWrapper<BookComment> commentCountQueryWrapper = new QueryWrapper<>();
commentCountQueryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, bookId);
Long commentTotal = bookCommentMapper.selectCount(commentCountQueryWrapper);
BookCommentRespDto bookCommentRespDto = BookCommentRespDto.builder()
.commentTotal(commentTotal).build();
if (commentTotal > 0) {
// 查询最新的评论列表
QueryWrapper<BookComment> commentQueryWrapper = new QueryWrapper<>();
commentQueryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, bookId)
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName())
.last(DatabaseConsts.SqlEnum.LIMIT_5.getSql());
List<BookComment> bookComments = bookCommentMapper.selectList(commentQueryWrapper);
// 查询评论用户信息,并设置需要返回的评论用户名
List<Long> userIds = bookComments.stream().map(BookComment::getUserId).toList();
List<UserInfoRespDto> userInfos = userFeignManager.listUserInfoByIds(userIds);
Map<Long, UserInfoRespDto> userInfoMap = userInfos.stream()
.collect(Collectors.toMap(UserInfoRespDto::getId, Function.identity()));
List<BookCommentRespDto.CommentInfo> commentInfos = bookComments.stream()
.map(v -> BookCommentRespDto.CommentInfo.builder()
.id(v.getId())
.commentUserId(v.getUserId())
.commentUser(userInfoMap.get(v.getUserId()).getUsername())
.commentUserPhoto(userInfoMap.get(v.getUserId()).getUserPhoto())
.commentContent(v.getCommentContent())
.commentTime(v.getCreateTime()).build()).toList();
bookCommentRespDto.setComments(commentInfos);
} else {
bookCommentRespDto.setComments(Collections.emptyList());
}
return RestResp.ok(bookCommentRespDto);
}
@Override
public RestResp<Void> deleteComment(BookCommentReqDto dto) {
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), dto.getCommentId())
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, dto.getUserId());
bookCommentMapper.delete(queryWrapper);
return RestResp.ok();
}
@Override
public RestResp<Void> updateComment(BookCommentReqDto dto) {
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), dto.getCommentId())
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, dto.getUserId());
BookComment bookComment = new BookComment();
bookComment.setCommentContent(dto.getCommentContent());
bookCommentMapper.update(bookComment, queryWrapper);
return RestResp.ok();
}
@Override
public RestResp<Void> saveBook(BookAddReqDto dto) {
// 校验小说名是否已存在
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookTable.COLUMN_BOOK_NAME, dto.getBookName());
if (bookInfoMapper.selectCount(queryWrapper) > 0) {
return RestResp.fail(ErrorCodeEnum.AUTHOR_BOOK_NAME_EXIST);
}
BookInfo bookInfo = new BookInfo();
// 设置作家信息
bookInfo.setAuthorId(dto.getAuthorId());
bookInfo.setAuthorName(dto.getPenName());
// 设置其他信息
bookInfo.setWorkDirection(dto.getWorkDirection());
bookInfo.setCategoryId(dto.getCategoryId());
bookInfo.setCategoryName(dto.getCategoryName());
bookInfo.setBookName(dto.getBookName());
bookInfo.setPicUrl(dto.getPicUrl());
bookInfo.setBookDesc(dto.getBookDesc());
bookInfo.setIsVip(dto.getIsVip());
bookInfo.setScore(0);
bookInfo.setCreateTime(LocalDateTime.now());
bookInfo.setUpdateTime(LocalDateTime.now());
// 保存小说信息
bookInfoMapper.insert(bookInfo);
return RestResp.ok();
}
@Transactional(rollbackFor = Exception.class)
@Override
public RestResp<Void> saveBookChapter(ChapterAddReqDto dto) {
// 校验该作品是否属于当前作家
BookInfo bookInfo = bookInfoMapper.selectById(dto.getBookId());
if (!Objects.equals(bookInfo.getAuthorId(), dto.getAuthorId())) {
return RestResp.fail(ErrorCodeEnum.USER_UN_AUTH);
}
// 1) 保存章节相关信息到小说章节表
// a) 查询最新章节号
int chapterNum = 0;
QueryWrapper<BookChapter> chapterQueryWrapper = new QueryWrapper<>();
chapterQueryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, dto.getBookId())
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
BookChapter bookChapter = bookChapterMapper.selectOne(chapterQueryWrapper);
if (Objects.nonNull(bookChapter)) {
chapterNum = bookChapter.getChapterNum() + 1;
}
// b) 设置章节相关信息并保存
BookChapter newBookChapter = new BookChapter();
newBookChapter.setBookId(dto.getBookId());
newBookChapter.setChapterName(dto.getChapterName());
newBookChapter.setChapterNum(chapterNum);
newBookChapter.setWordCount(dto.getChapterContent().length());
newBookChapter.setIsVip(dto.getIsVip());
newBookChapter.setCreateTime(LocalDateTime.now());
newBookChapter.setUpdateTime(LocalDateTime.now());
bookChapterMapper.insert(newBookChapter);
// 2) 保存章节内容到小说内容表
BookContent bookContent = new BookContent();
bookContent.setContent(dto.getChapterContent());
bookContent.setChapterId(newBookChapter.getId());
bookContent.setCreateTime(LocalDateTime.now());
bookContent.setUpdateTime(LocalDateTime.now());
bookContentMapper.insert(bookContent);
// 3) 更新小说表最新章节信息和小说总字数信息
// a) 更新小说表关于最新章节的信息
BookInfo newBookInfo = new BookInfo();
newBookInfo.setId(dto.getBookId());
newBookInfo.setLastChapterId(newBookChapter.getId());
newBookInfo.setLastChapterName(newBookChapter.getChapterName());
newBookInfo.setLastChapterUpdateTime(LocalDateTime.now());
newBookInfo.setWordCount(bookInfo.getWordCount() + newBookChapter.getWordCount());
newBookChapter.setUpdateTime(LocalDateTime.now());
bookInfoMapper.updateById(newBookInfo);
// b) 清除小说信息缓存
bookInfoCacheManager.evictBookInfoCache(dto.getBookId());
// c) 发送小说信息更新的 MQ 消息
amqpMsgManager.sendBookChangeMsg(dto.getBookId());
return RestResp.ok();
}
@Override
public RestResp<PageRespDto<BookInfoRespDto>> listAuthorBooks(BookPageReqDto dto) {
IPage<BookInfo> page = new Page<>();
page.setCurrent(dto.getPageNum());
page.setSize(dto.getPageSize());
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookTable.AUTHOR_ID, dto.getAuthorId())
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
IPage<BookInfo> bookInfoPage = bookInfoMapper.selectPage(page, queryWrapper);
return RestResp.ok(PageRespDto.of(dto.getPageNum(), dto.getPageSize(), page.getTotal(),
bookInfoPage.getRecords().stream().map(v -> BookInfoRespDto.builder()
.id(v.getId())
.bookName(v.getBookName())
.picUrl(v.getPicUrl())
.categoryName(v.getCategoryName())
.wordCount(v.getWordCount())
.visitCount(v.getVisitCount())
.updateTime(v.getUpdateTime())
.build()).toList()));
}
@Override
public RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(ChapterPageReqDto dto) {
IPage<BookChapter> page = new Page<>();
page.setCurrent(dto.getPageNum());
page.setSize(dto.getPageSize());
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, dto.getBookId())
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
IPage<BookChapter> bookChapterPage = bookChapterMapper.selectPage(page, queryWrapper);
return RestResp.ok(PageRespDto.of(dto.getPageNum(), dto.getPageSize(), page.getTotal(),
bookChapterPage.getRecords().stream().map(v -> BookChapterRespDto.builder()
.id(v.getId())
.chapterName(v.getChapterName())
.chapterUpdateTime(v.getUpdateTime())
.isVip(v.getIsVip())
.build()).toList()));
}
@Override
public RestResp<List<BookEsRespDto>> listNextEsBooks(Long maxBookId) {
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.clear();
queryWrapper
.orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
.gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxBookId)
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
return RestResp.ok(bookInfoMapper.selectList(queryWrapper).stream().map(bookInfo -> BookEsRespDto.builder()
.id(bookInfo.getId())
.categoryId(bookInfo.getCategoryId())
.categoryName(bookInfo.getCategoryName())
.bookDesc(bookInfo.getBookDesc())
.bookName(bookInfo.getBookName())
.authorId(bookInfo.getAuthorId())
.authorName(bookInfo.getAuthorName())
.bookStatus(bookInfo.getBookStatus())
.commentCount(bookInfo.getCommentCount())
.isVip(bookInfo.getIsVip())
.score(bookInfo.getScore())
.visitCount(bookInfo.getVisitCount())
.wordCount(bookInfo.getWordCount())
.workDirection(bookInfo.getWorkDirection())
.lastChapterId(bookInfo.getLastChapterId())
.lastChapterName(bookInfo.getLastChapterName())
.lastChapterUpdateTime(bookInfo.getLastChapterUpdateTime()
.toInstant(ZoneOffset.ofHours(8)).toEpochMilli())
.build()).collect(Collectors.toList()));
}
@Override
public RestResp<List<BookInfoRespDto>> listBookInfoByIds(List<Long> bookIds) {
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(), bookIds);
return RestResp.ok(
bookInfoMapper.selectList(queryWrapper).stream().map(v -> BookInfoRespDto.builder()
.id(v.getId())
.bookName(v.getBookName())
.authorName(v.getAuthorName())
.picUrl(v.getPicUrl())
.bookDesc(v.getBookDesc())
.build()).collect(Collectors.toList()));
}
@Override
public RestResp<BookContentAboutRespDto> getBookContentAbout(Long chapterId) {
log.debug("userId:{}", UserHolder.getUserId());
// 查询章节信息
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(chapterId);
// 查询章节内容
String content = bookContentCacheManager.getBookContent(chapterId);
// 查询小说信息
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookChapter.getBookId());
// 组装数据并返回
return RestResp.ok(BookContentAboutRespDto.builder()
.bookInfo(bookInfo)
.chapterInfo(bookChapter)
.bookContent(content)
.build());
}
}

View File

@@ -0,0 +1,15 @@
server:
port: 9020
spring:
profiles:
include: common
active: dev
management:
# 端点启用配置
endpoint:
logfile:
# 启用返回日志文件内容的端点
enabled: true
# 外部日志文件路径
external-file: logs/novel-book-service.log

View File

@@ -0,0 +1,6 @@
spring:
application:
name: novel-book-service
profiles:
include: common

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,%i索引【从数字0开始递增】,,, -->
<!-- appender是configuration的子节点,是负责写日志的组件。 -->
<!-- ConsoleAppender:把日志输出到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是demo.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过1KB时,对当前日志进行分割 重命名 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>logs/novel-book-service.log</File>
<!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 -->
<!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:logs/demo.2017-12-05.0.log -->
<fileNamePattern>logs/debug.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成1KB看效果 -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!-- pattern节点,用来设置日志的输入格式 -->
<pattern>
%d %p (%file:%line\)- %m%n
</pattern>
<!-- 记录日志的编码:此处设置字符集 - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<springProfile name="dev">
<!-- ROOT 日志级别 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
<!-- com.maijinjie.springboot 为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="io.github.xxyopen" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</logger>
</springProfile>
<springProfile name="prod">
<!-- ROOT 日志级别 -->
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
<!-- com.maijinjie.springboot 为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="io.github.xxyopen" level="ERROR" additivity="false">
<appender-ref ref="FILE"/>
</logger>
</springProfile>
</configuration>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.github.xxyopen.novel.book.dao.mapper.BookInfoMapper">
<select id="searchBooks" resultType="io.github.xxyopen.novel.book.dao.entity.BookInfo">
select
id,category_id,category_name,book_name,author_id,author_name,word_count,last_chapter_name
from book_info where word_count > 0
<if test="condition.keyword != null and condition.keyword != ''">
and (book_name like concat('%',#{condition.keyword},'%') or author_name like
concat('%',#{condition.keyword},'%'))
</if>
<if test="condition.workDirection != null">
and work_direction = #{condition.workDirection}
</if>
<if test="condition.categoryId != null">
and category_id = #{condition.categoryId}
</if>
<if test="condition.isVip != null">
and is_vip = #{condition.isVip}
</if>
<if test="condition.bookStatus != null">
and book_status = #{condition.bookStatus}
</if>
<if test="condition.wordCountMin != null">
and word_count >= #{condition.wordCountMin}
</if>
<if test="condition.wordCountMax != null">
and word_count <![CDATA[ < ]]> #{condition.wordCountMax}
</if>
<if test="condition.updateTimeMin != null">
and last_chapter_update_time >= #{condition.updateTimeMin}
</if>
<if test="condition.sort != null">
order by ${condition.sort}
</if>
</select>
<update id="addVisitCount">
update book_info
set visit_count = visit_count + 1
where id = #{bookId}
</update>
</mapper>