mirror of
https://github.com/201206030/novel.git
synced 2025-07-07 05:26:37 +00:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
d5e45e74c9 | |||
08237e11e9 | |||
78b366716a | |||
5d0ac13ae6 | |||
bf60ba30c9 | |||
5e995cd63e | |||
21f30df237 | |||
520faff51f | |||
320b985ce6 | |||
c4fabe2ca1 | |||
7f0d6c842a | |||
c628104a30 | |||
9894814fe4 | |||
d7a7580c4e | |||
9eb967402a | |||
220068cd3a | |||
4d71aa33b1 | |||
b6a07d3a0c | |||
121ec01fa2 | |||
ed882abbd1 | |||
785646b4c4 | |||
ad907063d9 | |||
f33c66c5d2 | |||
066dd0f13e | |||
fa47081398 | |||
1151ed3f9f | |||
d2cadda291 | |||
8a0105cfa4 | |||
3019093dc3 | |||
cdd99834a6 | |||
80e7264afa | |||
e537240c73 | |||
20a2d64cc1 | |||
b8950b5be2 | |||
3a1990fbbd | |||
e5592b85dd | |||
a8e2e2d5c9 | |||
23fa646cd6 | |||
471a24a330 | |||
774d3e3556 | |||
a056248398 | |||
ab2bead9b3 | |||
4c331224a4 | |||
d45dc3d015 | |||
61d261d277 | |||
45fdb58ab3 | |||
879673bca5 | |||
e3cf41fbd8 | |||
1b9627eddc | |||
5e01c1c5a1 | |||
61cc3b5f07 | |||
6b366348c0 | |||
282755f7ab | |||
c431683540 | |||
28a2717231 | |||
f0fc2d8e1e |
59
README.md
59
README.md
@ -11,39 +11,47 @@ novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开
|
|||||||
## 项目地址
|
## 项目地址
|
||||||
|
|
||||||
- 后端项目(更新中):[GitHub](https://github.com/201206030/novel) | [码云](https://gitee.com/novel_dev_team/novel)
|
- 后端项目(更新中):[GitHub](https://github.com/201206030/novel) | [码云](https://gitee.com/novel_dev_team/novel)
|
||||||
- 后端微服务版本项目(待更新):[GitHub](https://github.com/201206030/novel-cloud) | [码云](https://gitee.com/novel_dev_team/novel-cloud)
|
- 前端项目(更新中):[GitHub](https://github.com/201206030/novel-front-web) | [码云](https://gitee.com/novel_dev_team/novel-front-web)
|
||||||
- 前端小说门户系统项目(更新中):[GitHub](https://github.com/201206030/novel-front-web) | [码云](https://gitee.com/novel_dev_team/novel-front-web)
|
|
||||||
- 前端作家管理系统项目:待上线
|
|
||||||
- 前端平台后台管理系统项目:待上线
|
|
||||||
- 线上应用版:[GitHub](https://github.com/201206030/novel-plus) | [码云](https://gitee.com/novel_dev_team/novel-plus) |[演示地址](http://47.106.243.172:8888/)
|
- 线上应用版:[GitHub](https://github.com/201206030/novel-plus) | [码云](https://gitee.com/novel_dev_team/novel-plus) |[演示地址](http://47.106.243.172:8888/)
|
||||||
|
- 微服务版:[GitHub](https://github.com/201206030/novel-cloud) | [码云](https://gitee.com/novel_dev_team/novel-cloud)
|
||||||
|
|
||||||
## 开发环境
|
## 开发环境
|
||||||
|
|
||||||
- MySQL 8.0
|
- MySQL 8.0
|
||||||
- Redis 7.0
|
- Redis 7.0
|
||||||
|
- Elasticsearch 8.2.0(可选)
|
||||||
|
- RabbitMQ 3.10.2(可选)
|
||||||
|
- XXL-JOB 2.3.1(可选)
|
||||||
- JDK 17
|
- JDK 17
|
||||||
- Maven 3.8
|
- Maven 3.8
|
||||||
- IntelliJ IDEA 2021.3(可选)
|
- IntelliJ IDEA 2021.3(可选)
|
||||||
- Node 16.14
|
- Node 16.14
|
||||||
|
|
||||||
|
**注:Elasticsearch、RabbitMQ 和 XXL-JOB 默认关闭,可通过 application.yml 配置文件中相应的`enable`配置属性开启。**
|
||||||
|
|
||||||
## 后端技术选型
|
## 后端技术选型
|
||||||
|
|
||||||
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
||||||
| :----------------- | :-----: | -------------------------- | --------------------------------------- | :-------------------------------------------------: |
|
|:-------------------------------|:--------------:|---------------------| --------------------------------------- |:----------------------------------------------------------------------------------------:|
|
||||||
| Spring Boot | 3.0.0-SNAPSHOT | 容器 + MVC 框架 | https://spring.io/projects/spring-boot | [进入](https://youdoc.github.io/course/novel/11.html) |
|
| Spring Boot | 3.0.0-SNAPSHOT | 容器 + MVC 框架 | https://spring.io/projects/spring-boot | [进入](https://youdoc.github.io/course/novel/11.html) |
|
||||||
| Mybatis | 3.5.9 | ORM 框架 | http://www.mybatis.org | [进入](https://mybatis.org/mybatis-3/zh/index.html) |
|
| MyBatis | 3.5.9 | ORM 框架 | http://www.mybatis.org | [进入](https://mybatis.org/mybatis-3/zh/index.html) |
|
||||||
| MyBatis-Plus | 3.5.1 | Mybatis 增强工具 | https://baomidou.com/ | [进入](https://baomidou.com/pages/24112f/) |
|
| MyBatis-Plus | 3.5.1 | MyBatis 增强工具 | https://baomidou.com/ | [进入](https://baomidou.com/pages/24112f/) |
|
||||||
| JJWT | 0.11.5 | JWT 登录支持 | https://github.com/jwtk/jjwt | - |
|
| JJWT | 0.11.5 | JWT 登录支持 | https://github.com/jwtk/jjwt | - |
|
||||||
| Lombok | 1.18.24 | 简化对象封装工具 | https://github.com/projectlombok/lombok | [进入](https://projectlombok.org/features/all) |
|
| Lombok | 1.18.24 | 简化对象封装工具 | https://github.com/projectlombok/lombok | [进入](https://projectlombok.org/features/all) |
|
||||||
| Caffeine | 3.1.0 | 本地缓存支持 | https://github.com/ben-manes/caffeine | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
|
| Caffeine | 3.1.0 | 本地缓存支持 | https://github.com/ben-manes/caffeine | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
|
||||||
| Redis | 7.0 | 分布式缓存支持 | https://redis.io | [进入](https://redis.io/docs) |
|
| Redis | 7.0 | 分布式缓存支持 | https://redis.io | [进入](https://redis.io/docs) |
|
||||||
| MySQL | 8.0 | 数据库服务 | https://www.mysql.com | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
|
| MySQL | 8.0 | 数据库服务 | https://www.mysql.com | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
|
||||||
| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | https://undertow.io | [进入](https://undertow.io/documentation.html) |
|
| ShardingSphere-JDBC | 5.1.1 | 数据库分库分表支持 | https://shardingsphere.apache.org | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
|
||||||
| Docker | - | 应用容器引擎 | https://www.docker.com/ | - |
|
| Elasticsearch | 8.2.0 | 搜索引擎服务 | https://www.elastic.co | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
|
||||||
| Jenkins | - | 自动化部署工具 | https://github.com/jenkinsci/jenkins | - |
|
| RabbitMQ | 3.10.2 | 开源消息中间件 | https://www.rabbitmq.com | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
|
||||||
| Sonarqube | - | 代码质量控制 | https://www.sonarqube.org/ | - |
|
| XXL-JOB | 2.3.1 | 分布式任务调度平台 | https://www.xuxueli.com/xxl-job | [进入](https://www.xuxueli.com/xxl-job) |
|
||||||
|
| Sentinel | 1.8.4 | 流量控制组件 | https://github.com/alibaba/Sentinel | [进入](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5) |
|
||||||
|
| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | https://undertow.io | [进入](https://undertow.io/documentation.html) |
|
||||||
|
| Docker | - | 应用容器引擎 | https://www.docker.com/ | - |
|
||||||
|
| Jenkins | - | 自动化部署工具 | https://github.com/jenkinsci/jenkins | - |
|
||||||
|
| Sonarqube | - | 代码质量控制 | https://www.sonarqube.org/ | - |
|
||||||
|
|
||||||
<!--| SpringFox Swagger2 | ?(不支持 Spring 6) | Spring项目接口文档生成工具 | https://github.com/springfox/springfox | - | -->
|
**注:更多热门新技术待集成。**
|
||||||
## 前端技术选型
|
## 前端技术选型
|
||||||
|
|
||||||
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
||||||
@ -85,6 +93,7 @@ io
|
|||||||
| +- constant -- 业务相关常量
|
| +- constant -- 业务相关常量
|
||||||
| +- filter -- 过滤器
|
| +- filter -- 过滤器
|
||||||
| +- interceptor -- 拦截器
|
| +- interceptor -- 拦截器
|
||||||
|
| +- json -- JSON 相关的包,包括序列化器和反序列化器
|
||||||
| +- task -- 定时任务
|
| +- task -- 定时任务
|
||||||
| +- util -- 业务相关工具
|
| +- util -- 业务相关工具
|
||||||
| +- wrapper -- 装饰器
|
| +- wrapper -- 装饰器
|
||||||
@ -174,7 +183,7 @@ git clone https://gitee.com/novel_dev_team/novel.git
|
|||||||
|
|
||||||
1. 新建数据库(建议 novel)
|
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`数据库结构文件
|
3. 导入`novel_struct.sql`数据库结构文件
|
||||||
|
|
||||||
@ -244,11 +253,15 @@ git clone https://gitee.com/novel_dev_team/novel-front-web.git
|
|||||||
|
|
||||||
## 公众号
|
## 公众号
|
||||||
|
|
||||||
关注公众号接收项目最新动态,获取`Spring Boot 3`学习笔记!
|
- 关注公众号接收`项目`和`文档`的更新动态
|
||||||
|
|
||||||
加微信群交流,公众号后台回复「**微信群**」即可。
|
- 加微信群学习交流,公众号后台回复「**微信群**」即可
|
||||||
|
|
||||||

|
- 回复「**资料**」获取`Java 学习面试资料`
|
||||||
|
|
||||||
|
- 回复「**笔记**」获取`Spring Boot 3 学习笔记`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 赞赏支持
|
## 赞赏支持
|
||||||
|
|
||||||
|
62
doc/es/book.http
Normal file
62
doc/es/book.http
Normal file
@ -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": "long"
|
||||||
|
},
|
||||||
|
"picUrl" : {
|
||||||
|
"type" : "keyword",
|
||||||
|
"index" : false,
|
||||||
|
"doc_values" : false
|
||||||
|
},
|
||||||
|
"score" : {
|
||||||
|
"type" : "integer"
|
||||||
|
},
|
||||||
|
"wordCount" : {
|
||||||
|
"type" : "integer"
|
||||||
|
},
|
||||||
|
"workDirection" : {
|
||||||
|
"type" : "short"
|
||||||
|
},
|
||||||
|
"visitCount" : {
|
||||||
|
"type": "long"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
doc/sql/20220603.sql
Normal file
171
doc/sql/20220603.sql
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
DROP PROCEDURE IF EXISTS createBookChapterTable;
|
||||||
|
-- 创建小说章节表的存储过程
|
||||||
|
CREATE PROCEDURE createBookChapterTable()
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- 定义变量
|
||||||
|
DECLARE i int DEFAULT 0;
|
||||||
|
DECLARE tableName char(13) DEFAULT NULL;
|
||||||
|
|
||||||
|
while i < 10 do
|
||||||
|
|
||||||
|
set tableName = concat('book_chapter',i);
|
||||||
|
|
||||||
|
set @stmt = concat('create table ',tableName,'(
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`book_id` bigint(20) unsigned NOT NULL COMMENT \'小说ID\',
|
||||||
|
`chapter_num` smallint(5) unsigned NOT NULL COMMENT \'章节号\',
|
||||||
|
`chapter_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT \'章节名\',
|
||||||
|
`word_count` int(10) unsigned NOT NULL COMMENT \'章节字数\',
|
||||||
|
`is_vip` tinyint(3) unsigned NOT NULL DEFAULT \'0\' COMMENT \'是否收费;1-收费 0-免费\',
|
||||||
|
`create_time` datetime DEFAULT NULL,
|
||||||
|
`update_time` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `uk_bookId_chapterNum` (`book_id`,`chapter_num`) USING BTREE,
|
||||||
|
UNIQUE KEY `pk_id` (`id`) USING BTREE,
|
||||||
|
KEY `idx_bookId` (`book_id`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=\'小说章节\'');
|
||||||
|
prepare stmt from @stmt;
|
||||||
|
execute stmt;
|
||||||
|
deallocate prepare stmt;
|
||||||
|
|
||||||
|
set i = i + 1;
|
||||||
|
|
||||||
|
end while;
|
||||||
|
|
||||||
|
END;
|
||||||
|
call createBookChapterTable();
|
||||||
|
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS createBookContentTable;
|
||||||
|
-- 创建小说内容表的存储过程
|
||||||
|
CREATE PROCEDURE createBookContentTable()
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- 定义变量
|
||||||
|
DECLARE i int DEFAULT 0;
|
||||||
|
DECLARE tableName char(13) DEFAULT NULL;
|
||||||
|
|
||||||
|
while i < 10 do
|
||||||
|
|
||||||
|
set tableName = concat('book_content',i);
|
||||||
|
|
||||||
|
set @stmt = concat('create table ',tableName,'(
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT \'主键\',
|
||||||
|
`chapter_id` bigint(20) unsigned NOT NULL COMMENT \'章节ID\',
|
||||||
|
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT \'小说章节内容\',
|
||||||
|
`create_time` datetime DEFAULT NULL,
|
||||||
|
`update_time` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `uk_chapterId` (`chapter_id`) USING BTREE,
|
||||||
|
UNIQUE KEY `pk_id` (`id`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=\'小说内容\'');
|
||||||
|
prepare stmt from @stmt;
|
||||||
|
execute stmt;
|
||||||
|
deallocate prepare stmt;
|
||||||
|
|
||||||
|
set i = i + 1;
|
||||||
|
|
||||||
|
end while;
|
||||||
|
|
||||||
|
END;
|
||||||
|
call createBookContentTable();
|
||||||
|
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS copyBookChapterData;
|
||||||
|
-- 迁移小说章节数据的存储过程
|
||||||
|
CREATE PROCEDURE copyBookChapterData()
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- 定义变量
|
||||||
|
DECLARE s int DEFAULT 0;
|
||||||
|
DECLARE chapterId bigint;
|
||||||
|
DECLARE bookId bigint;
|
||||||
|
DECLARE chapterNum smallint;
|
||||||
|
DECLARE chapterName varchar(100);
|
||||||
|
DECLARE wordCount int DEFAULT 0;
|
||||||
|
DECLARE isVip tinyint(64) DEFAULT 0;
|
||||||
|
DECLARE createTime datetime DEFAULT NULL;
|
||||||
|
DECLARE updateTime datetime DEFAULT NULL;
|
||||||
|
DECLARE tableNumber int DEFAULT 0;
|
||||||
|
DECLARE tableName char(13) DEFAULT NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 定义游标
|
||||||
|
DECLARE report CURSOR FOR select id,book_id,chapter_num, chapter_name, word_count, is_vip,create_time,update_time from book_chapter;
|
||||||
|
|
||||||
|
-- 声明当游标遍历完后将标志变量置成某个值
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET s=1;
|
||||||
|
|
||||||
|
-- 打开游标
|
||||||
|
open report;
|
||||||
|
|
||||||
|
-- 将游标中的值赋值给变量,注意:变量名不要和返回的列名同名,变量顺序要和sql结果列的顺序一致
|
||||||
|
fetch report into chapterId,bookId,chapterNum, chapterName, wordCount,isVip,createTime,updateTime;
|
||||||
|
|
||||||
|
-- 循环遍历
|
||||||
|
while s<>1 do
|
||||||
|
-- 执行业务逻辑
|
||||||
|
set tableNumber = bookId % 10;
|
||||||
|
set tableName = concat('book_chapter',tableNumber);
|
||||||
|
set @stmt = concat('insert into ',tableName,'(`id`, `book_id`, `chapter_num`, `chapter_name`, `word_count`, `is_vip`, `create_time`, `update_time`) VALUES (',chapterId,', ',bookId,', ',chapterNum,', \'',chapterName,'\', ',wordCount,', ',isVip,', \'',createTime,'\', \'',updateTime,'\')');
|
||||||
|
prepare stmt from @stmt;
|
||||||
|
execute stmt;
|
||||||
|
deallocate prepare stmt;
|
||||||
|
|
||||||
|
fetch report into chapterId,bookId,chapterNum, chapterName, wordCount,isVip,createTime,updateTime;
|
||||||
|
end while;
|
||||||
|
-- 关闭游标
|
||||||
|
close report;
|
||||||
|
|
||||||
|
END;
|
||||||
|
call copyBookChapterData();
|
||||||
|
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS copyBookContentData;
|
||||||
|
-- 迁移小说内容数据的存储过程
|
||||||
|
CREATE PROCEDURE copyBookContentData()
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- 定义变量
|
||||||
|
DECLARE s int DEFAULT 0;
|
||||||
|
DECLARE contentId bigint;
|
||||||
|
DECLARE chapterId bigint;
|
||||||
|
DECLARE bookContent mediumtext;
|
||||||
|
DECLARE createTime datetime DEFAULT NULL;
|
||||||
|
DECLARE updateTime datetime DEFAULT NULL;
|
||||||
|
DECLARE tableNumber int DEFAULT 0;
|
||||||
|
DECLARE tableName char(13) DEFAULT NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 定义游标
|
||||||
|
DECLARE report CURSOR FOR select id,chapter_id,content,create_time,update_time from book_content;
|
||||||
|
|
||||||
|
-- 声明当游标遍历完后将标志变量置成某个值
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET s=1;
|
||||||
|
|
||||||
|
-- 打开游标
|
||||||
|
open report;
|
||||||
|
|
||||||
|
-- 将游标中的值赋值给变量,注意:变量名不要和返回的列名同名,变量顺序要和sql结果列的顺序一致
|
||||||
|
fetch report into contentId,chapterId,bookContent,createTime,updateTime;
|
||||||
|
|
||||||
|
-- 循环遍历
|
||||||
|
while s<>1 do
|
||||||
|
-- 执行业务逻辑
|
||||||
|
set tableNumber = chapterId % 10;
|
||||||
|
set tableName = concat('book_content',tableNumber);
|
||||||
|
set bookContent = REPLACE(bookContent,'\'',"\\'");
|
||||||
|
set @stmt = concat('insert into ',tableName,'(`id`, `chapter_id`, `content`) VALUES (',contentId,', ',chapterId,',\'',bookContent,'\')');
|
||||||
|
|
||||||
|
prepare stmt from @stmt;
|
||||||
|
execute stmt;
|
||||||
|
deallocate prepare stmt;
|
||||||
|
|
||||||
|
fetch report into contentId,chapterId,bookContent,createTime,updateTime;
|
||||||
|
end while;
|
||||||
|
-- 关闭游标
|
||||||
|
close report;
|
||||||
|
|
||||||
|
END;
|
||||||
|
call copyBookContentData();
|
375
pom.xml
375
pom.xml
@ -1,176 +1,223 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.0.0-SNAPSHOT</version>
|
<version>3.0.0-SNAPSHOT</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>io.github.xxyopen</groupId>
|
<groupId>io.github.xxyopen</groupId>
|
||||||
<artifactId>novel</artifactId>
|
<artifactId>novel</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
<name>novel</name>
|
<name>novel</name>
|
||||||
<description>Spring Boot 3 + Vue 3 构建的前后端分离小说系统</description>
|
<description>Spring Boot 3 + Vue 3 构建的前后端分离小说系统</description>
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<mybatis-plus.version>3.5.1</mybatis-plus.version>
|
<mybatis-plus.version>3.5.1</mybatis-plus.version>
|
||||||
<spring.version>6.0.0-SNAPSHOT</spring.version>
|
<spring.version>6.0.0-SNAPSHOT</spring.version>
|
||||||
<jjwt.version>0.11.5</jjwt.version>
|
<jjwt.version>0.11.5</jjwt.version>
|
||||||
</properties>
|
<elasticsearch.version>8.2.0</elasticsearch.version>
|
||||||
<dependencies>
|
<xxl-job.version>2.3.1</xxl-job.version>
|
||||||
<dependency>
|
<sentinel.version>1.8.4</sentinel.version>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<shardingsphere-jdbc.version>5.1.1</shardingsphere-jdbc.version>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
</properties>
|
||||||
<exclusions>
|
<dependencies>
|
||||||
<!-- Exclude the Tomcat dependency -->
|
<dependency>
|
||||||
<exclusion>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
<exclusions>
|
||||||
</exclusion>
|
<!-- Exclude the Tomcat dependency -->
|
||||||
</exclusions>
|
<exclusion>
|
||||||
</dependency>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Use Undertow instead -->
|
<!-- Use Undertow instead -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- mybatis-plus -->
|
<!-- mybatis-plus -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
<version>${mybatis-plus.version}</version>
|
<version>${mybatis-plus.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- mybatis-plus 代码生成器 -->
|
<!-- mybatis-plus 代码生成器 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-generator</artifactId>
|
<artifactId>mybatis-plus-generator</artifactId>
|
||||||
<version>${mybatis-plus.version}</version>
|
<version>${mybatis-plus.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.velocity</groupId>
|
<groupId>org.apache.velocity</groupId>
|
||||||
<artifactId>velocity-engine-core</artifactId>
|
<artifactId>velocity-engine-core</artifactId>
|
||||||
<version>2.3</version>
|
<version>2.3</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 缓存相关 -->
|
<!-- 缓存相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-cache</artifactId>
|
<artifactId>spring-boot-starter-cache</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
<artifactId>caffeine</artifactId>
|
<artifactId>caffeine</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JWT 相关 -->
|
<!-- JWT 相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-api</artifactId>
|
<artifactId>jjwt-api</artifactId>
|
||||||
<version>${jjwt.version}</version>
|
<version>${jjwt.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-impl</artifactId>
|
<artifactId>jjwt-impl</artifactId>
|
||||||
<version>${jjwt.version}</version>
|
<version>${jjwt.version}</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
<version>${jjwt.version}</version>
|
<version>${jjwt.version}</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 请求参数校验相关 -->
|
<!-- 请求参数校验相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hibernate.validator</groupId>
|
<groupId>org.hibernate.validator</groupId>
|
||||||
<artifactId>hibernate-validator</artifactId>
|
<artifactId>hibernate-validator</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<!-- elasticsearch 相关 -->
|
||||||
<groupId>mysql</groupId>
|
<dependency>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<groupId>co.elastic.clients</groupId>
|
||||||
<scope>runtime</scope>
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
</dependency>
|
<version>${elasticsearch.version}</version>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<dependency>
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<optional>true</optional>
|
<artifactId>jackson-databind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
<!-- MQ 相关 -->
|
||||||
<plugins>
|
<dependency>
|
||||||
<plugin>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
</dependency>
|
||||||
<configuration>
|
|
||||||
<excludes>
|
<!-- XXL-JOB 相关 -->
|
||||||
<exclude>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>com.xuxueli</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>xxl-job-core</artifactId>
|
||||||
</exclude>
|
<version>${xxl-job.version}</version>
|
||||||
</excludes>
|
</dependency>
|
||||||
</configuration>
|
|
||||||
</plugin>
|
<!-- sentinel 相关 -->
|
||||||
</plugins>
|
<dependency>
|
||||||
</build>
|
<groupId>com.alibaba.csp</groupId>
|
||||||
<repositories>
|
<artifactId>sentinel-core</artifactId>
|
||||||
<repository>
|
<version>${sentinel.version}</version>
|
||||||
<id>spring-milestones</id>
|
</dependency>
|
||||||
<name>Spring Milestones</name>
|
<dependency>
|
||||||
<url>https://repo.spring.io/milestone</url>
|
<groupId>com.alibaba.csp</groupId>
|
||||||
<snapshots>
|
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||||
<enabled>false</enabled>
|
<version>${sentinel.version}</version>
|
||||||
</snapshots>
|
</dependency>
|
||||||
</repository>
|
|
||||||
<repository>
|
<!-- ShardingSphere-JDBC -->
|
||||||
<id>spring-snapshots</id>
|
<dependency>
|
||||||
<name>Spring Snapshots</name>
|
<groupId>org.apache.shardingsphere</groupId>
|
||||||
<url>https://repo.spring.io/snapshot</url>
|
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
|
||||||
<releases>
|
<version>${shardingsphere-jdbc.version}</version>
|
||||||
<enabled>false</enabled>
|
</dependency>
|
||||||
</releases>
|
|
||||||
</repository>
|
<dependency>
|
||||||
</repositories>
|
<groupId>mysql</groupId>
|
||||||
<pluginRepositories>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
<pluginRepository>
|
<scope>runtime</scope>
|
||||||
<id>spring-milestones</id>
|
</dependency>
|
||||||
<name>Spring Milestones</name>
|
<dependency>
|
||||||
<url>https://repo.spring.io/milestone</url>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<snapshots>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
<enabled>false</enabled>
|
<optional>true</optional>
|
||||||
</snapshots>
|
</dependency>
|
||||||
</pluginRepository>
|
<dependency>
|
||||||
<pluginRepository>
|
<groupId>org.projectlombok</groupId>
|
||||||
<id>spring-snapshots</id>
|
<artifactId>lombok</artifactId>
|
||||||
<name>Spring Snapshots</name>
|
<optional>true</optional>
|
||||||
<url>https://repo.spring.io/snapshot</url>
|
</dependency>
|
||||||
<releases>
|
<dependency>
|
||||||
<enabled>false</enabled>
|
<groupId>org.springframework.boot</groupId>
|
||||||
</releases>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
</pluginRepository>
|
<scope>test</scope>
|
||||||
</pluginRepositories>
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<name>Spring Milestones</name>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>spring-snapshots</id>
|
||||||
|
<name>Spring Snapshots</name>
|
||||||
|
<url>https://repo.spring.io/snapshot</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<name>Spring Milestones</name>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</pluginRepository>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>spring-snapshots</id>
|
||||||
|
<name>Spring Snapshots</name>
|
||||||
|
<url>https://repo.spring.io/snapshot</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -9,12 +9,14 @@ import org.springframework.cache.CacheManager;
|
|||||||
import org.springframework.cache.annotation.EnableCaching;
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@MapperScan("io.github.xxyopen.novel.dao.mapper")
|
@MapperScan("io.github.xxyopen.novel.dao.mapper")
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
|
@EnableScheduling
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class NovelApplication {
|
public class NovelApplication {
|
||||||
|
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package io.github.xxyopen.novel.controller.author;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.auth.UserHolder;
|
||||||
|
import io.github.xxyopen.novel.core.common.req.PageReqDto;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||||
|
import io.github.xxyopen.novel.dto.req.AuthorRegisterReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
import io.github.xxyopen.novel.service.AuthorService;
|
||||||
|
import io.github.xxyopen.novel.service.BookService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家后台-作家模块 API 控制器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(ApiRouterConsts.API_AUTHOR_URL_PREFIX)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthorController {
|
||||||
|
|
||||||
|
private final AuthorService authorService;
|
||||||
|
|
||||||
|
private final BookService bookService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家注册接口
|
||||||
|
*/
|
||||||
|
@PostMapping("register")
|
||||||
|
public RestResp<Void> register(@Valid @RequestBody AuthorRegisterReqDto dto) {
|
||||||
|
dto.setUserId(UserHolder.getUserId());
|
||||||
|
return authorService.register(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询作家状态接口
|
||||||
|
*/
|
||||||
|
@GetMapping("status")
|
||||||
|
public RestResp<Integer> getStatus() {
|
||||||
|
return authorService.getStatus(UserHolder.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说发布接口
|
||||||
|
*/
|
||||||
|
@PostMapping("book")
|
||||||
|
public RestResp<Void> publishBook(@Valid @RequestBody BookAddReqDto dto) {
|
||||||
|
return bookService.saveBook(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说发布列表查询接口
|
||||||
|
*/
|
||||||
|
@GetMapping("books")
|
||||||
|
public RestResp<PageRespDto<BookInfoRespDto>> listBooks(PageReqDto dto) {
|
||||||
|
return bookService.listAuthorBooks(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节发布接口
|
||||||
|
*/
|
||||||
|
@PostMapping("book/chapter/{bookId}")
|
||||||
|
public RestResp<Void> publishBookChapter(@PathVariable("bookId") Long bookId, @Valid @RequestBody ChapterAddReqDto dto) {
|
||||||
|
dto.setBookId(bookId);
|
||||||
|
return bookService.saveBookChapter(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节发布列表查询接口
|
||||||
|
*/
|
||||||
|
@GetMapping("book/chapters/{bookId}")
|
||||||
|
public RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(@PathVariable("bookId") Long bookId, PageReqDto dto) {
|
||||||
|
return bookService.listBookChapters(bookId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
package io.github.xxyopen.novel.controller.front;
|
package io.github.xxyopen.novel.controller.front;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
|
||||||
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
|
||||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||||
import io.github.xxyopen.novel.dto.resp.*;
|
import io.github.xxyopen.novel.dto.resp.*;
|
||||||
import io.github.xxyopen.novel.service.BookService;
|
import io.github.xxyopen.novel.service.BookService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -13,7 +11,7 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说模块 API 接口
|
* 前台门户-小说模块 API 控制器
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/14
|
* @date 2022/5/14
|
||||||
@ -33,14 +31,6 @@ public class BookController {
|
|||||||
return bookService.listCategory(workDirection);
|
return bookService.listCategory(workDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 小说搜索接口
|
|
||||||
*/
|
|
||||||
@GetMapping("search_list")
|
|
||||||
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
|
||||||
return bookService.searchBooks(condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息查询接口
|
* 小说信息查询接口
|
||||||
*/
|
*/
|
||||||
|
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页模块 API 接口
|
* 前台门户-首页模块 API 控制器
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/12
|
* @date 2022/5/12
|
||||||
|
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新闻模块 API 接口
|
* 前台门户-新闻模块 API 控制器
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/12
|
* @date 2022/5/12
|
||||||
|
@ -5,14 +5,13 @@ import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
|||||||
import io.github.xxyopen.novel.dto.resp.ImgVerifyCodeRespDto;
|
import io.github.xxyopen.novel.dto.resp.ImgVerifyCodeRespDto;
|
||||||
import io.github.xxyopen.novel.service.ResourceService;
|
import io.github.xxyopen.novel.service.ResourceService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源(图片/视频/文档)相关 控制器
|
* 前台门户-资源(图片/视频/文档)模块 API 控制器
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/17
|
* @date 2022/5/17
|
||||||
@ -32,4 +31,12 @@ public class ResourceController {
|
|||||||
return resourceService.getImgVerifyCode();
|
return resourceService.getImgVerifyCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片上传接口
|
||||||
|
* */
|
||||||
|
@PostMapping("/image")
|
||||||
|
RestResp<String> uploadImage(@RequestParam("file") MultipartFile file) {
|
||||||
|
return resourceService.uploadImage(file);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package io.github.xxyopen.novel.controller.front;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||||
|
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
import io.github.xxyopen.novel.service.SearchService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前台门户-搜索模块 API 控制器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/27
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(ApiRouterConsts.API_FRONT_SEARCH_URL_PREFIX)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SearchController {
|
||||||
|
|
||||||
|
private final SearchService searchService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说搜索接口
|
||||||
|
*/
|
||||||
|
@GetMapping("books")
|
||||||
|
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
||||||
|
return searchService.searchBooks(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,6 +7,7 @@ import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
|||||||
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.UserInfoRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
||||||
import io.github.xxyopen.novel.service.BookService;
|
import io.github.xxyopen.novel.service.BookService;
|
||||||
@ -16,7 +17,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会员模块相关 控制器
|
* 前台门户-会员模块 API 控制器
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/17
|
* @date 2022/5/17
|
||||||
@ -46,6 +47,13 @@ public class UserController {
|
|||||||
return userService.login(dto);
|
return userService.login(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息查询接口
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
public RestResp<UserInfoRespDto> getUserInfo() {
|
||||||
|
return userService.getUserInfo(UserHolder.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户信息修改接口
|
* 用户信息修改接口
|
||||||
|
@ -15,7 +15,7 @@ import org.springframework.stereotype.Component;
|
|||||||
public class AdminAuthStrategy implements AuthStrategy {
|
public class AdminAuthStrategy implements AuthStrategy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void auth(String token) throws BusinessException {
|
public void auth(String token, String requestUri) throws BusinessException {
|
||||||
// TODO 平台后台 token 校验
|
// TODO 平台后台 token 校验
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
|||||||
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
import io.github.xxyopen.novel.core.util.JwtUtils;
|
import io.github.xxyopen.novel.core.util.JwtUtils;
|
||||||
import io.github.xxyopen.novel.dto.UserInfoDto;
|
import io.github.xxyopen.novel.dto.UserInfoDto;
|
||||||
import io.github.xxyopen.novel.manager.UserInfoCacheManager;
|
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -20,12 +20,12 @@ public interface AuthStrategy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求用户认证
|
* 请求用户认证
|
||||||
* 如果后面需要扩展到对每一个URI都进行权限控制,那么此方法可以加一个参数来接收用户请求的URI
|
|
||||||
*
|
*
|
||||||
* @param token 登录 token
|
* @param token 登录 token
|
||||||
|
* @param requestUri 请求的 URI
|
||||||
* @throws BusinessException 认证失败则抛出业务异常
|
* @throws BusinessException 认证失败则抛出业务异常
|
||||||
*/
|
*/
|
||||||
void auth(String token) throws BusinessException;
|
void auth(String token, String requestUri) throws BusinessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台多系统单点登录统一账号认证(门户系统、作家系统以及后面会扩展的漫画系统和视频系统等)
|
* 前台多系统单点登录统一账号认证(门户系统、作家系统以及后面会扩展的漫画系统和视频系统等)
|
||||||
|
@ -2,13 +2,15 @@ package io.github.xxyopen.novel.core.auth;
|
|||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
||||||
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
||||||
|
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||||
import io.github.xxyopen.novel.core.util.JwtUtils;
|
import io.github.xxyopen.novel.core.util.JwtUtils;
|
||||||
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
||||||
import io.github.xxyopen.novel.manager.AuthorInfoCacheManager;
|
import io.github.xxyopen.novel.manager.cache.AuthorInfoCacheManager;
|
||||||
import io.github.xxyopen.novel.manager.UserInfoCacheManager;
|
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,14 +29,25 @@ public class AuthorAuthStrategy implements AuthStrategy {
|
|||||||
|
|
||||||
private final AuthorInfoCacheManager authorInfoCacheManager;
|
private final AuthorInfoCacheManager authorInfoCacheManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不需要进行作家权限认证的 URI
|
||||||
|
* */
|
||||||
|
private static final List<String> EXCLUDE_URI = List.of(
|
||||||
|
ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/register",
|
||||||
|
ApiRouterConsts.API_AUTHOR_URL_PREFIX +"/status"
|
||||||
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void auth(String token) throws BusinessException {
|
public void auth(String token, String requestUri) throws BusinessException {
|
||||||
// 统一账号认证
|
// 统一账号认证
|
||||||
Long userId = authSSO(jwtUtils, userInfoCacheManager, token);
|
Long userId = authSSO(jwtUtils, userInfoCacheManager, token);
|
||||||
|
if(EXCLUDE_URI.contains(requestUri)){
|
||||||
|
// 该请求不需要进行作家权限认证
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 作家权限认证
|
// 作家权限认证
|
||||||
AuthorInfoDto authorInfo = authorInfoCacheManager.getAuthor(userId);
|
AuthorInfoDto authorInfo = authorInfoCacheManager.getAuthor(userId);
|
||||||
if(Objects.isNull(authorInfo)){
|
if (Objects.isNull(authorInfo)) {
|
||||||
// 作家账号不存在,无权访问作家专区
|
// 作家账号不存在,无权访问作家专区
|
||||||
throw new BusinessException(ErrorCodeEnum.USER_UN_AUTH);
|
throw new BusinessException(ErrorCodeEnum.USER_UN_AUTH);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package io.github.xxyopen.novel.core.auth;
|
|||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
||||||
import io.github.xxyopen.novel.core.util.JwtUtils;
|
import io.github.xxyopen.novel.core.util.JwtUtils;
|
||||||
import io.github.xxyopen.novel.manager.UserInfoCacheManager;
|
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ public class FrontAuthStrategy implements AuthStrategy {
|
|||||||
private final UserInfoCacheManager userInfoCacheManager;
|
private final UserInfoCacheManager userInfoCacheManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void auth(String token) throws BusinessException {
|
public void auth(String token, String requestUri) throws BusinessException {
|
||||||
// 统一账号认证
|
// 统一账号认证
|
||||||
authSSO(jwtUtils,userInfoCacheManager,token);
|
authSSO(jwtUtils,userInfoCacheManager,token);
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,14 @@ public class CommonConsts {
|
|||||||
* 是
|
* 是
|
||||||
* */
|
* */
|
||||||
public static final Integer YES = 1;
|
public static final Integer YES = 1;
|
||||||
|
public static final String TRUE = "true";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 否
|
* 否
|
||||||
* */
|
* */
|
||||||
public static final Integer NO = 0;
|
public static final Integer NO = 0;
|
||||||
|
public static final String FALSE = "false";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 性别常量
|
* 性别常量
|
||||||
|
@ -82,6 +82,16 @@ public enum ErrorCodeEnum {
|
|||||||
* */
|
* */
|
||||||
USER_UN_AUTH("A0301","访问未授权"),
|
USER_UN_AUTH("A0301","访问未授权"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户请求服务异常
|
||||||
|
* */
|
||||||
|
USER_REQ_EXCEPTION("A0500","用户请求服务异常"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求超出限制
|
||||||
|
* */
|
||||||
|
USER_REQ_MANY("A0501","请求超出限制"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户评论异常
|
* 用户评论异常
|
||||||
* */
|
* */
|
||||||
@ -92,6 +102,27 @@ public enum ErrorCodeEnum {
|
|||||||
* */
|
* */
|
||||||
USER_COMMENTED("A2001","用户已发表评论"),
|
USER_COMMENTED("A2001","用户已发表评论"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家发布异常
|
||||||
|
* */
|
||||||
|
AUTHOR_PUBLISH("A3000","作家发布异常"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说名已存在
|
||||||
|
* */
|
||||||
|
AUTHOR_BOOK_NAME_EXIST("A3001","小说名已存在"),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户上传文件异常
|
||||||
|
* */
|
||||||
|
USER_UPLOAD_FILE_ERROR("A0700","用户上传文件异常"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户上传文件类型不匹配
|
||||||
|
* */
|
||||||
|
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701","用户上传文件类型不匹配"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一级宏观错误码,系统执行出错
|
* 一级宏观错误码,系统执行出错
|
||||||
* */
|
* */
|
||||||
@ -116,11 +147,11 @@ public enum ErrorCodeEnum {
|
|||||||
/**
|
/**
|
||||||
* 错误码
|
* 错误码
|
||||||
* */
|
* */
|
||||||
private String code;
|
private final String code;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中文描述
|
* 中文描述
|
||||||
* */
|
* */
|
||||||
private String message;
|
private final String message;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.constant.AmqpConsts;
|
||||||
|
import org.springframework.amqp.core.Binding;
|
||||||
|
import org.springframework.amqp.core.BindingBuilder;
|
||||||
|
import org.springframework.amqp.core.FanoutExchange;
|
||||||
|
import org.springframework.amqp.core.Queue;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMQP 配置类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/25
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class AmqpConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说信息改变交换机
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public FanoutExchange bookChangeExchange() {
|
||||||
|
return new FanoutExchange(AmqpConsts.BookChangeMq.EXCHANGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elasticsearch book 索引更新队列
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public Queue esBookUpdateQueue() {
|
||||||
|
return new Queue(AmqpConsts.BookChangeMq.QUEUE_ES_UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elasticsearch book 索引更新队列绑定到小说信息改变交换机
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public Binding esBookUpdateQueueBinding() {
|
||||||
|
return BindingBuilder.bind(esBookUpdateQueue()).to(bookChangeExchange());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
package io.github.xxyopen.novel.core.config;
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||||
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
import io.github.xxyopen.novel.core.interceptor.AuthInterceptor;
|
import io.github.xxyopen.novel.core.interceptor.AuthInterceptor;
|
||||||
|
import io.github.xxyopen.novel.core.interceptor.FileInterceptor;
|
||||||
|
import io.github.xxyopen.novel.core.interceptor.FlowLimitInterceptor;
|
||||||
|
import io.github.xxyopen.novel.core.interceptor.TokenParseInterceptor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
@ -19,21 +23,46 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class WebConfig implements WebMvcConfigurer {
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
private final AuthInterceptor frontAuthInterceptor;
|
private final FlowLimitInterceptor flowLimitInterceptor;
|
||||||
|
|
||||||
|
private final AuthInterceptor authInterceptor;
|
||||||
|
|
||||||
|
private final FileInterceptor fileInterceptor;
|
||||||
|
|
||||||
|
private final TokenParseInterceptor tokenParseInterceptor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(frontAuthInterceptor)
|
|
||||||
|
// 流量限制拦截器
|
||||||
|
registry.addInterceptor(flowLimitInterceptor)
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.order(0);
|
||||||
|
|
||||||
|
// 文件访问拦截
|
||||||
|
registry.addInterceptor(fileInterceptor)
|
||||||
|
.addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "**")
|
||||||
|
.order(1);
|
||||||
|
|
||||||
|
// 权限认证拦截
|
||||||
|
registry.addInterceptor(authInterceptor)
|
||||||
// 拦截会员中心相关请求接口
|
// 拦截会员中心相关请求接口
|
||||||
.addPathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/**"
|
.addPathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/**",
|
||||||
// 拦截作家后台相关请求接口
|
// 拦截作家后台相关请求接口
|
||||||
, ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/**"
|
ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/**",
|
||||||
// 拦截平台后台相关请求接口
|
// 拦截平台后台相关请求接口
|
||||||
, ApiRouterConsts.API_ADMIN_URL_PREFIX + "/**")
|
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/**")
|
||||||
// 放行登录注册相关请求接口
|
// 放行登录注册相关请求接口
|
||||||
.excludePathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/register"
|
.excludePathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/register",
|
||||||
, ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/login"
|
ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/login",
|
||||||
, ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/register"
|
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/login")
|
||||||
,ApiRouterConsts.API_ADMIN_URL_PREFIX + "/login");
|
.order(2);
|
||||||
|
|
||||||
|
// Token 解析拦截器
|
||||||
|
registry.addInterceptor(tokenParseInterceptor)
|
||||||
|
// 拦截小说内容查询接口,需要解析 token 以判断该用户是否有权阅读该章节(付费章节是否已购买)
|
||||||
|
.addPathPatterns(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX + "/content/*")
|
||||||
|
.order(3);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XXL-JOB 配置类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/31
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(prefix = "xxl.job", name = "enable", havingValue = "true")
|
||||||
|
@Slf4j
|
||||||
|
public class XxlJobConfig {
|
||||||
|
|
||||||
|
@Value("${xxl.job.admin.addresses}")
|
||||||
|
private String adminAddresses;
|
||||||
|
|
||||||
|
@Value("${xxl.job.accessToken}")
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
@Value("${xxl.job.executor.appname}")
|
||||||
|
private String appname;
|
||||||
|
|
||||||
|
@Value("${xxl.job.executor.logpath}")
|
||||||
|
private String logPath;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public XxlJobSpringExecutor xxlJobExecutor() {
|
||||||
|
log.info(">>>>>>>>>>> xxl-job config init.");
|
||||||
|
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
|
||||||
|
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
|
||||||
|
xxlJobSpringExecutor.setAccessToken(accessToken);
|
||||||
|
xxlJobSpringExecutor.setAppname(appname);
|
||||||
|
xxlJobSpringExecutor.setLogPath(logPath);
|
||||||
|
return xxlJobSpringExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package io.github.xxyopen.novel.core.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMQP 相关常量
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/25
|
||||||
|
*/
|
||||||
|
public class AmqpConsts {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说信息改变 MQ
|
||||||
|
* */
|
||||||
|
public static class BookChangeMq{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说信息改变交换机
|
||||||
|
* */
|
||||||
|
public static final String EXCHANGE_NAME = "EXCHANGE-BOOK-CHANGE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elasticsearch book 索引更新的队列
|
||||||
|
* */
|
||||||
|
public static final String QUEUE_ES_UPDATE = "QUEUE-ES-BOOK-UPDATE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis book 缓存更新的队列
|
||||||
|
* */
|
||||||
|
public static final String QUEUE_REDIS_UPDATE = "QUEUE-REDIS-BOOK-UPDATE";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -57,6 +57,11 @@ public class ApiRouterConsts {
|
|||||||
* */
|
* */
|
||||||
public static final String RESOURCE_URL_PREFIX = "/resource";
|
public static final String RESOURCE_URL_PREFIX = "/resource";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索模块请求路径前缀
|
||||||
|
* */
|
||||||
|
public static final String SEARCH_URL_PREFIX = "/search";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户首页API请求路径前缀
|
* 前台门户首页API请求路径前缀
|
||||||
*/
|
*/
|
||||||
@ -82,4 +87,9 @@ public class ApiRouterConsts {
|
|||||||
*/
|
*/
|
||||||
public static final String API_FRONT_RESOURCE_URL_PREFIX = API_FRONT_URL_PREFIX + RESOURCE_URL_PREFIX;
|
public static final String API_FRONT_RESOURCE_URL_PREFIX = API_FRONT_URL_PREFIX + RESOURCE_URL_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前台门户搜索相关API请求路径前缀
|
||||||
|
* */
|
||||||
|
public static final String API_FRONT_SEARCH_URL_PREFIX = API_FRONT_URL_PREFIX + SEARCH_URL_PREFIX;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -89,8 +89,14 @@ public class DatabaseConsts {
|
|||||||
|
|
||||||
public static final String COLUMN_CATEGORY_ID = "category_id";
|
public static final String COLUMN_CATEGORY_ID = "category_id";
|
||||||
|
|
||||||
|
public static final String COLUMN_BOOK_NAME = "book_name";
|
||||||
|
|
||||||
|
public static final String AUTHOR_ID = "author_id";
|
||||||
|
|
||||||
public static final String COLUMN_VISIT_COUNT = "visit_count";
|
public static final String COLUMN_VISIT_COUNT = "visit_count";
|
||||||
|
|
||||||
|
public static final String COLUMN_WORD_COUNT = "word_count";
|
||||||
|
|
||||||
public static final String COLUMN_LAST_CHAPTER_UPDATE_TIME = "last_chapter_update_time";
|
public static final String COLUMN_LAST_CHAPTER_UPDATE_TIME = "last_chapter_update_time";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
package io.github.xxyopen.novel.core.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elasticsearch 相关常量
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
public class EsConsts {
|
||||||
|
|
||||||
|
private EsConsts() {
|
||||||
|
throw new IllegalStateException(SystemConfigConsts.CONST_INSTANCE_EXCEPTION_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说索引
|
||||||
|
* */
|
||||||
|
public static class BookIndex{
|
||||||
|
|
||||||
|
private BookIndex() {
|
||||||
|
throw new IllegalStateException(SystemConfigConsts.CONST_INSTANCE_EXCEPTION_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 索引名
|
||||||
|
* */
|
||||||
|
public static final String INDEX_NAME = "book";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
public static final String FIELD_ID = "id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作品方向;0-男频 1-女频
|
||||||
|
*/
|
||||||
|
public static final String FIELD_WORK_DIRECTION = "workDirection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类别ID
|
||||||
|
*/
|
||||||
|
public static final String FIELD_CATEGORY_ID = "categoryId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类别名
|
||||||
|
*/
|
||||||
|
public static final String FIELD_CATEGORY_NAME = "categoryName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说名
|
||||||
|
*/
|
||||||
|
public static final String FIELD_BOOK_NAME = "bookName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家id
|
||||||
|
*/
|
||||||
|
public static final String FIELD_AUTHOR_ID = "authorId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家名
|
||||||
|
*/
|
||||||
|
public static final String FIELD_AUTHOR_NAME = "authorName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 书籍描述
|
||||||
|
*/
|
||||||
|
public static final String FIELD_BOOK_DESC = "bookDesc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评分;总分:10 ,真实评分 = score/10
|
||||||
|
*/
|
||||||
|
public static final String FIELD_SCORE = "score";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 书籍状态;0-连载中 1-已完结
|
||||||
|
*/
|
||||||
|
public static final String FIELD_BOOK_STATUS = "bookStatus";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击量
|
||||||
|
*/
|
||||||
|
public static final String FIELD_VISIT_COUNT = "visitCount";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总字数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_WORD_COUNT = "wordCount";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_COMMENT_COUNT = "commentCount";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最新章节ID
|
||||||
|
*/
|
||||||
|
public static final String FIELD_LAST_CHAPTER_ID = "lastChapterId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最新章节名
|
||||||
|
*/
|
||||||
|
public static final String FIELD_LAST_CHAPTER_NAME = "lastChapterName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最新章节更新时间
|
||||||
|
*/
|
||||||
|
public static final String FIELD_LAST_CHAPTER_UPDATE_TIME = "lastChapterUpdateTime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
public static final String FIELD_IS_VIP = "isVip";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -32,6 +32,11 @@ public class SystemConfigConsts {
|
|||||||
* */
|
* */
|
||||||
public static final String NOVEL_ADMIN_KEY = "admin";
|
public static final String NOVEL_ADMIN_KEY = "admin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片上传目录
|
||||||
|
* */
|
||||||
|
public static final String IMAGE_UPLOAD_DIRECTORY = "/image/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 常量类实例化异常信息
|
* 常量类实例化异常信息
|
||||||
* */
|
* */
|
||||||
|
@ -43,13 +43,13 @@ public class AuthInterceptor implements HandlerInterceptor {
|
|||||||
String requestUri = request.getRequestURI();
|
String requestUri = request.getRequestURI();
|
||||||
|
|
||||||
// 根据请求的 URI 得到认证策略
|
// 根据请求的 URI 得到认证策略
|
||||||
String authStrategyName = requestUri.substring(ApiRouterConsts.API_URL_PREFIX.length() + 1);
|
String subUri = requestUri.substring(ApiRouterConsts.API_URL_PREFIX.length() + 1);
|
||||||
authStrategyName = authStrategyName.substring(0,authStrategyName.indexOf("/"));
|
String systemName = subUri.substring(0,subUri.indexOf("/"));
|
||||||
authStrategyName = String.format("%sAuthStrategy",authStrategyName);
|
String authStrategyName = String.format("%sAuthStrategy",systemName);
|
||||||
|
|
||||||
// 开始认证
|
// 开始认证
|
||||||
try {
|
try {
|
||||||
authStrategy.get(authStrategyName).auth(token);
|
authStrategy.get(authStrategyName).auth(token,requestUri);
|
||||||
return HandlerInterceptor.super.preHandle(request, response, handler);
|
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||||
}catch (BusinessException exception){
|
}catch (BusinessException exception){
|
||||||
// 认证失败
|
// 认证失败
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package io.github.xxyopen.novel.core.interceptor;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件 拦截器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/22
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FileInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
@Value("${novel.file.upload.path}")
|
||||||
|
private String fileUploadPath;
|
||||||
|
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
// 获取请求的 URI
|
||||||
|
String requestUri = request.getRequestURI();
|
||||||
|
// 缓存10天
|
||||||
|
response.setDateHeader("expires", System.currentTimeMillis() + 60 * 60 * 24 * 10 * 1000);
|
||||||
|
try (OutputStream out = response.getOutputStream(); InputStream input = new FileInputStream(fileUploadPath + requestUri)) {
|
||||||
|
byte[] b = new byte[4096];
|
||||||
|
for (int n; (n = input.read(b)) != -1; ) {
|
||||||
|
out.write(b, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package io.github.xxyopen.novel.core.interceptor;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.Entry;
|
||||||
|
import com.alibaba.csp.sentinel.EntryType;
|
||||||
|
import com.alibaba.csp.sentinel.SphU;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.core.common.util.IpUtils;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流量限制 拦截器
|
||||||
|
* 实现接口防刷和限流
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/6/1
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class FlowLimitInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* novel 项目所有的资源
|
||||||
|
*/
|
||||||
|
private static final String NOVEL_RESOURCE = "novelResource";
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 接口限流规则:所有的请求,限制每秒最多只能通过 2000 个,超出限制匀速排队
|
||||||
|
List<FlowRule> rules = new ArrayList<>();
|
||||||
|
FlowRule rule1 = new FlowRule();
|
||||||
|
rule1.setResource(NOVEL_RESOURCE);
|
||||||
|
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
|
||||||
|
// Set limit QPS to 2000.
|
||||||
|
rule1.setCount(2000);
|
||||||
|
rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
|
||||||
|
rules.add(rule1);
|
||||||
|
FlowRuleManager.loadRules(rules);
|
||||||
|
|
||||||
|
// 接口防刷规则 1:所有的请求,限制每个 IP 每秒最多只能通过 50 个,超出限制直接拒绝
|
||||||
|
ParamFlowRule rule2 = new ParamFlowRule(NOVEL_RESOURCE)
|
||||||
|
.setParamIdx(0)
|
||||||
|
.setCount(50);
|
||||||
|
// 接口防刷规则 2:所有的请求,限制每个 IP 每分钟最多只能通过 1000 个,超出限制直接拒绝
|
||||||
|
ParamFlowRule rule3 = new ParamFlowRule(NOVEL_RESOURCE)
|
||||||
|
.setParamIdx(0)
|
||||||
|
.setCount(1000)
|
||||||
|
.setDurationInSec(60);
|
||||||
|
ParamFlowRuleManager.loadRules(Arrays.asList(rule2, rule3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
String ip = IpUtils.getRealIp(request);
|
||||||
|
Entry entry = null;
|
||||||
|
try {
|
||||||
|
// 若需要配置例外项,则传入的参数只支持基本类型。
|
||||||
|
// EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效
|
||||||
|
// count 大多数情况都填 1,代表统计为一次调用。
|
||||||
|
entry = SphU.entry(NOVEL_RESOURCE, EntryType.IN, 1, ip);
|
||||||
|
// Your logic here.
|
||||||
|
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||||
|
} catch (BlockException ex) {
|
||||||
|
// Handle request rejection.
|
||||||
|
log.info("IP:{}被限流了!", ip);
|
||||||
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
response.getWriter().write(objectMapper.writeValueAsString(RestResp.fail(ErrorCodeEnum.USER_REQ_MANY)));
|
||||||
|
} finally {
|
||||||
|
// 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。
|
||||||
|
if (entry != null) {
|
||||||
|
entry.exit(1, ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package io.github.xxyopen.novel.core.interceptor;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.auth.UserHolder;
|
||||||
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
|
import io.github.xxyopen.novel.core.util.JwtUtils;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token 解析拦截器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/27
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TokenParseInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private final JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
// 获取登录 JWT
|
||||||
|
String token = request.getHeader(SystemConfigConsts.HTTP_AUTH_HEADER_NAME);
|
||||||
|
if (StringUtils.hasText(token)) {
|
||||||
|
// 解析 token 并保存
|
||||||
|
UserHolder.setUserId(jwtUtils.parseToken(token, SystemConfigConsts.NOVEL_FRONT_KEY));
|
||||||
|
}
|
||||||
|
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||||
|
// 清理当前线程保存的用户数据
|
||||||
|
UserHolder.clear();
|
||||||
|
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package io.github.xxyopen.novel.core.json.deserializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import org.springframework.boot.jackson.JsonComponent;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 全局反序列化器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/21
|
||||||
|
*/
|
||||||
|
@JsonComponent
|
||||||
|
public class GlobalJsonDeserializer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串反序列化器
|
||||||
|
* 过滤特殊字符,解决 XSS 攻击
|
||||||
|
*/
|
||||||
|
public static class StringDeserializer extends JsonDeserializer<String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||||
|
return jsonParser.getValueAsString()
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package io.github.xxyopen.novel.core.listener;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch.core.IndexResponse;
|
||||||
|
import io.github.xxyopen.novel.core.constant.AmqpConsts;
|
||||||
|
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.amqp.rabbit.annotation.RabbitListener;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rabbit 队列监听器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/25
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConditionalOnProperty(prefix = "spring", name = {"elasticsearch.enable","amqp.enable"}, havingValue = "true")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class RabbitQueueListener {
|
||||||
|
|
||||||
|
private final BookInfoMapper bookInfoMapper;
|
||||||
|
|
||||||
|
private final ElasticsearchClient esClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听小说信息改变的 ES 更新队列,更新最新小说信息到 ES
|
||||||
|
* */
|
||||||
|
@RabbitListener(queues = AmqpConsts.BookChangeMq.QUEUE_ES_UPDATE)
|
||||||
|
@SneakyThrows
|
||||||
|
public void updateEsBook(Long bookId) {
|
||||||
|
BookInfo bookInfo = bookInfoMapper.selectById(bookId);
|
||||||
|
IndexResponse response = esClient.index(i -> i
|
||||||
|
.index(EsConsts.BookIndex.INDEX_NAME)
|
||||||
|
.id(bookInfo.getId().toString())
|
||||||
|
.document(EsBookDto.build(bookInfo))
|
||||||
|
);
|
||||||
|
log.info("Indexed with version " + response.version());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package io.github.xxyopen.novel.core.task;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch._types.Time;
|
||||||
|
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 com.xxl.job.core.biz.model.ReturnT;
|
||||||
|
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||||
|
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.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
|
||||||
|
@XxlJob("saveToEsJobHandler")
|
||||||
|
public ReturnT<String> saveToEs() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
||||||
|
List<BookInfo> bookInfos;
|
||||||
|
long maxId = 0;
|
||||||
|
for (; ; ) {
|
||||||
|
queryWrapper.clear();
|
||||||
|
queryWrapper
|
||||||
|
.orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
|
||||||
|
.gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId)
|
||||||
|
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||||
|
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
||||||
|
bookInfos = bookInfoMapper.selectList(queryWrapper);
|
||||||
|
if (bookInfos.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BulkRequest.Builder br = new BulkRequest.Builder();
|
||||||
|
|
||||||
|
for (BookInfo book : bookInfos) {
|
||||||
|
br.operations(op -> op
|
||||||
|
.index(idx -> idx
|
||||||
|
.index(EsConsts.BookIndex.INDEX_NAME)
|
||||||
|
.id(book.getId().toString())
|
||||||
|
.document(EsBookDto.build(book))
|
||||||
|
)
|
||||||
|
).timeout(Time.of(t -> t.time("10s")));
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ReturnT.SUCCESS;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return ReturnT.FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -21,6 +21,8 @@ public class AuthorInfoDto implements Serializable {
|
|||||||
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
private String penName;
|
||||||
|
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
130
src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java
Normal file
130
src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.es;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elasticsearch 存储小说 DTO
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最新章节更新时间
|
||||||
|
*/
|
||||||
|
private Long lastChapterUpdateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
private Integer isVip;
|
||||||
|
|
||||||
|
public static EsBookDto build(BookInfo bookInfo){
|
||||||
|
return EsBookDto.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家注册 请求DTO
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AuthorRegisterReqDto {
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔名
|
||||||
|
*/
|
||||||
|
@NotBlank(message="笔名不能为空!")
|
||||||
|
private String penName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号码
|
||||||
|
*/
|
||||||
|
@NotBlank(message="手机号不能为空!")
|
||||||
|
@Pattern(regexp="^1[3|4|5|6|7|8|9][0-9]{9}$",message="手机号格式不正确!")
|
||||||
|
private String telPhone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QQ或微信账号
|
||||||
|
*/
|
||||||
|
@NotBlank(message="QQ或微信账号不能为空!")
|
||||||
|
private String chatAccount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电子邮箱
|
||||||
|
*/
|
||||||
|
@NotBlank(message="电子邮箱不能为空!")
|
||||||
|
@Email(message="邮箱格式不正确!")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作品方向;0-男频 1-女频
|
||||||
|
*/
|
||||||
|
@NotNull(message="作品方向不能为空!")
|
||||||
|
@Min(0)
|
||||||
|
@Max(1)
|
||||||
|
private Integer workDirection;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说发布 请求DTO
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BookAddReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作品方向;0-男频 1-女频
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private Integer workDirection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类别ID
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private Long categoryId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类别名
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
private String categoryName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说封面地址
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
private String picUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说名
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
private String bookName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 书籍描述
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
private String bookDesc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private Integer isVip;
|
||||||
|
}
|
@ -24,7 +24,7 @@ public class BookSearchReqDto extends PageReqDto {
|
|||||||
/**
|
/**
|
||||||
* 作品方向
|
* 作品方向
|
||||||
*/
|
*/
|
||||||
private Byte workDirection;
|
private Integer workDirection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分类ID
|
* 分类ID
|
||||||
@ -34,12 +34,12 @@ public class BookSearchReqDto extends PageReqDto {
|
|||||||
/**
|
/**
|
||||||
* 是否收费,1:收费,0:免费
|
* 是否收费,1:收费,0:免费
|
||||||
*/
|
*/
|
||||||
private Byte isVip;
|
private Integer isVip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说更新状态,0:连载中,1:已完结
|
* 小说更新状态,0:连载中,1:已完结
|
||||||
*/
|
*/
|
||||||
private Byte bookStatus;
|
private Integer bookStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字数最小值
|
* 字数最小值
|
||||||
@ -64,5 +64,5 @@ public class BookSearchReqDto extends PageReqDto {
|
|||||||
/**
|
/**
|
||||||
* 排序字段
|
* 排序字段
|
||||||
*/
|
*/
|
||||||
private String sort = "last_chapter_update_time desc";
|
private String sort;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节发布 请求DTO
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ChapterAddReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说ID
|
||||||
|
*/
|
||||||
|
private Long bookId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节名
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
private String chapterName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节内容
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
@Length(min = 50)
|
||||||
|
private String chapterContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private Integer isVip;
|
||||||
|
|
||||||
|
}
|
@ -19,7 +19,7 @@ public class UserInfoUptReqDto {
|
|||||||
@Length(min = 2,max = 10)
|
@Length(min = 2,max = 10)
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
@Pattern(regexp="^/[^\s]{10,}\\.(png|jpg|jpeg|gif|bpm)$")
|
@Pattern(regexp="^/[^\s]{10,}\\.(png|PNG|jpg|JPG|jpeg|JPEG|gif|GIF|bpm|BPM)$")
|
||||||
private String userPhoto;
|
private String userPhoto;
|
||||||
|
|
||||||
@Min(value = 0)
|
@Min(value = 0)
|
||||||
|
@ -52,4 +52,9 @@ public class BookChapterRespDto implements Serializable {
|
|||||||
@JsonFormat(pattern = "yyyy/MM/dd HH:dd")
|
@JsonFormat(pattern = "yyyy/MM/dd HH:dd")
|
||||||
private LocalDateTime chapterUpdateTime;
|
private LocalDateTime chapterUpdateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
private Integer isVip;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import java.util.List;
|
|||||||
@Builder
|
@Builder
|
||||||
public class BookCommentRespDto {
|
public class BookCommentRespDto {
|
||||||
|
|
||||||
|
|
||||||
private Long commentTotal;
|
private Long commentTotal;
|
||||||
|
|
||||||
private List<CommentInfo> comments;
|
private List<CommentInfo> comments;
|
||||||
@ -36,11 +35,11 @@ public class BookCommentRespDto {
|
|||||||
|
|
||||||
private Long commentUserId;
|
private Long commentUserId;
|
||||||
|
|
||||||
|
private String commentUserPhoto;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private LocalDateTime commentTime;
|
private LocalDateTime commentTime;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
import lombok.Builder;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息 响应DTO
|
* 小说信息 响应DTO
|
||||||
@ -10,6 +12,8 @@ import lombok.Data;
|
|||||||
* @date 2022/5/15
|
* @date 2022/5/15
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
@Builder
|
@Builder
|
||||||
public class BookInfoRespDto {
|
public class BookInfoRespDto {
|
||||||
|
|
||||||
@ -88,5 +92,11 @@ public class BookInfoRespDto {
|
|||||||
*/
|
*/
|
||||||
private String lastChapterName;
|
private String lastChapterName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最新章节更新时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息 响应DTO
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/22
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class UserInfoRespDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
* */
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户头像
|
||||||
|
* */
|
||||||
|
private String userPhoto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户性别
|
||||||
|
* */
|
||||||
|
private Integer userSex;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -7,6 +7,7 @@ import io.github.xxyopen.novel.dao.entity.AuthorInfo;
|
|||||||
import io.github.xxyopen.novel.dao.mapper.AuthorInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.AuthorInfoMapper;
|
||||||
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -27,8 +28,8 @@ public class AuthorInfoCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 查询作家信息,并放入缓存中
|
* 查询作家信息,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
, value = CacheConsts.AUTHOR_INFO_CACHE_NAME)
|
value = CacheConsts.AUTHOR_INFO_CACHE_NAME, unless = "#result == null")
|
||||||
public AuthorInfoDto getAuthor(Long userId) {
|
public AuthorInfoDto getAuthor(Long userId) {
|
||||||
QueryWrapper<AuthorInfo> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<AuthorInfo> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper
|
queryWrapper
|
||||||
@ -40,8 +41,14 @@ public class AuthorInfoCacheManager {
|
|||||||
}
|
}
|
||||||
return AuthorInfoDto.builder()
|
return AuthorInfoDto.builder()
|
||||||
.id(authorInfo.getId())
|
.id(authorInfo.getId())
|
||||||
|
.penName(authorInfo.getPenName())
|
||||||
.status(authorInfo.getStatus()).build();
|
.status(authorInfo.getStatus()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheEvict(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
|
value = CacheConsts.AUTHOR_INFO_CACHE_NAME)
|
||||||
|
public void evictAuthorCache() {
|
||||||
|
// 调用此方法自动清除作家信息的缓存
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -27,8 +27,8 @@ public class BookCategoryCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 根据作品方向查询小说分类列表,并放入缓存中
|
* 根据作品方向查询小说分类列表,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_CATEGORY_LIST_CACHE_NAME)
|
value = CacheConsts.BOOK_CATEGORY_LIST_CACHE_NAME)
|
||||||
public List<BookCategoryRespDto> listCategory(Integer workDirection) {
|
public List<BookCategoryRespDto> listCategory(Integer workDirection) {
|
||||||
QueryWrapper<BookCategory> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookCategory> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq(DatabaseConsts.BookCategoryTable.COLUMN_WORK_DIRECTION, workDirection);
|
queryWrapper.eq(DatabaseConsts.BookCategoryTable.COLUMN_WORK_DIRECTION, workDirection);
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
import io.github.xxyopen.novel.dao.entity.BookChapter;
|
import io.github.xxyopen.novel.dao.entity.BookChapter;
|
||||||
@ -23,8 +23,8 @@ public class BookChapterCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 查询小说章节信息,并放入缓存中
|
* 查询小说章节信息,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_CHAPTER_CACHE_NAME)
|
value = CacheConsts.BOOK_CHAPTER_CACHE_NAME)
|
||||||
public BookChapterRespDto getChapter(Long chapterId) {
|
public BookChapterRespDto getChapter(Long chapterId) {
|
||||||
BookChapter bookChapter = bookChapterMapper.selectById(chapterId);
|
BookChapter bookChapter = bookChapterMapper.selectById(chapterId);
|
||||||
return BookChapterRespDto.builder()
|
return BookChapterRespDto.builder()
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -24,8 +24,8 @@ public class BookContentCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 查询小说内容,并放入缓存中
|
* 查询小说内容,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_CONTENT_CACHE_NAME)
|
value = CacheConsts.BOOK_CONTENT_CACHE_NAME)
|
||||||
public String getBookContent(Long chapterId) {
|
public String getBookContent(Long chapterId) {
|
||||||
QueryWrapper<BookContent> contentQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookContent> contentQueryWrapper = new QueryWrapper<>();
|
||||||
contentQueryWrapper.eq(DatabaseConsts.BookContentTable.COLUMN_CHAPTER_ID, chapterId)
|
contentQueryWrapper.eq(DatabaseConsts.BookContentTable.COLUMN_CHAPTER_ID, chapterId)
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -9,6 +9,8 @@ import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
|||||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
|
import org.springframework.cache.annotation.CachePut;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -29,11 +31,20 @@ public class BookInfoCacheManager {
|
|||||||
private final BookChapterMapper bookChapterMapper;
|
private final BookChapterMapper bookChapterMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询小说信息,并放入缓存中
|
* 从缓存中查询小说信息(先判断缓存中是否已存在,存在则直接从缓存中取,否则执行方法体中的逻辑后缓存结果)
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||||
public BookInfoRespDto getBookInfo(Long id) {
|
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);
|
BookInfo bookInfo = bookInfoMapper.selectById(id);
|
||||||
// 查询首章ID
|
// 查询首章ID
|
||||||
@ -62,14 +73,21 @@ public class BookInfoCacheManager {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheEvict(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
|
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||||
|
public void evictBookInfoCache(Long ignoredId) {
|
||||||
|
// 调用此方法自动清除小说信息的缓存
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询每个类别下最新更新的 500 个小说ID列表,并放入缓存中 1 个小时
|
* 查询每个类别下最新更新的 500 个小说ID列表,并放入缓存中 1 个小时
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME)
|
value = CacheConsts.LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME)
|
||||||
public List<Long> getLastUpdateIdList(Long categoryId) {
|
public List<Long> getLastUpdateIdList(Long categoryId) {
|
||||||
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq(DatabaseConsts.BookTable.COLUMN_CATEGORY_ID, categoryId)
|
queryWrapper.eq(DatabaseConsts.BookTable.COLUMN_CATEGORY_ID, categoryId)
|
||||||
|
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||||
.orderByDesc(DatabaseConsts.BookTable.COLUMN_LAST_CHAPTER_UPDATE_TIME)
|
.orderByDesc(DatabaseConsts.BookTable.COLUMN_LAST_CHAPTER_UPDATE_TIME)
|
||||||
.last(DatabaseConsts.SqlEnum.LIMIT_500.getSql());
|
.last(DatabaseConsts.SqlEnum.LIMIT_500.getSql());
|
||||||
return bookInfoMapper.selectList(queryWrapper).stream().map(BookInfo::getId).toList();
|
return bookInfoMapper.selectList(queryWrapper).stream().map(BookInfo::getId).toList();
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -27,40 +27,44 @@ public class BookRankCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 查询小说点击榜列表,并放入缓存中
|
* 查询小说点击榜列表,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_VISIT_RANK_CACHE_NAME)
|
value = CacheConsts.BOOK_VISIT_RANK_CACHE_NAME)
|
||||||
public List<BookRankRespDto> listVisitRankBooks() {
|
public List<BookRankRespDto> listVisitRankBooks() {
|
||||||
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||||
bookInfoQueryWrapper.orderByDesc(DatabaseConsts.BookTable.COLUMN_VISIT_COUNT);
|
bookInfoQueryWrapper.orderByDesc(DatabaseConsts.BookTable.COLUMN_VISIT_COUNT);
|
||||||
return getBookRankRespDtos(bookInfoQueryWrapper);
|
return listRankBooks(bookInfoQueryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询小说新书榜列表,并放入缓存中
|
* 查询小说新书榜列表,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_NEWEST_RANK_CACHE_NAME)
|
value = CacheConsts.BOOK_NEWEST_RANK_CACHE_NAME)
|
||||||
public List<BookRankRespDto> listNewestRankBooks() {
|
public List<BookRankRespDto> listNewestRankBooks() {
|
||||||
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||||
bookInfoQueryWrapper
|
bookInfoQueryWrapper
|
||||||
|
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
|
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
|
||||||
return getBookRankRespDtos(bookInfoQueryWrapper);
|
return listRankBooks(bookInfoQueryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询小说更新榜列表,并放入缓存中
|
* 查询小说更新榜列表,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_UPDATE_RANK_CACHE_NAME)
|
value = CacheConsts.BOOK_UPDATE_RANK_CACHE_NAME)
|
||||||
public List<BookRankRespDto> listUpdateRankBooks() {
|
public List<BookRankRespDto> listUpdateRankBooks() {
|
||||||
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||||
bookInfoQueryWrapper
|
bookInfoQueryWrapper
|
||||||
|
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.UPDATE_TIME.getName());
|
.orderByDesc(DatabaseConsts.CommonColumnEnum.UPDATE_TIME.getName());
|
||||||
return getBookRankRespDtos(bookInfoQueryWrapper);
|
return listRankBooks(bookInfoQueryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<BookRankRespDto> getBookRankRespDtos(QueryWrapper<BookInfo> bookInfoQueryWrapper) {
|
private List<BookRankRespDto> listRankBooks(QueryWrapper<BookInfo> bookInfoQueryWrapper) {
|
||||||
bookInfoQueryWrapper.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
bookInfoQueryWrapper
|
||||||
|
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||||
|
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
||||||
return bookInfoMapper.selectList(bookInfoQueryWrapper).stream().map(v -> {
|
return bookInfoMapper.selectList(bookInfoQueryWrapper).stream().map(v -> {
|
||||||
BookRankRespDto respDto = new BookRankRespDto();
|
BookRankRespDto respDto = new BookRankRespDto();
|
||||||
respDto.setId(v.getId());
|
respDto.setId(v.getId());
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -27,8 +27,8 @@ public class FriendLinkCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 友情链接列表查询,并放入缓存中
|
* 友情链接列表查询,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
, value = CacheConsts.HOME_FRIEND_LINK_CACHE_NAME)
|
value = CacheConsts.HOME_FRIEND_LINK_CACHE_NAME)
|
||||||
public List<HomeFriendLinkRespDto> listFriendLinks() {
|
public List<HomeFriendLinkRespDto> listFriendLinks() {
|
||||||
// 从友情链接表中查询出友情链接列表
|
// 从友情链接表中查询出友情链接列表
|
||||||
QueryWrapper<HomeFriendLink> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<HomeFriendLink> queryWrapper = new QueryWrapper<>();
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -36,8 +36,8 @@ public class HomeBookCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 查询首页小说推荐,并放入缓存中
|
* 查询首页小说推荐,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.HOME_BOOK_CACHE_NAME)
|
value = CacheConsts.HOME_BOOK_CACHE_NAME)
|
||||||
public List<HomeBookRespDto> listHomeBooks() {
|
public List<HomeBookRespDto> listHomeBooks() {
|
||||||
// 从首页小说推荐表中查询出需要推荐的小说
|
// 从首页小说推荐表中查询出需要推荐的小说
|
||||||
QueryWrapper<HomeBook> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<HomeBook> queryWrapper = new QueryWrapper<>();
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -27,8 +27,8 @@ public class NewsCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 最新新闻列表查询,并放入缓存中
|
* 最新新闻列表查询,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.LATEST_NEWS_CACHE_NAME)
|
value = CacheConsts.LATEST_NEWS_CACHE_NAME)
|
||||||
public List<NewsInfoRespDto> listLatestNews() {
|
public List<NewsInfoRespDto> listLatestNews() {
|
||||||
// 从新闻信息表中查询出最新发布的两条新闻
|
// 从新闻信息表中查询出最新发布的两条新闻
|
||||||
QueryWrapper<NewsInfo> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<NewsInfo> queryWrapper = new QueryWrapper<>();
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.cache;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
||||||
@ -25,11 +25,11 @@ public class UserInfoCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 查询用户信息,并放入缓存中
|
* 查询用户信息,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
, value = CacheConsts.USER_INFO_CACHE_NAME)
|
value = CacheConsts.USER_INFO_CACHE_NAME)
|
||||||
public UserInfoDto getUser(Long userId) {
|
public UserInfoDto getUser(Long userId) {
|
||||||
UserInfo userInfo = userInfoMapper.selectById(userId);
|
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||||
if(Objects.isNull(userInfo)){
|
if (Objects.isNull(userInfo)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return UserInfoDto.builder()
|
return UserInfoDto.builder()
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.dao;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
@ -0,0 +1,52 @@
|
|||||||
|
package io.github.xxyopen.novel.manager.mq;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.constant.CommonConsts;
|
||||||
|
import io.github.xxyopen.novel.core.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;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMQP 消息管理类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/25
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AmqpMsgManager {
|
||||||
|
|
||||||
|
private final AmqpTemplate amqpTemplate;
|
||||||
|
|
||||||
|
@Value("${spring.amqp.enable}")
|
||||||
|
private String enableAmqp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送小说信息改变消息
|
||||||
|
*/
|
||||||
|
public void sendBookChangeMsg(Long bookId) {
|
||||||
|
if (Objects.equals(enableAmqp, CommonConsts.TRUE)) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.xxyopen.novel.manager;
|
package io.github.xxyopen.novel.manager.redis;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.util.ImgVerifyCodeUtils;
|
import io.github.xxyopen.novel.core.common.util.ImgVerifyCodeUtils;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
@ -25,27 +25,25 @@ public class VerifyCodeManager {
|
|||||||
private final StringRedisTemplate stringRedisTemplate;
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成图片验证码,并放入缓存中
|
* 生成图形验证码,并放入 Redis 中
|
||||||
*/
|
*/
|
||||||
public String genImgVerifyCode(String sessionId) throws IOException {
|
public String genImgVerifyCode(String sessionId) throws IOException {
|
||||||
String verifyCode = ImgVerifyCodeUtils.getRandomVerifyCode(4);
|
String verifyCode = ImgVerifyCodeUtils.getRandomVerifyCode(4);
|
||||||
String img = ImgVerifyCodeUtils.genVerifyCodeImg(verifyCode);
|
String img = ImgVerifyCodeUtils.genVerifyCodeImg(verifyCode);
|
||||||
stringRedisTemplate.opsForValue().set(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId
|
stringRedisTemplate.opsForValue().set(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId,
|
||||||
, verifyCode, Duration.ofMinutes(5));
|
verifyCode, Duration.ofMinutes(5));
|
||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验图片验证码
|
* 校验图形验证码
|
||||||
*/
|
*/
|
||||||
public boolean imgVerifyCodeOk(String sessionId, String verifyCode) {
|
public boolean imgVerifyCodeOk(String sessionId, String verifyCode) {
|
||||||
return Objects.equals(
|
return Objects.equals(stringRedisTemplate.opsForValue().get(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId), verifyCode);
|
||||||
stringRedisTemplate.opsForValue().get(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId)
|
|
||||||
, verifyCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除验证码
|
* 从 Redis 中删除验证码
|
||||||
*/
|
*/
|
||||||
public void removeImgVerifyCode(String sessionId) {
|
public void removeImgVerifyCode(String sessionId) {
|
||||||
stringRedisTemplate.delete(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId);
|
stringRedisTemplate.delete(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId);
|
@ -0,0 +1,28 @@
|
|||||||
|
package io.github.xxyopen.novel.service;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.dto.req.AuthorRegisterReqDto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家模块 业务服务类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
public interface AuthorService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家注册
|
||||||
|
*
|
||||||
|
* @param dto 注册参数
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
RestResp<Void> register(AuthorRegisterReqDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询作家状态
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 作家状态
|
||||||
|
* */
|
||||||
|
RestResp<Integer> getStatus(Long userId);
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
package io.github.xxyopen.novel.service;
|
package io.github.xxyopen.novel.service;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.req.PageReqDto;
|
||||||
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.*;
|
import io.github.xxyopen.novel.dto.resp.*;
|
||||||
|
|
||||||
@ -17,14 +19,6 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface BookService {
|
public interface BookService {
|
||||||
|
|
||||||
/**
|
|
||||||
* 小说搜索
|
|
||||||
*
|
|
||||||
* @param condition 搜索条件
|
|
||||||
* @return 搜索结果
|
|
||||||
*/
|
|
||||||
RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说点击榜查询
|
* 小说点击榜查询
|
||||||
*
|
*
|
||||||
@ -136,18 +130,53 @@ public interface BookService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除评论
|
* 删除评论
|
||||||
* @param userId 评论用户ID
|
*
|
||||||
|
* @param userId 评论用户ID
|
||||||
* @param commentId 评论ID
|
* @param commentId 评论ID
|
||||||
* @return void
|
* @return void
|
||||||
* */
|
*/
|
||||||
RestResp<Void> deleteComment(Long userId, Long commentId);
|
RestResp<Void> deleteComment(Long userId, Long commentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评论
|
* 修改评论
|
||||||
* @param userId 用户ID
|
*
|
||||||
* @param id 评论ID
|
* @param userId 用户ID
|
||||||
|
* @param id 评论ID
|
||||||
* @param content 修改后的评论内容
|
* @param content 修改后的评论内容
|
||||||
* @return void
|
* @return void
|
||||||
* */
|
*/
|
||||||
RestResp<Void> updateComment(Long userId, Long id, String content);
|
RestResp<Void> updateComment(Long userId, Long id, String content);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说信息保存
|
||||||
|
*
|
||||||
|
* @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(PageReqDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询小说发布章节列表
|
||||||
|
*
|
||||||
|
* @param bookId 小说ID
|
||||||
|
* @param dto 分页请求参数
|
||||||
|
* @return 章节分页列表数据
|
||||||
|
*/
|
||||||
|
RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(Long bookId, PageReqDto dto);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package io.github.xxyopen.novel.service;
|
|||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import io.github.xxyopen.novel.dto.resp.ImgVerifyCodeRespDto;
|
import io.github.xxyopen.novel.dto.resp.ImgVerifyCodeRespDto;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -16,7 +17,15 @@ public interface ResourceService {
|
|||||||
/**
|
/**
|
||||||
* 获取图片验证码
|
* 获取图片验证码
|
||||||
*
|
*
|
||||||
|
* @throws IOException 验证码图片生成失败
|
||||||
* @return Base64编码的图片
|
* @return Base64编码的图片
|
||||||
*/
|
*/
|
||||||
RestResp<ImgVerifyCodeRespDto> getImgVerifyCode() throws IOException;
|
RestResp<ImgVerifyCodeRespDto> getImgVerifyCode() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片上传
|
||||||
|
* @param file 需要上传的图片
|
||||||
|
* @return 图片访问路径
|
||||||
|
* */
|
||||||
|
RestResp<String> uploadImage(MultipartFile file);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package io.github.xxyopen.novel.service;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索 服务类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
public interface SearchService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说搜索
|
||||||
|
*
|
||||||
|
* @param condition 搜索条件
|
||||||
|
* @return 搜索结果
|
||||||
|
*/
|
||||||
|
RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition);
|
||||||
|
|
||||||
|
}
|
@ -4,6 +4,7 @@ import io.github.xxyopen.novel.core.common.resp.RestResp;
|
|||||||
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.UserInfoRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
||||||
|
|
||||||
@ -65,4 +66,11 @@ public interface UserService {
|
|||||||
* @return 0-不在书架 1-已在书架
|
* @return 0-不在书架 1-已在书架
|
||||||
*/
|
*/
|
||||||
RestResp<Integer> getBookshelfStatus(Long userId, String bookId);
|
RestResp<Integer> getBookshelfStatus(Long userId, String bookId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息查询
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
RestResp<UserInfoRespDto> getUserInfo(Long userId);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package io.github.xxyopen.novel.service.impl;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.dao.entity.AuthorInfo;
|
||||||
|
import io.github.xxyopen.novel.dao.mapper.AuthorInfoMapper;
|
||||||
|
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
||||||
|
import io.github.xxyopen.novel.dto.req.AuthorRegisterReqDto;
|
||||||
|
import io.github.xxyopen.novel.manager.cache.AuthorInfoCacheManager;
|
||||||
|
import io.github.xxyopen.novel.service.AuthorService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家模块 服务实现类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class AuthorServiceImpl implements AuthorService {
|
||||||
|
|
||||||
|
private final AuthorInfoCacheManager authorInfoCacheManager;
|
||||||
|
|
||||||
|
private final AuthorInfoMapper authorInfoMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestResp<Void> register(AuthorRegisterReqDto dto) {
|
||||||
|
// 校验该用户是否已注册为作家
|
||||||
|
AuthorInfoDto author = authorInfoCacheManager.getAuthor(dto.getUserId());
|
||||||
|
if (Objects.nonNull(author)) {
|
||||||
|
// 该用户已经是作家,直接返回
|
||||||
|
return RestResp.ok();
|
||||||
|
}
|
||||||
|
// 保存作家注册信息
|
||||||
|
AuthorInfo authorInfo = new AuthorInfo();
|
||||||
|
authorInfo.setUserId(dto.getUserId());
|
||||||
|
authorInfo.setChatAccount(dto.getChatAccount());
|
||||||
|
authorInfo.setEmail(dto.getEmail());
|
||||||
|
authorInfo.setInviteCode("0");
|
||||||
|
authorInfo.setTelPhone(dto.getTelPhone());
|
||||||
|
authorInfo.setPenName(dto.getPenName());
|
||||||
|
authorInfo.setWorkDirection(dto.getWorkDirection());
|
||||||
|
authorInfo.setCreateTime(LocalDateTime.now());
|
||||||
|
authorInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
authorInfoMapper.insert(authorInfo);
|
||||||
|
// 清除作家缓存
|
||||||
|
authorInfoCacheManager.evictAuthorCache();
|
||||||
|
return RestResp.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestResp<Integer> getStatus(Long userId) {
|
||||||
|
AuthorInfoDto author = authorInfoCacheManager.getAuthor(userId);
|
||||||
|
return Objects.isNull(author) ? RestResp.ok(null) : RestResp.ok(author.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,31 +1,38 @@
|
|||||||
package io.github.xxyopen.novel.service.impl;
|
package io.github.xxyopen.novel.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import io.github.xxyopen.novel.core.auth.UserHolder;
|
||||||
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
||||||
|
import io.github.xxyopen.novel.core.common.req.PageReqDto;
|
||||||
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||||
import io.github.xxyopen.novel.dao.entity.BookChapter;
|
import io.github.xxyopen.novel.dao.entity.*;
|
||||||
import io.github.xxyopen.novel.dao.entity.BookComment;
|
|
||||||
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
|
||||||
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
|
||||||
import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
||||||
import io.github.xxyopen.novel.dao.mapper.BookCommentMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookCommentMapper;
|
||||||
|
import io.github.xxyopen.novel.dao.mapper.BookContentMapper;
|
||||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||||
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
||||||
|
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.*;
|
import io.github.xxyopen.novel.dto.resp.*;
|
||||||
import io.github.xxyopen.novel.manager.*;
|
import io.github.xxyopen.novel.manager.cache.*;
|
||||||
|
import io.github.xxyopen.novel.manager.dao.UserDaoManager;
|
||||||
|
import io.github.xxyopen.novel.manager.mq.AmqpMsgManager;
|
||||||
import io.github.xxyopen.novel.service.BookService;
|
import io.github.xxyopen.novel.service.BookService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,34 +56,21 @@ public class BookServiceImpl implements BookService {
|
|||||||
|
|
||||||
private final BookContentCacheManager bookContentCacheManager;
|
private final BookContentCacheManager bookContentCacheManager;
|
||||||
|
|
||||||
|
private final AuthorInfoCacheManager authorInfoCacheManager;
|
||||||
|
|
||||||
private final BookInfoMapper bookInfoMapper;
|
private final BookInfoMapper bookInfoMapper;
|
||||||
|
|
||||||
private final BookChapterMapper bookChapterMapper;
|
private final BookChapterMapper bookChapterMapper;
|
||||||
|
|
||||||
|
private final BookContentMapper bookContentMapper;
|
||||||
|
|
||||||
private final BookCommentMapper bookCommentMapper;
|
private final BookCommentMapper bookCommentMapper;
|
||||||
|
|
||||||
private final UserDaoManager userDaoManager;
|
private final UserDaoManager userDaoManager;
|
||||||
|
|
||||||
private static final Integer REC_BOOK_COUNT = 4;
|
private final AmqpMsgManager amqpMsgManager;
|
||||||
|
|
||||||
@Override
|
private static final Integer REC_BOOK_COUNT = 4;
|
||||||
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
|
||||||
Page<BookInfoRespDto> page = new Page<>();
|
|
||||||
page.setCurrent(condition.getPageNum());
|
|
||||||
page.setSize(condition.getPageSize());
|
|
||||||
List<BookInfo> bookInfos = bookInfoMapper.searchBooks(page, condition);
|
|
||||||
return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal()
|
|
||||||
, bookInfos.stream().map(v -> BookInfoRespDto.builder()
|
|
||||||
.id(v.getId())
|
|
||||||
.bookName(v.getBookName())
|
|
||||||
.categoryId(v.getCategoryId())
|
|
||||||
.categoryName(v.getCategoryName())
|
|
||||||
.authorId(v.getAuthorId())
|
|
||||||
.authorName(v.getAuthorName())
|
|
||||||
.wordCount(v.getWordCount())
|
|
||||||
.lastChapterName(v.getLastChapterName())
|
|
||||||
.build()).toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
|
public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
|
||||||
@ -197,6 +191,7 @@ public class BookServiceImpl implements BookService {
|
|||||||
return RestResp.ok(bookChapterMapper.selectList(queryWrapper).stream().map(v -> BookChapterRespDto.builder()
|
return RestResp.ok(bookChapterMapper.selectList(queryWrapper).stream().map(v -> BookChapterRespDto.builder()
|
||||||
.id(v.getId())
|
.id(v.getId())
|
||||||
.chapterName(v.getChapterName())
|
.chapterName(v.getChapterName())
|
||||||
|
.isVip(v.getIsVip())
|
||||||
.build()).toList());
|
.build()).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,9 +204,9 @@ public class BookServiceImpl implements BookService {
|
|||||||
public RestResp<Void> saveComment(UserCommentReqDto dto) {
|
public RestResp<Void> saveComment(UserCommentReqDto dto) {
|
||||||
// 校验用户是否已发表评论
|
// 校验用户是否已发表评论
|
||||||
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID,dto.getUserId())
|
queryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, dto.getUserId())
|
||||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID,dto.getBookId());
|
.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, dto.getBookId());
|
||||||
if(bookCommentMapper.selectCount(queryWrapper) > 0){
|
if (bookCommentMapper.selectCount(queryWrapper) > 0) {
|
||||||
// 用户已发表评论
|
// 用户已发表评论
|
||||||
return RestResp.fail(ErrorCodeEnum.USER_COMMENTED);
|
return RestResp.fail(ErrorCodeEnum.USER_COMMENTED);
|
||||||
}
|
}
|
||||||
@ -244,12 +239,13 @@ public class BookServiceImpl implements BookService {
|
|||||||
// 查询评论用户信息,并设置需要返回的评论用户名
|
// 查询评论用户信息,并设置需要返回的评论用户名
|
||||||
List<Long> userIds = bookComments.stream().map(BookComment::getUserId).toList();
|
List<Long> userIds = bookComments.stream().map(BookComment::getUserId).toList();
|
||||||
List<UserInfo> userInfos = userDaoManager.listUsers(userIds);
|
List<UserInfo> userInfos = userDaoManager.listUsers(userIds);
|
||||||
Map<Long, String> userInfoMap = userInfos.stream().collect(Collectors.toMap(UserInfo::getId, UserInfo::getUsername));
|
Map<Long, UserInfo> userInfoMap = userInfos.stream().collect(Collectors.toMap(UserInfo::getId, Function.identity()));
|
||||||
List<BookCommentRespDto.CommentInfo> commentInfos = bookComments.stream()
|
List<BookCommentRespDto.CommentInfo> commentInfos = bookComments.stream()
|
||||||
.map(v -> BookCommentRespDto.CommentInfo.builder()
|
.map(v -> BookCommentRespDto.CommentInfo.builder()
|
||||||
.id(v.getId())
|
.id(v.getId())
|
||||||
.commentUserId(v.getUserId())
|
.commentUserId(v.getUserId())
|
||||||
.commentUser(userInfoMap.get(v.getUserId()))
|
.commentUser(userInfoMap.get(v.getUserId()).getUsername())
|
||||||
|
.commentUserPhoto(userInfoMap.get(v.getUserId()).getUserPhoto())
|
||||||
.commentContent(v.getCommentContent())
|
.commentContent(v.getCommentContent())
|
||||||
.commentTime(v.getCreateTime()).build()).toList();
|
.commentTime(v.getCreateTime()).build()).toList();
|
||||||
bookCommentRespDto.setComments(commentInfos);
|
bookCommentRespDto.setComments(commentInfos);
|
||||||
@ -263,8 +259,8 @@ public class BookServiceImpl implements BookService {
|
|||||||
public RestResp<Void> deleteComment(Long userId, Long commentId) {
|
public RestResp<Void> deleteComment(Long userId, Long commentId) {
|
||||||
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), commentId)
|
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), commentId)
|
||||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID,userId);
|
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, userId);
|
||||||
bookCommentMapper.delete(queryWrapper);
|
bookCommentMapper.delete(queryWrapper);
|
||||||
return RestResp.ok();
|
return RestResp.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,15 +268,139 @@ public class BookServiceImpl implements BookService {
|
|||||||
public RestResp<Void> updateComment(Long userId, Long id, String content) {
|
public RestResp<Void> updateComment(Long userId, Long id, String content) {
|
||||||
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), id)
|
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), id)
|
||||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID,userId);
|
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, userId);
|
||||||
BookComment bookComment = new BookComment();
|
BookComment bookComment = new BookComment();
|
||||||
bookComment.setCommentContent(content);
|
bookComment.setCommentContent(content);
|
||||||
bookCommentMapper.update(bookComment,queryWrapper);
|
bookCommentMapper.update(bookComment, queryWrapper);
|
||||||
return RestResp.ok();
|
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();
|
||||||
|
// 设置作家信息
|
||||||
|
AuthorInfoDto author = authorInfoCacheManager.getAuthor(UserHolder.getUserId());
|
||||||
|
bookInfo.setAuthorId(author.getId());
|
||||||
|
bookInfo.setAuthorName(author.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(), UserHolder.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(PageReqDto 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, UserHolder.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(Long bookId, PageReqDto 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, bookId)
|
||||||
|
.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
|
@Override
|
||||||
public RestResp<BookContentAboutRespDto> getBookContentAbout(Long chapterId) {
|
public RestResp<BookContentAboutRespDto> getBookContentAbout(Long chapterId) {
|
||||||
|
log.debug("userId:{}", UserHolder.getUserId());
|
||||||
// 查询章节信息
|
// 查询章节信息
|
||||||
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(chapterId);
|
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(chapterId);
|
||||||
|
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package io.github.xxyopen.novel.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||||
|
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||||
|
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
import io.github.xxyopen.novel.service.SearchService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库搜索 服务实现类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "false")
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class DbSearchServiceImpl implements SearchService {
|
||||||
|
|
||||||
|
private final BookInfoMapper bookInfoMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
||||||
|
Page<BookInfoRespDto> page = new Page<>();
|
||||||
|
page.setCurrent(condition.getPageNum());
|
||||||
|
page.setSize(condition.getPageSize());
|
||||||
|
List<BookInfo> bookInfos = bookInfoMapper.searchBooks(page, condition);
|
||||||
|
return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal(),
|
||||||
|
bookInfos.stream().map(v -> BookInfoRespDto.builder()
|
||||||
|
.id(v.getId())
|
||||||
|
.bookName(v.getBookName())
|
||||||
|
.categoryId(v.getCategoryId())
|
||||||
|
.categoryName(v.getCategoryName())
|
||||||
|
.authorId(v.getAuthorId())
|
||||||
|
.authorName(v.getAuthorName())
|
||||||
|
.wordCount(v.getWordCount())
|
||||||
|
.lastChapterName(v.getLastChapterName())
|
||||||
|
.build()).toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
package io.github.xxyopen.novel.service.impl;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch._types.SortOrder;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
|
||||||
|
import co.elastic.clients.elasticsearch.core.SearchRequest;
|
||||||
|
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.TotalHits;
|
||||||
|
import co.elastic.clients.json.JsonData;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.core.constant.EsConsts;
|
||||||
|
import io.github.xxyopen.novel.dto.es.EsBookDto;
|
||||||
|
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
import io.github.xxyopen.novel.service.SearchService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elasticsearch 搜索 服务实现类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true")
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class EsSearchServiceImpl implements SearchService {
|
||||||
|
|
||||||
|
private final ElasticsearchClient esClient;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@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(StringUtils.underlineToCamel(condition.getSort().split(" ")[0]))
|
||||||
|
.order(SortOrder.Desc))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 分页
|
||||||
|
searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize())
|
||||||
|
.size(condition.getPageSize());
|
||||||
|
// 设置高亮显示
|
||||||
|
searchBuilder.highlight(h -> h.fields(EsConsts.BookIndex.FIELD_BOOK_NAME,
|
||||||
|
t -> t.preTags("<em style='color:red'>").postTags("</em>"))
|
||||||
|
.fields(EsConsts.BookIndex.FIELD_AUTHOR_NAME,
|
||||||
|
t -> t.preTags("<em style='color:red'>").postTags("</em>")));
|
||||||
|
|
||||||
|
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();
|
||||||
|
assert book != null;
|
||||||
|
if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME))) {
|
||||||
|
book.setBookName(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME).get(0));
|
||||||
|
}
|
||||||
|
if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME))) {
|
||||||
|
book.setAuthorName(hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME).get(0));
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
assert total != null;
|
||||||
|
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 -> {
|
||||||
|
|
||||||
|
// 只查有字数的小说
|
||||||
|
b.must(RangeQuery.of(m -> m
|
||||||
|
.field(EsConsts.BookIndex.FIELD_WORD_COUNT)
|
||||||
|
.gt(JsonData.of(0))
|
||||||
|
)._toQuery());
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,8 @@ package io.github.xxyopen.novel.service.impl;
|
|||||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import io.github.xxyopen.novel.dto.resp.HomeBookRespDto;
|
import io.github.xxyopen.novel.dto.resp.HomeBookRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.HomeFriendLinkRespDto;
|
import io.github.xxyopen.novel.dto.resp.HomeFriendLinkRespDto;
|
||||||
import io.github.xxyopen.novel.manager.FriendLinkCacheManager;
|
import io.github.xxyopen.novel.manager.cache.FriendLinkCacheManager;
|
||||||
import io.github.xxyopen.novel.manager.HomeBookCacheManager;
|
import io.github.xxyopen.novel.manager.cache.HomeBookCacheManager;
|
||||||
import io.github.xxyopen.novel.service.HomeService;
|
import io.github.xxyopen.novel.service.HomeService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -8,7 +8,7 @@ import io.github.xxyopen.novel.dao.entity.NewsInfo;
|
|||||||
import io.github.xxyopen.novel.dao.mapper.NewsContentMapper;
|
import io.github.xxyopen.novel.dao.mapper.NewsContentMapper;
|
||||||
import io.github.xxyopen.novel.dao.mapper.NewsInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.NewsInfoMapper;
|
||||||
import io.github.xxyopen.novel.dto.resp.NewsInfoRespDto;
|
import io.github.xxyopen.novel.dto.resp.NewsInfoRespDto;
|
||||||
import io.github.xxyopen.novel.manager.NewsCacheManager;
|
import io.github.xxyopen.novel.manager.cache.NewsCacheManager;
|
||||||
import io.github.xxyopen.novel.service.NewsService;
|
import io.github.xxyopen.novel.service.NewsService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -1,14 +1,27 @@
|
|||||||
package io.github.xxyopen.novel.service.impl;
|
package io.github.xxyopen.novel.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
|
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
||||||
|
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
||||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
import io.github.xxyopen.novel.dto.resp.ImgVerifyCodeRespDto;
|
import io.github.xxyopen.novel.dto.resp.ImgVerifyCodeRespDto;
|
||||||
import io.github.xxyopen.novel.manager.VerifyCodeManager;
|
import io.github.xxyopen.novel.manager.redis.VerifyCodeManager;
|
||||||
import io.github.xxyopen.novel.service.ResourceService;
|
import io.github.xxyopen.novel.service.ResourceService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源(图片/视频/文档)相关服务实现类
|
* 资源(图片/视频/文档)相关服务实现类
|
||||||
@ -18,10 +31,14 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class ResourceServiceImpl implements ResourceService {
|
public class ResourceServiceImpl implements ResourceService {
|
||||||
|
|
||||||
private final VerifyCodeManager verifyCodeManager;
|
private final VerifyCodeManager verifyCodeManager;
|
||||||
|
|
||||||
|
@Value("${novel.file.upload.path}")
|
||||||
|
private String fileUploadPath;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RestResp<ImgVerifyCodeRespDto> getImgVerifyCode() throws IOException {
|
public RestResp<ImgVerifyCodeRespDto> getImgVerifyCode() throws IOException {
|
||||||
String sessionId = IdWorker.get32UUID();
|
String sessionId = IdWorker.get32UUID();
|
||||||
@ -31,4 +48,32 @@ public class ResourceServiceImpl implements ResourceService {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public RestResp<String> uploadImage(MultipartFile file) {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
String savePath =
|
||||||
|
SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY
|
||||||
|
+ now.format(DateTimeFormatter.ofPattern("yyyy")) + File.separator
|
||||||
|
+ now.format(DateTimeFormatter.ofPattern("MM")) + File.separator
|
||||||
|
+ now.format(DateTimeFormatter.ofPattern("dd"));
|
||||||
|
String oriName = file.getOriginalFilename();
|
||||||
|
assert oriName != null;
|
||||||
|
String saveFileName = IdWorker.get32UUID() + oriName.substring(oriName.lastIndexOf("."));
|
||||||
|
File saveFile = new File(fileUploadPath + savePath, saveFileName);
|
||||||
|
if (!saveFile.getParentFile().exists()) {
|
||||||
|
boolean isSuccess = saveFile.getParentFile().mkdirs();
|
||||||
|
if (!isSuccess) {
|
||||||
|
throw new BusinessException(ErrorCodeEnum.USER_UPLOAD_FILE_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.transferTo(saveFile);
|
||||||
|
if (Objects.isNull(ImageIO.read(saveFile))) {
|
||||||
|
// 上传的文件不是图片
|
||||||
|
Files.delete(saveFile.toPath());
|
||||||
|
throw new BusinessException(ErrorCodeEnum.USER_UPLOAD_FILE_TYPE_NOT_MATCH);
|
||||||
|
}
|
||||||
|
return RestResp.ok(savePath + File.separator + saveFileName);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,10 @@ import io.github.xxyopen.novel.dao.mapper.UserInfoMapper;
|
|||||||
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.UserInfoRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
||||||
import io.github.xxyopen.novel.manager.VerifyCodeManager;
|
import io.github.xxyopen.novel.manager.redis.VerifyCodeManager;
|
||||||
import io.github.xxyopen.novel.service.UserService;
|
import io.github.xxyopen.novel.service.UserService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -157,4 +158,14 @@ public class UserServiceImpl implements UserService {
|
|||||||
: CommonConsts.NO
|
: CommonConsts.NO
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestResp<UserInfoRespDto> getUserInfo(Long userId) {
|
||||||
|
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||||
|
return RestResp.ok(UserInfoRespDto.builder()
|
||||||
|
.nickName(userInfo.getNickName())
|
||||||
|
.userSex(userInfo.getUserSex())
|
||||||
|
.userPhoto(userInfo.getUserPhoto())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,28 +6,81 @@ spring:
|
|||||||
jackson:
|
jackson:
|
||||||
generator:
|
generator:
|
||||||
write-numbers-as-strings: true
|
write-numbers-as-strings: true
|
||||||
|
servlet:
|
||||||
|
# 上传文件最大大小
|
||||||
|
multipart:
|
||||||
|
max-file-size: 5MB
|
||||||
|
|
||||||
server:
|
server:
|
||||||
port: 8888
|
port: 8888
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:mysql://localhost:3306/novel_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
url: jdbc:mysql://localhost:3306/novel_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
username: root
|
username: root
|
||||||
password: test123456
|
password: test123456
|
||||||
|
# ShardingSphere-JDBC 配置
|
||||||
|
# 配置是 ShardingSphere-JDBC 中唯一与应用开发者交互的模块,
|
||||||
|
# 通过它可以快速清晰的理解 ShardingSphere-JDBC 所提供的功能。
|
||||||
|
shardingsphere:
|
||||||
|
# 是否开启分库分表
|
||||||
|
enabled: false
|
||||||
|
props:
|
||||||
|
# 是否在日志中打印 SQL
|
||||||
|
sql-show: true
|
||||||
|
# 模式配置
|
||||||
|
mode:
|
||||||
|
# 单机模式
|
||||||
|
type: Standalone
|
||||||
|
repository:
|
||||||
|
# 文件持久化
|
||||||
|
type: File
|
||||||
|
props:
|
||||||
|
# 元数据存储路径
|
||||||
|
path: .shardingsphere
|
||||||
|
# 使用本地配置覆盖持久化配置
|
||||||
|
overwrite: true
|
||||||
|
# 数据源配置
|
||||||
|
datasource:
|
||||||
|
names: ds_0
|
||||||
|
ds_0:
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
|
jdbcUrl: jdbc:mysql://localhost:3306/novel_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
|
username: root
|
||||||
|
password: test123456
|
||||||
|
# 规则配置
|
||||||
|
rules:
|
||||||
|
# 数据分片
|
||||||
|
sharding:
|
||||||
|
tables:
|
||||||
|
# book_content 表
|
||||||
|
book_content:
|
||||||
|
# 数据节点
|
||||||
|
actual-data-nodes: ds_$->{0}.book_content$->{0..9}
|
||||||
|
# 分表策略
|
||||||
|
table-strategy:
|
||||||
|
standard:
|
||||||
|
# 分片列名称
|
||||||
|
sharding-column: chapter_id
|
||||||
|
# 分片算法名称
|
||||||
|
sharding-algorithm-name: bookContentSharding
|
||||||
|
sharding-algorithms:
|
||||||
|
bookContentSharding:
|
||||||
|
# 行表达式分片算法,使用 Groovy 的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持
|
||||||
|
type: INLINE
|
||||||
|
props:
|
||||||
|
# 分片算法的行表达式
|
||||||
|
algorithm-expression: book_content$->{chapter_id % 10}
|
||||||
|
|
||||||
config:
|
config:
|
||||||
activate:
|
activate:
|
||||||
on-profile: dev
|
on-profile: dev
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
spring:
|
spring:
|
||||||
|
# Redis 配置
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
@ -35,6 +88,45 @@ spring:
|
|||||||
config:
|
config:
|
||||||
activate:
|
activate:
|
||||||
on-profile: dev
|
on-profile: dev
|
||||||
|
# Elasticsearch 配置
|
||||||
|
elasticsearch:
|
||||||
|
# 是否开启 elasticsearch 搜索引擎功能:true-开启 false-不开启
|
||||||
|
enable: false
|
||||||
|
uris:
|
||||||
|
- https://my-deployment-ce7ca3.es.us-central1.gcp.cloud.es.io:9243
|
||||||
|
username: elastic
|
||||||
|
password: qTjgYVKSuExX6tWAsDuvuvwl
|
||||||
|
amqp:
|
||||||
|
# 是否开启 Spring AMQP:true-开启 false-不开启
|
||||||
|
enable: false
|
||||||
|
# RabbitMQ 配置
|
||||||
|
rabbitmq:
|
||||||
|
addresses: "amqp://guest:guest@47.106.243.172"
|
||||||
|
virtual-host: novel
|
||||||
|
template:
|
||||||
|
retry:
|
||||||
|
# 开启重试
|
||||||
|
enabled: true
|
||||||
|
# 最大重试次数
|
||||||
|
max-attempts: 3
|
||||||
|
# 第一次和第二次重试之间的持续时间
|
||||||
|
initial-interval: "3s"
|
||||||
|
|
||||||
|
# XXL-JOB 配置
|
||||||
|
xxl:
|
||||||
|
job:
|
||||||
|
# 是否开启 XXL-JOB:true-开启 false-不开启
|
||||||
|
enable: false
|
||||||
|
admin:
|
||||||
|
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
|
||||||
|
addresses: http://127.0.0.1:8080/xxl-job-admin
|
||||||
|
executor:
|
||||||
|
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
|
||||||
|
appname: xxl-job-executor-novel
|
||||||
|
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
|
||||||
|
logpath: logs/xxl-job/jobhandler
|
||||||
|
### xxl-job, access token
|
||||||
|
accessToken: 123
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -61,7 +153,11 @@ novel:
|
|||||||
# 排除链接
|
# 排除链接
|
||||||
excludes:
|
excludes:
|
||||||
- /system/notice/*
|
- /system/notice/*
|
||||||
|
file:
|
||||||
|
# 文件上传配置
|
||||||
|
upload:
|
||||||
|
# 上传路径
|
||||||
|
path: /Users/xiongxiaoyang/upload
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
id,category_id,category_name,book_name,author_id,author_name,word_count,last_chapter_name
|
id,category_id,category_name,book_name,author_id,author_name,word_count,last_chapter_name
|
||||||
from book_info where word_count > 0
|
from book_info where word_count > 0
|
||||||
<if test="condition.keyword != null and condition.keyword != ''">
|
<if test="condition.keyword != null and condition.keyword != ''">
|
||||||
and (book_name like concat('%',#{condition.keyword},'%') or author_name like concat('%',#{condition.keyword},'%'))
|
and (book_name like concat('%',#{condition.keyword},'%') or author_name like
|
||||||
|
concat('%',#{condition.keyword},'%'))
|
||||||
</if>
|
</if>
|
||||||
<if test="condition.workDirection != null">
|
<if test="condition.workDirection != null">
|
||||||
and work_direction = #{condition.workDirection}
|
and work_direction = #{condition.workDirection}
|
||||||
@ -30,8 +31,9 @@
|
|||||||
<if test="condition.updateTimeMin != null">
|
<if test="condition.updateTimeMin != null">
|
||||||
and last_chapter_update_time >= #{condition.updateTimeMin}
|
and last_chapter_update_time >= #{condition.updateTimeMin}
|
||||||
</if>
|
</if>
|
||||||
|
<if test="condition.sort != null">
|
||||||
order by ${condition.sort}
|
order by ${condition.sort}
|
||||||
|
</if>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<update id="addVisitCount">
|
<update id="addVisitCount">
|
||||||
|
Reference in New Issue
Block a user