From d45dc3d015b4e409eca23037cfbcf8b4ba1a65ca Mon Sep 17 00:00:00 2001 From: xiongxiaoyang <773861846@qq.com> Date: Mon, 23 May 2022 21:57:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90=20Elasticsearch=208.?= =?UTF-8?q?2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- doc/es/book.http | 62 ++++ {sql => doc/sql}/novel.sql.zip | Bin pom.xml | 344 +++++++++--------- .../xxyopen/novel/NovelApplication.java | 2 + .../xxyopen/novel/core/config/EsConfig.java | 35 ++ .../xxyopen/novel/core/constant/EsConsts.java | 28 ++ .../xxyopen/novel/core/task/BookToEsTask.java | 109 ++++++ .../xxyopen/novel/dto/es/EsBookDto.java | 108 ++++++ src/main/resources/application.yml | 8 +- 10 files changed, 532 insertions(+), 166 deletions(-) create mode 100644 doc/es/book.http rename {sql => doc/sql}/novel.sql.zip (100%) create mode 100644 src/main/java/io/github/xxyopen/novel/core/config/EsConfig.java create mode 100644 src/main/java/io/github/xxyopen/novel/core/constant/EsConsts.java create mode 100644 src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java create mode 100644 src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java diff --git a/README.md b/README.md index 3f59a6b..4617b6c 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ git clone https://gitee.com/novel_dev_team/novel.git 1. 新建数据库(建议 novel) - 2. 解压后端源码`sql/novel.sql.zip`压缩包,得到数据库结构文件`novel_struc.sql`和数据库小说数据文件`novel_data.sql` + 2. 解压后端源码`doc/sql/novel.sql.zip`压缩包,得到数据库结构文件`novel_struc.sql`和数据库小说数据文件`novel_data.sql` 3. 导入`novel_struct.sql`数据库结构文件 diff --git a/doc/es/book.http b/doc/es/book.http new file mode 100644 index 0000000..e80fd3d --- /dev/null +++ b/doc/es/book.http @@ -0,0 +1,62 @@ +PUT /book +{ + "mappings" : { + "properties" : { + "id" : { + "type" : "long" + }, + "authorId" : { + "type" : "long" + }, + "authorName" : { + "type" : "text", + "analyzer": "ik_smart" + }, + "bookName" : { + "type" : "text", + "analyzer": "ik_smart" + }, + "bookDesc" : { + "type" : "text", + "analyzer": "ik_smart" + }, + "bookStatus" : { + "type" : "short" + }, + "categoryId" : { + "type" : "integer" + }, + "categoryName" : { + "type" : "text", + "analyzer": "ik_smart" + }, + "lastChapterId" : { + "type" : "long" + }, + "lastChapterName" : { + "type" : "text", + "analyzer": "ik_smart" + }, + "lastChapterUpdateTime" : { + "type": "keyword" + }, + "picUrl" : { + "type" : "keyword", + "index" : false, + "doc_values" : false + }, + "score" : { + "type" : "integer" + }, + "wordCount" : { + "type" : "integer" + }, + "workDirection" : { + "type" : "short" + }, + "visitCount" : { + "type": "long" + } + } + } +} \ No newline at end of file diff --git a/sql/novel.sql.zip b/doc/sql/novel.sql.zip similarity index 100% rename from sql/novel.sql.zip rename to doc/sql/novel.sql.zip diff --git a/pom.xml b/pom.xml index 608939e..347f5e1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,176 +1,192 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.0.0-SNAPSHOT - - - io.github.xxyopen - novel - 3.0.0 - novel - Spring Boot 3 + Vue 3 构建的前后端分离小说系统 - - 17 - 3.5.1 - 6.0.0-SNAPSHOT - 0.11.5 - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-tomcat - - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.0-SNAPSHOT + + + io.github.xxyopen + novel + 3.0.0 + novel + Spring Boot 3 + Vue 3 构建的前后端分离小说系统 + + 17 + 3.5.1 + 6.0.0-SNAPSHOT + 0.11.5 + 8.2.0 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-tomcat + + + - - - org.springframework.boot - spring-boot-starter-undertow - + + + org.springframework.boot + spring-boot-starter-undertow + - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus.version} - + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + - - - com.baomidou - mybatis-plus-generator - ${mybatis-plus.version} - test - - - org.apache.velocity - velocity-engine-core - 2.3 - test - + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + test + + + org.apache.velocity + velocity-engine-core + 2.3 + test + - - - org.springframework.boot - spring-boot-starter-cache - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.github.ben-manes.caffeine - caffeine - + + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.github.ben-manes.caffeine + caffeine + - - - io.jsonwebtoken - jjwt-api - ${jjwt.version} - - - io.jsonwebtoken - jjwt-impl - ${jjwt.version} - runtime - - - io.jsonwebtoken - jjwt-jackson - ${jjwt.version} - runtime - + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + - - - org.hibernate.validator - hibernate-validator - + + + org.hibernate.validator + hibernate-validator + - - mysql - mysql-connector-java - runtime - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-test - test - - + + + co.elastic.clients + elasticsearch-java + ${elasticsearch.version} + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + diff --git a/src/main/java/io/github/xxyopen/novel/NovelApplication.java b/src/main/java/io/github/xxyopen/novel/NovelApplication.java index 0de7b9c..5f5d082 100644 --- a/src/main/java/io/github/xxyopen/novel/NovelApplication.java +++ b/src/main/java/io/github/xxyopen/novel/NovelApplication.java @@ -9,12 +9,14 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableScheduling; import java.util.Map; @SpringBootApplication @MapperScan("io.github.xxyopen.novel.dao.mapper") @EnableCaching +@EnableScheduling @Slf4j public class NovelApplication { diff --git a/src/main/java/io/github/xxyopen/novel/core/config/EsConfig.java b/src/main/java/io/github/xxyopen/novel/core/config/EsConfig.java new file mode 100644 index 0000000..0603fe4 --- /dev/null +++ b/src/main/java/io/github/xxyopen/novel/core/config/EsConfig.java @@ -0,0 +1,35 @@ +package io.github.xxyopen.novel.core.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import lombok.RequiredArgsConstructor; +import org.elasticsearch.client.RestClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * elasticsearch 相关配置 + * + * @author xiongxiaoyang + * @date 2022/5/23 + */ +@Configuration +@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true") +@RequiredArgsConstructor +public class EsConfig { + + @Bean + public ElasticsearchClient elasticsearchClient(RestClient restClient) { + + // Create the transport with a Jackson mapper + ElasticsearchTransport transport = new RestClientTransport( + restClient, new JacksonJsonpMapper()); + + // And create the API client + return new ElasticsearchClient(transport); + } + +} diff --git a/src/main/java/io/github/xxyopen/novel/core/constant/EsConsts.java b/src/main/java/io/github/xxyopen/novel/core/constant/EsConsts.java new file mode 100644 index 0000000..d4b9a26 --- /dev/null +++ b/src/main/java/io/github/xxyopen/novel/core/constant/EsConsts.java @@ -0,0 +1,28 @@ +package io.github.xxyopen.novel.core.constant; + +import lombok.Getter; + +/** + * elasticsearch 相关常量 + * + * @author xiongxiaoyang + * @date 2022/5/23 + */ +public class EsConsts { + + /** + * ES 索引枚举类 + */ + @Getter + public enum IndexEnum { + + BOOK("book"); + + IndexEnum(String name) { + this.name = name; + } + + private String name; + + } +} diff --git a/src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java b/src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java new file mode 100644 index 0000000..3c9be46 --- /dev/null +++ b/src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java @@ -0,0 +1,109 @@ +package io.github.xxyopen.novel.core.task; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.BulkRequest; +import co.elastic.clients.elasticsearch.core.BulkResponse; +import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import io.github.xxyopen.novel.core.constant.DatabaseConsts; +import io.github.xxyopen.novel.core.constant.EsConsts; +import io.github.xxyopen.novel.dao.entity.BookInfo; +import io.github.xxyopen.novel.dao.mapper.BookInfoMapper; +import io.github.xxyopen.novel.dto.es.EsBookDto; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 小说数据同步到 elasticsearch 任务 + * + * @author xiongxiaoyang + * @date 2022/5/23 + */ +@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true") +@Component +@RequiredArgsConstructor +@Slf4j +public class BookToEsTask { + + private final BookInfoMapper bookInfoMapper; + + private final ElasticsearchClient elasticsearchClient; + + /** + * 每月凌晨做一次全量数据同步 + */ + @SneakyThrows + @Scheduled(cron = "0 0 0 1 * ?") + public void saveToEs() { + + QueryWrapper queryWrapper = new QueryWrapper<>(); + List bookInfos; + long maxId = 0; + for(;;) { + queryWrapper.clear(); + queryWrapper + .orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName()) + .gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId) + .last(DatabaseConsts.SqlEnum.LIMIT_30.getSql()); + bookInfos = bookInfoMapper.selectList(queryWrapper); + if (bookInfos.isEmpty()) { + break; + } + BulkRequest.Builder br = new BulkRequest.Builder(); + + for (BookInfo book : bookInfos) { + EsBookDto esBook = buildEsBook(book); + br.operations(op -> op + .index(idx -> idx + .index(EsConsts.IndexEnum.BOOK.getName()) + .id(book.getId().toString()) + .document(esBook) + ) + ); + maxId = book.getId(); + } + + BulkResponse result = elasticsearchClient.bulk(br.build()); + + // Log errors, if any + if (result.errors()) { + log.error("Bulk had errors"); + for (BulkResponseItem item : result.items()) { + if (item.error() != null) { + log.error(item.error().reason()); + } + } + } + + } + + } + + private EsBookDto buildEsBook(BookInfo book) { + return EsBookDto.builder() + .id(book.getId()) + .categoryId(book.getCategoryId()) + .categoryName(book.getCategoryName()) + .bookDesc(book.getBookDesc()) + .bookName(book.getBookName()) + .authorId(book.getAuthorId()) + .authorName(book.getAuthorName()) + .bookStatus(book.getBookStatus()) + .commentCount(book.getCommentCount()) + .isVip(book.getIsVip()) + .score(book.getScore()) + .visitCount(book.getVisitCount()) + .wordCount(book.getWordCount()) + .workDirection(book.getWorkDirection()) + .lastChapterId(book.getLastChapterId()) + .lastChapterName(book.getLastChapterName()) + .lastChapterUpdateTime(book.getLastChapterUpdateTime()) + .build(); + } +} diff --git a/src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java b/src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java new file mode 100644 index 0000000..7d77fea --- /dev/null +++ b/src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java @@ -0,0 +1,108 @@ +package io.github.xxyopen.novel.dto.es; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * Elasticsearch 存储小说 DTO + * @author xiongxiaoyang + * @date 2022/5/23 + */ +@Data +@Builder +public class EsBookDto { + + /** + * id + */ + private Long id; + + /** + * 作品方向;0-男频 1-女频 + */ + private Integer workDirection; + + /** + * 类别ID + */ + private Long categoryId; + + /** + * 类别名 + */ + private String categoryName; + + /** + * 小说名 + */ + 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; + + /** + * 最新章节更新时间 + */ + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateTimeSerializer.class) + private LocalDateTime lastChapterUpdateTime; + + /** + * 是否收费;1-收费 0-免费 + */ + private Integer isVip; + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ed2103b..23df130 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,7 +41,13 @@ spring: config: activate: on-profile: dev - + elasticsearch: + # 是否开启 elasticsearch 搜索引擎功能:true-开启 false-不开启 + enable: false + uris: + - https://my-deployment-ce7ca3.es.us-central1.gcp.cloud.es.io:9243 + username: elastic + password: qTjgYVKSuExX6tWAsDuvuvwl --- spring: