项目简介
novel 是一套基于时下最新 Java 技术栈 Spring Boot 3 + Vue 3 开发的前后端分离的学习型小说项目,配备详细的项目教程手把手教你从零开始开发上线一个生产级别的 Java 系统,由小说门户系统、作家后台管理系统、平台后台管理系统等多个子系统构成。包括小说推荐、作品检索、小说排行榜、小说阅读、小说评论、会员中心、作家专区、充值订阅、新闻发布等功能。
项目地址
开发环境
- MySQL 8.0
- Redis 7.0
- Elasticsearch 8.2.0(可选)
- RabbitMQ 3.10.2(可选)
- JDK 17
- Maven 3.8
- IntelliJ IDEA 2021.3(可选)
- Node 16.14
后端技术选型
技术 | 版本 | 说明 |
---|---|---|
Spring Boot | 3.0.0-SNAPSHOT | 容器 + MVC 框架 |
Mybatis | 3.5.9 | ORM 框架 |
MyBatis-Plus | 3.5.1 | Mybatis 增强工具 |
JJWT | 0.11.5 | JWT 登录支持 |
Lombok | 1.18.24 | 简化对象封装工具 |
Caffeine | 3.1.0 | 本地缓存支持 |
Redis | 7.0 | 分布式缓存支持 |
MySQL | 8.0 | 数据库服务 |
Elasticsearch | 8.2.0 | 搜索引擎服务 |
RabbitMQ | 3.10.2 | 开源消息中间件 |
Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 |
Docker | - | 应用容器引擎 |
Jenkins | - | 自动化部署工具 |
Sonarqube | - | 代码质量控制 |
注:更多热门新技术待集成。
前端技术选型
技术 | 版本 | 说明 |
---|---|---|
Vue.js | 3.2.13 | 渐进式 JavaScript 框架 |
Vue Router | 4.0.15 | Vue.js 的官方路由 |
axios | 0.27.2 | 基于 promise 的网络请求库 |
element-plus | 2.2.0 | 基于 Vue 3,面向设计师和开发者的组件库 |
示例代码
代码严格遵守阿里编码规约。
/** * 小说搜索 */ @Override public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) { SearchResponse<EsBookDto> response = esClient.search(s -> { // 搜索构建器 SearchRequest.Builder searchBuilder = s.index(EsConsts.BookIndex.INDEX_NAME); // 构建搜索条件 buildSearchCondition(condition, searchBuilder); // 排序 if (!StringUtils.isBlank(condition.getSort())) { searchBuilder.sort(o -> o.field(f -> f.field(condition.getSort()).order(SortOrder.Desc)) ); } // 分页 searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize()) .size(condition.getPageSize()); return searchBuilder; }, EsBookDto.class ); TotalHits total = response.hits().total(); List<BookInfoRespDto> list = new ArrayList<>(); List<Hit<EsBookDto>> hits = response.hits().hits(); for (Hit<EsBookDto> hit : hits) { EsBookDto book = hit.source(); list.add(BookInfoRespDto.builder() .id(book.getId()) .bookName(book.getBookName()) .categoryId(book.getCategoryId()) .categoryName(book.getCategoryName()) .authorId(book.getAuthorId()) .authorName(book.getAuthorName()) .wordCount(book.getWordCount()) .lastChapterName(book.getLastChapterName()) .build()); } return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list)); } /** * 构建搜索条件 */ private void buildSearchCondition(BookSearchReqDto condition, SearchRequest.Builder searchBuilder) { BoolQuery boolQuery = BoolQuery.of(b -> { if (!StringUtils.isBlank(condition.getKeyword())) { // 关键词匹配 b.must((q -> q.multiMatch(t -> t .fields(EsConsts.BookIndex.FIELD_BOOK_NAME + "^2" , EsConsts.BookIndex.FIELD_AUTHOR_NAME + "^1.8" , EsConsts.BookIndex.FIELD_BOOK_DESC + "^0.1") .query(condition.getKeyword()) ) )); } // 精确查询 if (Objects.nonNull(condition.getWorkDirection())) { b.must(TermQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_WORK_DIRECTION) .value(condition.getWorkDirection()) )._toQuery()); } if (Objects.nonNull(condition.getCategoryId())) { b.must(TermQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_CATEGORY_ID) .value(condition.getCategoryId()) )._toQuery()); } // 范围查询 if (Objects.nonNull(condition.getWordCountMin())) { b.must(RangeQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_WORD_COUNT) .gte(JsonData.of(condition.getWordCountMin())) )._toQuery()); } if (Objects.nonNull(condition.getWordCountMax())) { b.must(RangeQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_WORD_COUNT) .lt(JsonData.of(condition.getWordCountMax())) )._toQuery()); } if (Objects.nonNull(condition.getUpdateTimeMin())) { b.must(RangeQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_LAST_CHAPTER_UPDATE_TIME) .gte(JsonData.of(condition.getUpdateTimeMin().getTime())) )._toQuery()); } return b; }); searchBuilder.query(q -> q.bool(boolQuery)); }