mirror of
https://github.com/201206030/novel.git
synced 2025-07-07 21:36:38 +00:00
Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b54e739d4 | |||
00acb47ec0 | |||
ada179053c | |||
1614e79106 | |||
8b43851955 | |||
8ba670cebb | |||
657c8a2286 | |||
cf4c6b77db | |||
6f6183af21 | |||
c3105c5b33 | |||
4f7a4f964a | |||
fd9ce3cb49 | |||
ae7267b605 | |||
9600f9c7e6 | |||
eea6682870 | |||
0890a3295d | |||
b0294a5b90 | |||
97b0e40534 | |||
5852cc6d26 | |||
9b93d90270 | |||
6d2fa74237 | |||
76677ea8b8 | |||
e795d26aa5 | |||
80480bc026 | |||
dec50ab0a0 | |||
c572650107 | |||
d9202c76fa | |||
fdc74bb7da | |||
5dceb7ac96 | |||
154b009c05 | |||
d7a9416c57 | |||
57793c4444 | |||
9bd95d3f28 | |||
ac1628aa2a | |||
8e9f31a240 | |||
37811bf173 | |||
cfafc6450b | |||
8199b3d4af | |||
fc0f3bee1d | |||
925da99d0b | |||
7012b7b8ea | |||
0a8808b418 | |||
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,3 +33,4 @@ build/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
/logs/
|
/logs/
|
||||||
|
/.shardingsphere/
|
||||||
|
91
README.md
91
README.md
@ -1,9 +1,15 @@
|
|||||||
[]( https://curl.qcloud.com/kgMaOjoq )
|
[]( https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=736e609d66e0ac4e57813316cec6fd0b&from=console )
|
||||||
|
<p align="center">
|
||||||
|
<a href='https://docs.oracle.com/en/java/javase/17'><img alt="Java 17" src="https://img.shields.io/badge/Java%2017-%234479A1.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAABNVBMVEUAAABkmP9ml/9mmf9mmf9lmv9nmf9mmf9mmf9nmP9mmf9mmf9mmv9mmf9mmf9mmf9mmf9mmP9llv9mmf9mmf9mmv9mmf9mmf9mmf9mmf//AABlmf9mmf9km/9mmf9mmf9lmf9mmf9mmf//AABmmf9mmf9mmf9lmv9mmf//AABmmf9mmP9mmf9mmf//AABgl/9mmf//AABmmP//AABmmf//AAD/AABmmf9mmv9mmf//AABnmf//AAD/AAD/AABmmf//AAD/AABlmf9mmf//AABmmf9mmv9mmf//AAD/AAD/AABmmf//AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AABmmf//AAD/AAD/AABsof9mmf//AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AABmmf//AAB37HanAAAAZXRSTlMAP4CLnb8dtpUP2plK78zEpyoKrGbVbxj334BjRzL7sVA168C8hXlEIwvJYJBXQQahWC8k5M6nW1Q6Myb2ya5yY0gT0rp9dWpSEerm35JtH5l7Xhjnzo2EdATz2dOfiDst8AW1KD5Fo/kAAAl3SURBVHja3Ny7ruIwEAbg/zloU9FEiqJQoCQiEsUh4k4ESNw56H//R1gHlsQk8TnbrcdfS8OIsT2escB/MoEjlnDEfAQ3ZMUDTlgwhRvIIZyQcAcneDzc4QKPDOACj3TjLPHICC7wSMIFR5JOFFxUXDjcQyoXyJeRbpzteyoLyOdR6UO8kMoN8u2pjCGfR+UM8b5IOlE0jlkKId2aigt33YQl+X2UPUu3DYTzqThwrZokLMVXCLfk0xbC9fiUSy8Ye3zpQbYRXyLhha9P0oXqpIpjANGqOGLZd1yfdGKlB3QjsVZ8SyDZiW+55GnCxmMlxE/sPl/8gpULzL52X7DZiDUfBv0g5dnu3+P8exyT9SDmt92t4E36axzDWU4Wc1jtEv0WR5BSOVneUlnffo5jsTpSiexe5MCOmhBN/jKmYvsix2TAWjFBw/D1se2LHBgmrI3v+LQ582UPy2UFa0s0zKd8Omaw3IiabTPIMV8G1nfpVtT08GEx418r2G5Lzbw7q3iwPq1woiaAblJ9ltrf2vpmLQ+huySC7rsD1o74MLrxJbb9LFfmrHndfUbmPqy3/oc4pgKuu196HHfoepLaD48pK8nG8HvAfvcxK8W1ddTLGfHMWFtDd81JihmCBub6KpXUZ+xHrIwNtVcuIbF6rA2hu7Ik5gFKwsqp684r5dXcwtho6E+piHnaFLCStm9ZgqbSO9PWizHfBNSKevUe9xs5JysQz5RZGUkpPZPPQALjtnyCAN+mPbZHWXO3Hd8WxkBiCQ/7A7712/WJpDcPG77dW8eIrNl0tdo3rV1L1gZcrYWHuXZhJGCVLG6G0W1EUlSVMjMce1sKS65hdxsIGaUl18AwZjvwRcxL2cwwL5jxwxrWW/Jp2siex426qeXDaGUSdS/3FUlZx6LPp0O/3WDRWf7OobTv/qYZPxwtH63XO1dkmCrKGR8iPHQnT0rdAfbLYipF2AxwSp39k1Bg1H2rDaRtXMCuu6Y6U3OEBMvul/xj1nKIkHY2rK8FKzFk8Kj4HatH0gCuukwl7eSS1RiqSvcZGkYSX/oXHd2fkCVp/5Nw6yjYj+IyqxS3z72ZoEbdn3bu/HVpMI4D+Gf37S63uXRebSoeKCKoGQl+UyMpouiEIIhP//+fULoOc1/LdFsuev0ssrfu+Tx7ru15HHnmevUpmyeO7x6OTN6HvWH2Xony5OA9Wh+zMmiPuHP/RaSxP4Eserp/2fcys7R7i+d7Jfh1Zv+PrUd7cxCZ6tIj7n1vMNnO8SPQ/cdZrFdRD+9mYSvK7716kIHZrBM8ewOn8myKNn0p78EV+vACTmKQFQW/q6yHkBZ9QvKabxXaVFWAizVzBWYQ8JU6fiMJkDi9Q1u4xdkexGJCEm9hpyrhV7kmJEm3NRd3mJUOMRFgj0fjVxNICkGqGFIbQ0hMNdkkNUrEkELWIFEr/IqAuBkdDr+hdUgaiSEO4uVRLn7DlSBOswnpQES/jaE5xIks4HddiIs+DiruhnR+2UwsiI/Txj02XKqvV+c8J+IWz8IRGwzNYr1f962GcB5Bl/PBgBnhd0wJjhpgqAix4fHAhiNLApzCcGa1ZrVjL/nKjYIH6vNTfj4S4pPDWyiiakrkukfsaVZ7RbtBUvzA5JiXYr2ARyk+NYO0g0A+h3Gy6GLtLYTSCxKazjmMgctRnZoBpzCTqL8hLy/l8CwFV9UoexJGOJGVWN8ecjy5+KXptt0y/pZi+XSQlz0H/pyOobIASVsMm/JkXFyvlgHF01qFYziN7gYNu9iTiZrOCn24QIAhFbLNEDE0hmyzMVSBbGttIk09YQIkQsJQAOmQIRkTDHUhDbWgA8mYpFixFnMGSUiGjl9B8joWIgMJwZALiZtp+AUPiWhhyIfk+bj1Es5jEHMDjpIxJEHyKAyRLfhDrR7lK4jos7/pCN01pIDBr95JHQ9O05+upXYZv3nZhNs4NO50WUgDg3sKucqgS86LVcJrCZHaphPVfIMacBYe4lmIqIYfqxCQjhIeVX6n1EeuaL1sq6pYx1+pSwdR3lK4pRYhNaSCMVB8aj+KrOIXZhXS1OpeGqKyai5gz1DbtbkppE0nLTyTqNnDw4rMIyITVsH0TXi1gH9mZDaaAkTYCmrFFvxFfS/Pq8pJEXzJlh24FUk338I1YEur7oBru4eJynUx55t0l+x5BhznwPUxFuxsWCOmwxnrCH3477//dmT4F3gVFv4BXeThHzB+iSJk39RERAYyz8Md0oDTOQRcHUPFkEtNT8jSkle0qiDSLbgyxE/DXXpVrBK604dDwrBUJLWbMn5TJq+syhF4m3duzucq2kDiqSAYMGIBo5TuEK6Jieejp3A9HLyEWYKrwb7EsyldHa4IKZ6XYnB1T2f9jo9/SKTHBlyjaXCDJ+PsGlyDxWzKQlSJ4jb4OyOOkv/+4LdPLBlrU/5FvWFlcqCO8BYFkeHzUwEi0p6OEJpLroBflP1GpzRc/DLwrCn3Ouv5kgwV5S+fP0OHgLiVpDpuuVKqbVQbQJxmqzbuMHkBUtXDBsSmqZVxh25C2tZ4AzFheQypMqROt2IL0nH/4jYjwsK4bi0av2lC6sj41r67+B0J6SJ2JwlsiEUH91hrFlIyLPJt/ELyYI8Q25jp3aAKSRPkgNsgRmPAFM5H4aFRZUlAMgxdtmkLv8o19IMeWYbzzRi8RZmhGz1PgLiwRIfU1M1PK9YlOFDNvYVLkBYeU29rQYdw4DyOTkzyy67mi2X8mX/LfvsFhV24zNsxV8ejwnW1NqfxwXbPw5AV4FBfWLAzvTYNt9TOG4Fkqtaxr8xRX0JE5UVUDbjY21p+cFPAhI3MFdE/viHfifFOLlJmInmUnEn1WLidt8wlssk0zJMblTEG7rZq1BZw1GzOhHWfgMQIOtGzA5r7kumcAGSHYOHXpiSDoRwBqTAcvdacdOwlRZuc2s7dWKK7qSuF8v4eIcakqUa+V6q1BDhBlXbxq1wesunnQx3tLMYQagcHUxSpBEnqCxAztmTzzAZ/Vlk7kLQW/1JbVmcxHD2Ri6T2soAR5tiBVDjFShnR9TWeXPeaHmvAyQxWn05sSgtXTaJErSG/hRS1xpT/41IKoxumsqtMTWJPSa72xsW8PV8tKUnj2mK9jEe9U/l8zYDUhY8vtIUXU244mqz+9WXFfosYN/hKro5/aNQ2u6sx0bq6afldoqArbc+Dtm/EUeREaGET3n/rbc+4gP+iPgNo04Ue6Gbq9gAAAABJRU5ErkJggg=="></a>
|
||||||
|
<a href='https://docs.spring.io/spring-boot/docs/3.0.0-SNAPSHOT/reference/html'><img alt="Spring Boot 3" src="https://img.shields.io/badge/Spring%20Boot%203-%23000000.svg?logo=springboot"></a>
|
||||||
|
<a href='https://staging-cn.vuejs.org'><img alt="Vue 3" src="https://img.shields.io/badge/Vue%203%20-%232b3847.svg?logo=vue.js"></a><br/>
|
||||||
|
<a href='https://github.com/201206030/novel'><img alt="Github stars" src="https://img.shields.io/github/stars/201206030/novel?logo=github"></a>
|
||||||
|
<a href='https://github.com/201206030/novel'><img alt="Github forks" src="https://img.shields.io/github/forks/201206030/novel?logo=github"></a>
|
||||||
|
<a href='https://gitee.com/novel_dev_team/novel'><img alt="Gitee stars" src="https://gitee.com/novel_dev_team/novel/badge/star.svg?theme=gitee"></a>
|
||||||
|
<a href='https://gitee.com/novel_dev_team/novel'><img alt="Gitee forks" src="https://gitee.com/novel_dev_team/novel/badge/fork.svg?theme=gitee"></a>
|
||||||
|
<a href="https://github.com/201206030/novel"><img src="https://visitor-badge.glitch.me/badge?page_id=201206030.novel" alt="visitors"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
[](https://github.com/201206030/novel)
|
|
||||||
[](https://github.com/201206030/novel)
|
|
||||||
[](https://gitee.com/novel_dev_team/novel)
|
|
||||||
[](https://gitee.com/novel_dev_team/novel)
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
|
|
||||||
novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开发的前后端分离的**学习型**小说项目,配备详细的项目教程手把手教你**从零开始**开发上线一个生产级别的 Java 系统,由小说门户系统、作家后台管理系统、平台后台管理系统、爬虫管理系统等多个子系统构成。包括小说推荐、作品检索、小说排行榜、小说阅读、小说评论、充值订阅、新闻发布等功能。
|
novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开发的前后端分离的**学习型**小说项目,配备详细的项目教程手把手教你**从零开始**开发上线一个生产级别的 Java 系统,由小说门户系统、作家后台管理系统、平台后台管理系统、爬虫管理系统等多个子系统构成。包括小说推荐、作品检索、小说排行榜、小说阅读、小说评论、充值订阅、新闻发布等功能。
|
||||||
@ -11,9 +17,9 @@ 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)
|
||||||
|
|
||||||
## 开发环境
|
## 开发环境
|
||||||
|
|
||||||
@ -21,25 +27,34 @@ novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开
|
|||||||
- Redis 7.0
|
- Redis 7.0
|
||||||
- Elasticsearch 8.2.0(可选)
|
- Elasticsearch 8.2.0(可选)
|
||||||
- RabbitMQ 3.10.2(可选)
|
- 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) |
|
||||||
|
| Redisson | 3.17.4 | 分布式锁实现 | https://github.com/redisson/redisson | [进入](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
|
||||||
| 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) |
|
||||||
|
| ShardingSphere-JDBC | 5.1.1 | 数据库分库分表支持 | https://shardingsphere.apache.org | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
|
||||||
| Elasticsearch | 8.2.0 | 搜索引擎服务 | https://www.elastic.co | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
|
| Elasticsearch | 8.2.0 | 搜索引擎服务 | https://www.elastic.co | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
|
||||||
| RabbitMQ | 3.10.2 | 开源消息中间件 | https://www.rabbitmq.com | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
|
| RabbitMQ | 3.10.2 | 开源消息中间件 | https://www.rabbitmq.com | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
|
||||||
|
| 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) |
|
||||||
|
| Springdoc-openapi | 2.0.0-M4-SNAPSHOT | Swagger 3 接口文档自动生成 | https://github.com/springdoc/springdoc-openapi | [进入](https://springdoc.org/) |
|
||||||
|
| Spring Boot Admin | 3.0.0-M1 | 应用管理和监控 | https://github.com/codecentric/spring-boot-admin | [进入](https://codecentric.github.io/spring-boot-admin/3.0.0-M1) |
|
||||||
| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | https://undertow.io | [进入](https://undertow.io/documentation.html) |
|
| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | https://undertow.io | [进入](https://undertow.io/documentation.html) |
|
||||||
| Docker | - | 应用容器引擎 | https://www.docker.com/ | - |
|
| Docker | - | 应用容器引擎 | https://www.docker.com/ | - |
|
||||||
| Jenkins | - | 自动化部署工具 | https://github.com/jenkinsci/jenkins | - |
|
| Jenkins | - | 自动化部署工具 | https://github.com/jenkinsci/jenkins | - |
|
||||||
@ -50,10 +65,10 @@ novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开
|
|||||||
|
|
||||||
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
||||||
| :----------------- | :-----: | -------------------------- | --------------------------------------- | :-------------------------------------------------: |
|
| :----------------- | :-----: | -------------------------- | --------------------------------------- | :-------------------------------------------------: |
|
||||||
| Vue.js | 3.2.13 | 渐进式 JavaScript 框架 | https://v3.cn.vuejs.org | [进入](https://v3.cn.vuejs.org/guide/introduction.html) |
|
| Vue.js | 3.2.13 | 渐进式 JavaScript 框架 | https://vuejs.org | [进入](https://staging-cn.vuejs.org/guide/introduction.html) |
|
||||||
| Vue Router | 4.0.15 | Vue.js 的官方路由 | https://router.vuejs.org/zh/index.html | [进入](https://router.vuejs.org/zh/guide/) |
|
| Vue Router | 4.0.15 | Vue.js 的官方路由 | https://router.vuejs.org | [进入](https://router.vuejs.org/zh/guide/) |
|
||||||
| axios | 0.27.2 | 基于 promise 的网络请求库 | https://axios-http.com/zh | [进入](https://axios-http.com/zh/docs/intro) |
|
| axios | 0.27.2 | 基于 promise 的网络请求库 | https://axios-http.com | [进入](https://axios-http.com/zh/docs/intro) |
|
||||||
| element-plus | 2.2.0 | 基于 Vue 3,面向设计师和开发者的组件库 | https://element-plus.org/zh-CN/ | [进入](https://element-plus.org/zh-CN/guide/design.html) |
|
| element-plus | 2.2.0 | 基于 Vue 3,面向设计师和开发者的组件库 | https://element-plus.org | [进入](https://element-plus.org/zh-CN/guide/design.html) |
|
||||||
|
|
||||||
## 编码规范
|
## 编码规范
|
||||||
|
|
||||||
@ -82,6 +97,8 @@ io
|
|||||||
| | +- resp -- 接口响应工具及响应数据格式封装
|
| | +- resp -- 接口响应工具及响应数据格式封装
|
||||||
| | +- util -- 通用工具
|
| | +- util -- 通用工具
|
||||||
| |
|
| |
|
||||||
|
| +- annotation -- 自定义注解类
|
||||||
|
| +- aspect -- Spring AOP 切面
|
||||||
| +- auth -- 用户认证授权相关
|
| +- auth -- 用户认证授权相关
|
||||||
| +- config -- 业务相关配置
|
| +- config -- 业务相关配置
|
||||||
| +- constant -- 业务相关常量
|
| +- constant -- 业务相关常量
|
||||||
@ -162,6 +179,10 @@ io
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
11. 接口文档
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## 安装步骤
|
## 安装步骤
|
||||||
|
|
||||||
@ -169,7 +190,7 @@ io
|
|||||||
|
|
||||||
- 下载后端源码
|
- 下载后端源码
|
||||||
|
|
||||||
```
|
```bash
|
||||||
git clone https://gitee.com/novel_dev_team/novel.git
|
git clone https://gitee.com/novel_dev_team/novel.git
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -187,7 +208,7 @@ git clone https://gitee.com/novel_dev_team/novel.git
|
|||||||
|
|
||||||
1. 修改`src/resources/application.yml`配置文件中的数据源配置
|
1. 修改`src/resources/application.yml`配置文件中的数据源配置
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
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
|
||||||
@ -195,9 +216,9 @@ git clone https://gitee.com/novel_dev_team/novel.git
|
|||||||
password: test123456
|
password: test123456
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 修改`src/resources/application.yml`配置文件中的`redis`连接配置
|
2. 修改`src/resources/application.yml` 和 `src/resources/redisson.yml` 配置文件中的`redis`连接配置
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
spring:
|
spring:
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
@ -205,17 +226,25 @@ git clone https://gitee.com/novel_dev_team/novel.git
|
|||||||
password: 123456
|
password: 123456
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 项目根目录下运行如下命令来启动后端服务(有安装 IDE 的可以导入源码到 IDE 中运行)
|
```yaml
|
||||||
|
singleServerConfig:
|
||||||
|
address: "redis://127.0.0.1:6379"
|
||||||
|
password: 123456
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. 根据前后端的实际部署情况,修改`application.yml`中的跨域配置(默认情况可忽略此步骤)
|
||||||
|
|
||||||
|
4. 项目根目录下运行如下命令来启动后端服务(有安装 IDE 的可以导入源码到 IDE 中运行)
|
||||||
|
|
||||||
|
```bash
|
||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
```
|
```
|
||||||
|
5. 接口文档访问地址:`http://server:port/swagger-ui/index.html`
|
||||||
|
|
||||||
4. 根据前后端的实际部署情况,修改`application.yml`中的跨域配置(默认情况可忽略此步骤)
|
|
||||||
|
|
||||||
- 下载前端前台门户系统源码
|
- 下载前端前台门户系统源码
|
||||||
|
|
||||||
```
|
```bash
|
||||||
git clone https://gitee.com/novel_dev_team/novel-front-web.git
|
git clone https://gitee.com/novel_dev_team/novel-front-web.git
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -225,33 +254,37 @@ git clone https://gitee.com/novel_dev_team/novel-front-web.git
|
|||||||
|
|
||||||
2. `yarn`安装
|
2. `yarn`安装
|
||||||
|
|
||||||
```
|
```bash
|
||||||
npm install -g yarn
|
npm install -g yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 项目根目录下运行如下命令来安装项目依赖
|
3. 项目根目录下运行如下命令来安装项目依赖
|
||||||
|
|
||||||
```
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
4. 项目根目录下运行如下命令启动
|
4. 项目根目录下运行如下命令启动
|
||||||
|
|
||||||
```
|
```bash
|
||||||
yarn serve
|
yarn serve
|
||||||
```
|
```
|
||||||
5. 浏览器通过`http://localhost:1024`来访问
|
5. 浏览器通过`http://localhost:1024`来访问
|
||||||
|
|
||||||
## 项目教程
|
## 项目教程
|
||||||
|
|
||||||
[手把手教你从零开始开发上线一个生产级别的小说系统](https://youdoc.github.io/course/novel/3.html)
|
[手把手教你从零开始开发上线一个生产级别的小说系统](https://docs.xxyopen.com/course/novel/3.html)
|
||||||
|
|
||||||
## 公众号
|
## 公众号
|
||||||
|
|
||||||
关注公众号接收项目最新动态,获取`Spring Boot 3`学习笔记!
|
- 关注公众号接收`项目`和`文档`的更新动态
|
||||||
|
|
||||||
加微信群交流,公众号后台回复「**微信群**」即可。
|
- 加微信群学习交流,公众号后台回复「**微信群**」即可
|
||||||
|
|
||||||

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

|
||||||
|
|
||||||
## 赞赏支持
|
## 赞赏支持
|
||||||
|
|
||||||
|
6
doc/sql/README.md
Normal file
6
doc/sql/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
1. 初始状态下,MySQL 只需要执行 `novel.sql` 文件即可正常运行本系统
|
||||||
|
2. 代码更新后再执行以日期命名的增量 SQL 文件
|
||||||
|
3. 只有开启 XXL-JOB 的功能,才需要执行 `xxl-job.sql` 和以 xxl-job 开头日期结尾的增量 SQL 文件
|
||||||
|
4. 只有开启 ShardingSphere-JDBC 的功能,才需要执行 `shardingsphere-jdbc.sql` 和以 shardingsphere-jdbc 开头日期结尾的增量 SQL
|
||||||
|
文件
|
||||||
|
|
171
doc/sql/shardingsphere-jdbc.sql
Normal file
171
doc/sql/shardingsphere-jdbc.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();
|
190
doc/sql/xxl-job.sql
Normal file
190
doc/sql/xxl-job.sql
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#
|
||||||
|
# XXL-JOB v2.4.0-SNAPSHOT
|
||||||
|
# Copyright (c) 2015-present, xuxueli.
|
||||||
|
|
||||||
|
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||||
|
use `xxl_job`;
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_info` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
||||||
|
`job_desc` varchar(255) NOT NULL,
|
||||||
|
`add_time` datetime DEFAULT NULL,
|
||||||
|
`update_time` datetime DEFAULT NULL,
|
||||||
|
`author` varchar(64) DEFAULT NULL COMMENT '作者',
|
||||||
|
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
|
||||||
|
`schedule_type` varchar(50) NOT NULL DEFAULT 'NONE' COMMENT '调度类型',
|
||||||
|
`schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
|
||||||
|
`misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
|
||||||
|
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
|
||||||
|
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
||||||
|
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
||||||
|
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
|
||||||
|
`executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
|
||||||
|
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
||||||
|
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
|
||||||
|
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
||||||
|
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
|
||||||
|
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
|
||||||
|
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
|
||||||
|
`trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
|
||||||
|
`trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
|
||||||
|
`trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_log` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
||||||
|
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
||||||
|
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
|
||||||
|
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
||||||
|
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
||||||
|
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
|
||||||
|
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
||||||
|
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
|
||||||
|
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
|
||||||
|
`trigger_msg` text COMMENT '调度-日志',
|
||||||
|
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
|
||||||
|
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
|
||||||
|
`handle_msg` text COMMENT '执行-日志',
|
||||||
|
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `I_trigger_time` (`trigger_time`),
|
||||||
|
KEY `I_handle_code` (`handle_code`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_log_report` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
|
||||||
|
`running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
|
||||||
|
`suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
|
||||||
|
`fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
|
||||||
|
`update_time` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_logglue` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
||||||
|
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
|
||||||
|
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
||||||
|
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
|
||||||
|
`add_time` datetime DEFAULT NULL,
|
||||||
|
`update_time` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_registry` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`registry_group` varchar(50) NOT NULL,
|
||||||
|
`registry_key` varchar(255) NOT NULL,
|
||||||
|
`registry_value` varchar(255) NOT NULL,
|
||||||
|
`update_time` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_group` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
|
||||||
|
`title` varchar(12) NOT NULL COMMENT '执行器名称',
|
||||||
|
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
|
||||||
|
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
|
||||||
|
`update_time` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_user` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`username` varchar(50) NOT NULL COMMENT '账号',
|
||||||
|
`password` varchar(50) NOT NULL COMMENT '密码',
|
||||||
|
`role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员',
|
||||||
|
`permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `i_username` (`username`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `xxl_job_lock` (
|
||||||
|
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
|
||||||
|
PRIMARY KEY (`lock_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2018-11-03 22:21:31' );
|
||||||
|
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
|
||||||
|
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
|
||||||
|
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
|
||||||
|
-- 增加 novel 任务执行器和同步小说数据到 Elasticsearch 的任务
|
||||||
|
-- 增加 novel 任务执行器和同步小说数据到 Elasticsearch 的任务
|
||||||
|
INSERT INTO `xxl_job`.`xxl_job_group` ( `app_name`, `title`, `address_type`, `address_list`, `update_time` )
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'xxl-job-executor-novel',
|
||||||
|
'novel 任务执行器',
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
now()
|
||||||
|
);
|
||||||
|
INSERT INTO `xxl_job`.`xxl_job_info` (
|
||||||
|
`job_group`,
|
||||||
|
`job_desc`,
|
||||||
|
`add_time`,
|
||||||
|
`update_time`,
|
||||||
|
`author`,
|
||||||
|
`alarm_email`,
|
||||||
|
`schedule_type`,
|
||||||
|
`schedule_conf`,
|
||||||
|
`misfire_strategy`,
|
||||||
|
`executor_route_strategy`,
|
||||||
|
`executor_handler`,
|
||||||
|
`executor_param`,
|
||||||
|
`executor_block_strategy`,
|
||||||
|
`executor_timeout`,
|
||||||
|
`executor_fail_retry_count`,
|
||||||
|
`glue_type`,
|
||||||
|
`glue_source`,
|
||||||
|
`glue_remark`,
|
||||||
|
`glue_updatetime`,
|
||||||
|
`child_jobid`,
|
||||||
|
`trigger_status`,
|
||||||
|
`trigger_last_time`,
|
||||||
|
`trigger_next_time`
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
(SELECT
|
||||||
|
id
|
||||||
|
FROM
|
||||||
|
xxl_job_group
|
||||||
|
WHERE
|
||||||
|
app_name = 'xxl-job-executor-novel'),
|
||||||
|
'同步小说数据到 Elasticsearch',
|
||||||
|
now(),
|
||||||
|
now(),
|
||||||
|
'xxyopen',
|
||||||
|
'',
|
||||||
|
'CRON',
|
||||||
|
'0 0 0 1 * ?',
|
||||||
|
'DO_NOTHING',
|
||||||
|
'FIRST',
|
||||||
|
'saveToEsJobHandler',
|
||||||
|
'',
|
||||||
|
'SERIAL_EXECUTION',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'BEAN',
|
||||||
|
'',
|
||||||
|
'GLUE代码初始化',
|
||||||
|
now(),
|
||||||
|
'',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
3
doc/style/README.md
Normal file
3
doc/style/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
IntelliJ IDEA 中导入 `intellij-java-google-style.xml` 文件:
|
||||||
|
|
||||||
|
`Preferences` => `Editor` => `Code Style` => `Java` => `Schema` => `Import Schema`
|
35
doc/style/intellij-java-google-style.xml
Normal file
35
doc/style/intellij-java-google-style.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<code_scheme name="GoogleStyle">
|
||||||
|
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false"/>
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1"/>
|
||||||
|
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1"/>
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false"/>
|
||||||
|
<option name="ALIGN_MULTILINE_RESOURCES" value="false"/>
|
||||||
|
<option name="ALIGN_MULTILINE_FOR" value="false"/>
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="1"/>
|
||||||
|
<option name="METHOD_PARAMETERS_WRAP" value="1"/>
|
||||||
|
<option name="EXTENDS_LIST_WRAP" value="1"/>
|
||||||
|
<option name="THROWS_KEYWORD_WRAP" value="1"/>
|
||||||
|
<option name="METHOD_CALL_CHAIN_WRAP" value="1"/>
|
||||||
|
<option name="BINARY_OPERATION_WRAP" value="1"/>
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true"/>
|
||||||
|
<option name="TERNARY_OPERATION_WRAP" value="1"/>
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true"/>
|
||||||
|
<option name="FOR_STATEMENT_WRAP" value="1"/>
|
||||||
|
<option name="ARRAY_INITIALIZER_WRAP" value="1"/>
|
||||||
|
<option name="WRAP_COMMENTS" value="true"/>
|
||||||
|
<option name="IF_BRACE_FORCE" value="3"/>
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3"/>
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3"/>
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3"/>
|
||||||
|
<option name="PARENT_SETTINGS_INSTALLED" value="true"/>
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4"/>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4"/>
|
||||||
|
<option name="TAB_SIZE" value="4"/>
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
|
||||||
|
</code_scheme>
|
115
pom.xml
115
pom.xml
@ -1,5 +1,6 @@
|
|||||||
<?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>
|
||||||
@ -10,7 +11,7 @@
|
|||||||
</parent>
|
</parent>
|
||||||
<groupId>io.github.xxyopen</groupId>
|
<groupId>io.github.xxyopen</groupId>
|
||||||
<artifactId>novel</artifactId>
|
<artifactId>novel</artifactId>
|
||||||
<version>3.1.0</version>
|
<version>3.3.0</version>
|
||||||
<name>novel</name>
|
<name>novel</name>
|
||||||
<description>Spring Boot 3 + Vue 3 构建的前后端分离小说系统</description>
|
<description>Spring Boot 3 + Vue 3 构建的前后端分离小说系统</description>
|
||||||
<properties>
|
<properties>
|
||||||
@ -19,7 +20,14 @@
|
|||||||
<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>
|
||||||
<elasticsearch.version>8.2.0</elasticsearch.version>
|
<elasticsearch.version>8.2.0</elasticsearch.version>
|
||||||
|
<xxl-job.version>2.3.1</xxl-job.version>
|
||||||
|
<sentinel.version>1.8.4</sentinel.version>
|
||||||
|
<shardingsphere-jdbc.version>5.1.1</shardingsphere-jdbc.version>
|
||||||
|
<redisson.version>3.17.4</redisson.version>
|
||||||
|
<spring-boot-admin.version>3.0.0-M1</spring-boot-admin.version>
|
||||||
|
<springdoc-openapi.version>2.0.0-M4-SNAPSHOT</springdoc-openapi.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -116,6 +124,67 @@
|
|||||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- XXL-JOB 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.xuxueli</groupId>
|
||||||
|
<artifactId>xxl-job-core</artifactId>
|
||||||
|
<version>${xxl-job.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- sentinel 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.csp</groupId>
|
||||||
|
<artifactId>sentinel-core</artifactId>
|
||||||
|
<version>${sentinel.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.csp</groupId>
|
||||||
|
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||||
|
<version>${sentinel.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ShardingSphere-JDBC -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.shardingsphere</groupId>
|
||||||
|
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
|
||||||
|
<version>${shardingsphere-jdbc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot 管理和监控 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.codecentric</groupId>
|
||||||
|
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||||
|
<version>${spring-boot-admin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Redisson 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.redisson</groupId>
|
||||||
|
<artifactId>redisson</artifactId>
|
||||||
|
<version>${redisson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Aop 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- OpenAPI 3 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>${springdoc-openapi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
@ -155,6 +224,16 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
<repositories>
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>ali</id>
|
||||||
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>spring-milestones</id>
|
<id>spring-milestones</id>
|
||||||
<name>Spring Milestones</name>
|
<name>Spring Milestones</name>
|
||||||
@ -171,8 +250,40 @@
|
|||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype-nexus-snapshots</id>
|
||||||
|
<name>Sonatype Nexus Snapshots</name>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype-nexus-snapshots-2</id>
|
||||||
|
<name>Sonatype Nexus Snapshots 2</name>
|
||||||
|
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
<pluginRepositories>
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>ali</id>
|
||||||
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</pluginRepository>
|
||||||
<pluginRepository>
|
<pluginRepository>
|
||||||
<id>spring-milestones</id>
|
<id>spring-milestones</id>
|
||||||
<name>Spring Milestones</name>
|
<name>Spring Milestones</name>
|
||||||
|
@ -1,18 +1,29 @@
|
|||||||
package io.github.xxyopen.novel;
|
package io.github.xxyopen.novel;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||||
|
import io.swagger.v3.oas.annotations.info.Info;
|
||||||
|
import io.swagger.v3.oas.annotations.info.License;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||||
|
import java.util.Map;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.CommandLineRunner;
|
import org.springframework.boot.CommandLineRunner;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cache.CacheManager;
|
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 org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
import java.util.Map;
|
@OpenAPIDefinition(info = @Info(title = "novel 项目接口文档", version = "v3.2.0", license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0")))
|
||||||
|
@SecurityScheme(type = SecuritySchemeType.APIKEY, in = SecuritySchemeIn.HEADER, name = SystemConfigConsts.HTTP_AUTH_HEADER_NAME, description = "登录 token")
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@MapperScan("io.github.xxyopen.novel.dao.mapper")
|
@MapperScan("io.github.xxyopen.novel.dao.mapper")
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
@ -37,4 +48,13 @@ public class NovelApplication {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf().disable()
|
||||||
|
.requestMatcher(EndpointRequest.toAnyEndpoint())
|
||||||
|
.authorizeRequests(requests -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
|
||||||
|
http.httpBasic();
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,27 @@
|
|||||||
package io.github.xxyopen.novel.controller.author;
|
package io.github.xxyopen.novel.controller.author;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.auth.UserHolder;
|
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.common.resp.RestResp;
|
||||||
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.dto.req.AuthorRegisterReqDto;
|
import io.github.xxyopen.novel.dto.req.AuthorRegisterReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
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.AuthorService;
|
||||||
import io.github.xxyopen.novel.service.BookService;
|
import io.github.xxyopen.novel.service.BookService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springdoc.core.annotations.ParameterObject;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -17,9 +29,12 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家后台-作家模块 API 控制器
|
* 作家后台-作家模块 API 控制器
|
||||||
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/23
|
* @date 2022/5/23
|
||||||
*/
|
*/
|
||||||
|
@Tag(name = "AuthorController", description = "作家后台-作者模块")
|
||||||
|
@SecurityRequirement(name = SystemConfigConsts.HTTP_AUTH_HEADER_NAME)
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(ApiRouterConsts.API_AUTHOR_URL_PREFIX)
|
@RequestMapping(ApiRouterConsts.API_AUTHOR_URL_PREFIX)
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -32,26 +47,61 @@ public class AuthorController {
|
|||||||
/**
|
/**
|
||||||
* 作家注册接口
|
* 作家注册接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "作家注册接口")
|
||||||
@PostMapping("register")
|
@PostMapping("register")
|
||||||
public RestResp<Void> register(@Valid @RequestBody AuthorRegisterReqDto dto) {
|
public RestResp<Void> register(@Valid @RequestBody AuthorRegisterReqDto dto) {
|
||||||
dto.setUserId(UserHolder.getUserId());
|
dto.setUserId(UserHolder.getUserId());
|
||||||
return authorService.register(dto);
|
return authorService.register(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询作家状态接口
|
||||||
|
*/
|
||||||
|
@Operation(summary = "作家状态查询接口")
|
||||||
|
@GetMapping("status")
|
||||||
|
public RestResp<Integer> getStatus() {
|
||||||
|
return authorService.getStatus(UserHolder.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说发布接口
|
* 小说发布接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说发布接口")
|
||||||
@PostMapping("book")
|
@PostMapping("book")
|
||||||
public RestResp<Void> publishBook(@Valid @RequestBody BookAddReqDto dto) {
|
public RestResp<Void> publishBook(@Valid @RequestBody BookAddReqDto dto) {
|
||||||
return bookService.saveBook(dto);
|
return bookService.saveBook(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说发布列表查询接口
|
||||||
|
*/
|
||||||
|
@Operation(summary = "小说发布列表查询接口")
|
||||||
|
@GetMapping("books")
|
||||||
|
public RestResp<PageRespDto<BookInfoRespDto>> listBooks(@ParameterObject PageReqDto dto) {
|
||||||
|
return bookService.listAuthorBooks(dto);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说章节发布接口
|
* 小说章节发布接口
|
||||||
*/
|
*/
|
||||||
@PostMapping("book/chapter")
|
@Operation(summary = "小说章节发布接口")
|
||||||
public RestResp<Void> publishBookChapter(@Valid @RequestBody ChapterAddReqDto dto) {
|
@PostMapping("book/chapter/{bookId}")
|
||||||
|
public RestResp<Void> publishBookChapter(
|
||||||
|
@Parameter(description = "小说ID") @PathVariable("bookId") Long bookId,
|
||||||
|
@Valid @RequestBody ChapterAddReqDto dto) {
|
||||||
|
dto.setBookId(bookId);
|
||||||
return bookService.saveBookChapter(dto);
|
return bookService.saveBookChapter(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节发布列表查询接口
|
||||||
|
*/
|
||||||
|
@Operation(summary = "小说章节发布列表查询接口")
|
||||||
|
@GetMapping("book/chapters/{bookId}")
|
||||||
|
public RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(
|
||||||
|
@Parameter(description = "小说ID") @PathVariable("bookId") Long bookId,
|
||||||
|
@ParameterObject PageReqDto dto) {
|
||||||
|
return bookService.listBookChapters(bookId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
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.BookCategoryRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookChapterAboutRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookCommentRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookContentAboutRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookRankRespDto;
|
||||||
import io.github.xxyopen.novel.service.BookService;
|
import io.github.xxyopen.novel.service.BookService;
|
||||||
import io.github.xxyopen.novel.service.SearchService;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import lombok.RequiredArgsConstructor;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户-小说模块 API 控制器
|
* 前台门户-小说模块 API 控制器
|
||||||
@ -19,6 +28,7 @@ import java.util.List;
|
|||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/14
|
* @date 2022/5/14
|
||||||
*/
|
*/
|
||||||
|
@Tag(name = "BookController", description = "前台门户-小说模块")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX)
|
@RequestMapping(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX)
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -26,91 +36,99 @@ public class BookController {
|
|||||||
|
|
||||||
private final BookService bookService;
|
private final BookService bookService;
|
||||||
|
|
||||||
private final SearchService searchService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说分类列表查询接口
|
* 小说分类列表查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说分类列表查询接口")
|
||||||
@GetMapping("category/list")
|
@GetMapping("category/list")
|
||||||
public RestResp<List<BookCategoryRespDto>> listCategory(Integer workDirection) {
|
public RestResp<List<BookCategoryRespDto>> listCategory(
|
||||||
|
@Parameter(description = "作品方向", required = true) Integer workDirection) {
|
||||||
return bookService.listCategory(workDirection);
|
return bookService.listCategory(workDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 小说搜索接口
|
|
||||||
*/
|
|
||||||
@GetMapping("search_list")
|
|
||||||
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
|
||||||
return searchService.searchBooks(condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息查询接口
|
* 小说信息查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说信息查询接口")
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
public RestResp<BookInfoRespDto> getBookById(@PathVariable("id") Long bookId) {
|
public RestResp<BookInfoRespDto> getBookById(
|
||||||
|
@Parameter(description = "小说 ID") @PathVariable("id") Long bookId) {
|
||||||
return bookService.getBookById(bookId);
|
return bookService.getBookById(bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增加小说点击量接口
|
* 增加小说点击量接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "增加小说点击量接口")
|
||||||
@PostMapping("visit")
|
@PostMapping("visit")
|
||||||
public RestResp<Void> addVisitCount(Long bookId) {
|
public RestResp<Void> addVisitCount(@Parameter(description = "小说ID") Long bookId) {
|
||||||
return bookService.addVisitCount(bookId);
|
return bookService.addVisitCount(bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说最新章节相关信息查询接口
|
* 小说最新章节相关信息查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说最新章节相关信息查询接口")
|
||||||
@GetMapping("last_chapter/about")
|
@GetMapping("last_chapter/about")
|
||||||
public RestResp<BookChapterAboutRespDto> getLastChapterAbout(Long bookId) {
|
public RestResp<BookChapterAboutRespDto> getLastChapterAbout(
|
||||||
|
@Parameter(description = "小说ID") Long bookId) {
|
||||||
return bookService.getLastChapterAbout(bookId);
|
return bookService.getLastChapterAbout(bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说推荐列表查询接口
|
* 小说推荐列表查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说推荐列表查询接口")
|
||||||
@GetMapping("rec_list")
|
@GetMapping("rec_list")
|
||||||
public RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId) throws NoSuchAlgorithmException {
|
public RestResp<List<BookInfoRespDto>> listRecBooks(
|
||||||
|
@Parameter(description = "小说ID") Long bookId) throws NoSuchAlgorithmException {
|
||||||
return bookService.listRecBooks(bookId);
|
return bookService.listRecBooks(bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说章节列表查询接口
|
* 小说章节列表查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说章节列表查询接口")
|
||||||
@GetMapping("chapter/list")
|
@GetMapping("chapter/list")
|
||||||
public RestResp<List<BookChapterRespDto>> listChapters(Long bookId) {
|
public RestResp<List<BookChapterRespDto>> listChapters(
|
||||||
|
@Parameter(description = "小说ID") Long bookId) {
|
||||||
return bookService.listChapters(bookId);
|
return bookService.listChapters(bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说内容相关信息查询接口
|
* 小说内容相关信息查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说内容相关信息查询接口")
|
||||||
@GetMapping("content/{chapterId}")
|
@GetMapping("content/{chapterId}")
|
||||||
public RestResp<BookContentAboutRespDto> getBookContentAbout(@PathVariable("chapterId") Long chapterId) {
|
public RestResp<BookContentAboutRespDto> getBookContentAbout(
|
||||||
|
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||||
return bookService.getBookContentAbout(chapterId);
|
return bookService.getBookContentAbout(chapterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取上一章节ID接口
|
* 获取上一章节ID接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "获取上一章节ID接口")
|
||||||
@GetMapping("pre_chapter_id/{chapterId}")
|
@GetMapping("pre_chapter_id/{chapterId}")
|
||||||
public RestResp<Long> getPreChapterId(@PathVariable("chapterId") Long chapterId) {
|
public RestResp<Long> getPreChapterId(
|
||||||
|
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||||
return bookService.getPreChapterId(chapterId);
|
return bookService.getPreChapterId(chapterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取下一章节ID接口
|
* 获取下一章节ID接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "获取下一章节ID接口")
|
||||||
@GetMapping("next_chapter_id/{chapterId}")
|
@GetMapping("next_chapter_id/{chapterId}")
|
||||||
public RestResp<Long> getNextChapterId(@PathVariable("chapterId") Long chapterId) {
|
public RestResp<Long> getNextChapterId(
|
||||||
|
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||||
return bookService.getNextChapterId(chapterId);
|
return bookService.getNextChapterId(chapterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说点击榜查询接口
|
* 小说点击榜查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说点击榜查询接口")
|
||||||
@GetMapping("visit_rank")
|
@GetMapping("visit_rank")
|
||||||
public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
|
public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
|
||||||
return bookService.listVisitRankBooks();
|
return bookService.listVisitRankBooks();
|
||||||
@ -119,6 +137,7 @@ public class BookController {
|
|||||||
/**
|
/**
|
||||||
* 小说新书榜查询接口
|
* 小说新书榜查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说新书榜查询接口")
|
||||||
@GetMapping("newest_rank")
|
@GetMapping("newest_rank")
|
||||||
public RestResp<List<BookRankRespDto>> listNewestRankBooks() {
|
public RestResp<List<BookRankRespDto>> listNewestRankBooks() {
|
||||||
return bookService.listNewestRankBooks();
|
return bookService.listNewestRankBooks();
|
||||||
@ -127,6 +146,7 @@ public class BookController {
|
|||||||
/**
|
/**
|
||||||
* 小说更新榜查询接口
|
* 小说更新榜查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说更新榜查询接口")
|
||||||
@GetMapping("update_rank")
|
@GetMapping("update_rank")
|
||||||
public RestResp<List<BookRankRespDto>> listUpdateRankBooks() {
|
public RestResp<List<BookRankRespDto>> listUpdateRankBooks() {
|
||||||
return bookService.listUpdateRankBooks();
|
return bookService.listUpdateRankBooks();
|
||||||
@ -135,8 +155,10 @@ public class BookController {
|
|||||||
/**
|
/**
|
||||||
* 小说最新评论查询接口
|
* 小说最新评论查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "小说最新评论查询接口")
|
||||||
@GetMapping("comment/newest_list")
|
@GetMapping("comment/newest_list")
|
||||||
public RestResp<BookCommentRespDto> listNewestComments(Long bookId) {
|
public RestResp<BookCommentRespDto> listNewestComments(
|
||||||
|
@Parameter(description = "小说ID") Long bookId) {
|
||||||
return bookService.listNewestComments(bookId);
|
return bookService.listNewestComments(bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ 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.service.HomeService;
|
import io.github.xxyopen.novel.service.HomeService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -18,6 +20,7 @@ import java.util.List;
|
|||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/12
|
* @date 2022/5/12
|
||||||
*/
|
*/
|
||||||
|
@Tag(name = "HomeController", description = "前台门户-首页模块")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(ApiRouterConsts.API_FRONT_HOME_URL_PREFIX)
|
@RequestMapping(ApiRouterConsts.API_FRONT_HOME_URL_PREFIX)
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -28,6 +31,7 @@ public class HomeController {
|
|||||||
/**
|
/**
|
||||||
* 首页小说推荐查询接口
|
* 首页小说推荐查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "首页小说推荐查询接口")
|
||||||
@GetMapping("books")
|
@GetMapping("books")
|
||||||
public RestResp<List<HomeBookRespDto>> listHomeBooks() {
|
public RestResp<List<HomeBookRespDto>> listHomeBooks() {
|
||||||
return homeService.listHomeBooks();
|
return homeService.listHomeBooks();
|
||||||
@ -36,6 +40,7 @@ public class HomeController {
|
|||||||
/**
|
/**
|
||||||
* 首页友情链接列表查询接口
|
* 首页友情链接列表查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "首页友情链接列表查询接口")
|
||||||
@GetMapping("friend_Link/list")
|
@GetMapping("friend_Link/list")
|
||||||
public RestResp<List<HomeFriendLinkRespDto>> listHomeFriendLinks() {
|
public RestResp<List<HomeFriendLinkRespDto>> listHomeFriendLinks() {
|
||||||
return homeService.listHomeFriendLinks();
|
return homeService.listHomeFriendLinks();
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
package io.github.xxyopen.novel.controller.front;
|
package io.github.xxyopen.novel.controller.front;
|
||||||
|
|
||||||
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.core.constant.ApiRouterConsts;
|
||||||
import io.github.xxyopen.novel.dto.resp.NewsInfoRespDto;
|
import io.github.xxyopen.novel.dto.resp.NewsInfoRespDto;
|
||||||
import io.github.xxyopen.novel.service.NewsService;
|
import io.github.xxyopen.novel.service.NewsService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户-新闻模块 API 控制器
|
* 前台门户-新闻模块 API 控制器
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/12
|
* @date 2022/5/12
|
||||||
*/
|
*/
|
||||||
|
@Tag(name = "NewsController", description = "前台门户-新闻模块")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(ApiRouterConsts.API_FRONT_NEWS_URL_PREFIX)
|
@RequestMapping(ApiRouterConsts.API_FRONT_NEWS_URL_PREFIX)
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -28,6 +31,7 @@ public class NewsController {
|
|||||||
/**
|
/**
|
||||||
* 最新新闻列表查询接口
|
* 最新新闻列表查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "最新新闻列表查询接口")
|
||||||
@GetMapping("latest_list")
|
@GetMapping("latest_list")
|
||||||
public RestResp<List<NewsInfoRespDto>> listLatestNews() {
|
public RestResp<List<NewsInfoRespDto>> listLatestNews() {
|
||||||
return newsService.listLatestNews();
|
return newsService.listLatestNews();
|
||||||
@ -36,8 +40,10 @@ public class NewsController {
|
|||||||
/**
|
/**
|
||||||
* 新闻信息查询接口
|
* 新闻信息查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "新闻信息查询接口")
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
public RestResp<NewsInfoRespDto> getNews(@PathVariable Long id) {
|
public RestResp<NewsInfoRespDto> getNews(
|
||||||
|
@Parameter(description = "新闻ID") @PathVariable Long id) {
|
||||||
return newsService.getNews(id);
|
return newsService.getNews(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,17 @@ import io.github.xxyopen.novel.core.common.resp.RestResp;
|
|||||||
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
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 io.swagger.v3.oas.annotations.Operation;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户-资源(图片/视频/文档)模块 API 控制器
|
* 前台门户-资源(图片/视频/文档)模块 API 控制器
|
||||||
@ -16,6 +22,7 @@ import java.io.IOException;
|
|||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/17
|
* @date 2022/5/17
|
||||||
*/
|
*/
|
||||||
|
@Tag(name = "ResourceController", description = "前台门户-资源模块")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(ApiRouterConsts.API_FRONT_RESOURCE_URL_PREFIX)
|
@RequestMapping(ApiRouterConsts.API_FRONT_RESOURCE_URL_PREFIX)
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -26,6 +33,7 @@ public class ResourceController {
|
|||||||
/**
|
/**
|
||||||
* 获取图片验证码接口
|
* 获取图片验证码接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "获取图片验证码接口")
|
||||||
@GetMapping("img_verify_code")
|
@GetMapping("img_verify_code")
|
||||||
public RestResp<ImgVerifyCodeRespDto> getImgVerifyCode() throws IOException {
|
public RestResp<ImgVerifyCodeRespDto> getImgVerifyCode() throws IOException {
|
||||||
return resourceService.getImgVerifyCode();
|
return resourceService.getImgVerifyCode();
|
||||||
@ -33,9 +41,11 @@ public class ResourceController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片上传接口
|
* 图片上传接口
|
||||||
* */
|
*/
|
||||||
|
@Operation(summary = "图片上传接口")
|
||||||
@PostMapping("/image")
|
@PostMapping("/image")
|
||||||
RestResp<String> uploadImage(@RequestParam("file") MultipartFile file) {
|
RestResp<String> uploadImage(
|
||||||
|
@Parameter(description = "上传文件") @RequestParam("file") MultipartFile file) {
|
||||||
return resourceService.uploadImage(file);
|
return resourceService.uploadImage(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
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 io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springdoc.core.annotations.ParameterObject;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@Tag(name = "SearchController", description = "前台门户-搜索模块")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(ApiRouterConsts.API_FRONT_SEARCH_URL_PREFIX)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SearchController {
|
||||||
|
|
||||||
|
private final SearchService searchService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说搜索接口
|
||||||
|
*/
|
||||||
|
@Operation(summary = "小说搜索接口")
|
||||||
|
@GetMapping("books")
|
||||||
|
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(
|
||||||
|
@ParameterObject BookSearchReqDto condition) {
|
||||||
|
return searchService.searchBooks(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ package io.github.xxyopen.novel.controller.front;
|
|||||||
import io.github.xxyopen.novel.core.auth.UserHolder;
|
import io.github.xxyopen.novel.core.auth.UserHolder;
|
||||||
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.ApiRouterConsts;
|
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||||
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
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;
|
||||||
@ -12,9 +13,20 @@ 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;
|
||||||
import io.github.xxyopen.novel.service.UserService;
|
import io.github.xxyopen.novel.service.UserService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户-会员模块 API 控制器
|
* 前台门户-会员模块 API 控制器
|
||||||
@ -22,6 +34,8 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/17
|
* @date 2022/5/17
|
||||||
*/
|
*/
|
||||||
|
@Tag(name = "UserController", description = "前台门户-会员模块")
|
||||||
|
@SecurityRequirement(name = SystemConfigConsts.HTTP_AUTH_HEADER_NAME)
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(ApiRouterConsts.API_FRONT_USER_URL_PREFIX)
|
@RequestMapping(ApiRouterConsts.API_FRONT_USER_URL_PREFIX)
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -34,6 +48,7 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 用户注册接口
|
* 用户注册接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "用户注册接口")
|
||||||
@PostMapping("register")
|
@PostMapping("register")
|
||||||
public RestResp<UserRegisterRespDto> register(@Valid @RequestBody UserRegisterReqDto dto) {
|
public RestResp<UserRegisterRespDto> register(@Valid @RequestBody UserRegisterReqDto dto) {
|
||||||
return userService.register(dto);
|
return userService.register(dto);
|
||||||
@ -42,6 +57,7 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 用户登录接口
|
* 用户登录接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "用户登录接口")
|
||||||
@PostMapping("login")
|
@PostMapping("login")
|
||||||
public RestResp<UserLoginRespDto> login(@Valid @RequestBody UserLoginReqDto dto) {
|
public RestResp<UserLoginRespDto> login(@Valid @RequestBody UserLoginReqDto dto) {
|
||||||
return userService.login(dto);
|
return userService.login(dto);
|
||||||
@ -50,6 +66,7 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 用户信息查询接口
|
* 用户信息查询接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "用户信息查询接口")
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public RestResp<UserInfoRespDto> getUserInfo() {
|
public RestResp<UserInfoRespDto> getUserInfo() {
|
||||||
return userService.getUserInfo(UserHolder.getUserId());
|
return userService.getUserInfo(UserHolder.getUserId());
|
||||||
@ -58,6 +75,7 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 用户信息修改接口
|
* 用户信息修改接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "用户信息修改接口")
|
||||||
@PutMapping
|
@PutMapping
|
||||||
public RestResp<Void> updateUserInfo(@Valid @RequestBody UserInfoUptReqDto dto) {
|
public RestResp<Void> updateUserInfo(@Valid @RequestBody UserInfoUptReqDto dto) {
|
||||||
dto.setUserId(UserHolder.getUserId());
|
dto.setUserId(UserHolder.getUserId());
|
||||||
@ -67,6 +85,7 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 用户反馈提交接口
|
* 用户反馈提交接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "用户反馈提交接口")
|
||||||
@PostMapping("feedback")
|
@PostMapping("feedback")
|
||||||
public RestResp<Void> submitFeedback(@RequestBody String content) {
|
public RestResp<Void> submitFeedback(@RequestBody String content) {
|
||||||
return userService.saveFeedback(UserHolder.getUserId(), content);
|
return userService.saveFeedback(UserHolder.getUserId(), content);
|
||||||
@ -75,14 +94,16 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 用户反馈删除接口
|
* 用户反馈删除接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "用户反馈删除接口")
|
||||||
@DeleteMapping("feedback/{id}")
|
@DeleteMapping("feedback/{id}")
|
||||||
public RestResp<Void> deleteFeedback(@PathVariable Long id) {
|
public RestResp<Void> deleteFeedback(@Parameter(description = "反馈ID") @PathVariable Long id) {
|
||||||
return userService.deleteFeedback(UserHolder.getUserId(), id);
|
return userService.deleteFeedback(UserHolder.getUserId(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发表评论接口
|
* 发表评论接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "发表评论接口")
|
||||||
@PostMapping("comment")
|
@PostMapping("comment")
|
||||||
public RestResp<Void> comment(@Valid @RequestBody UserCommentReqDto dto) {
|
public RestResp<Void> comment(@Valid @RequestBody UserCommentReqDto dto) {
|
||||||
dto.setUserId(UserHolder.getUserId());
|
dto.setUserId(UserHolder.getUserId());
|
||||||
@ -92,26 +113,28 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 修改评论接口
|
* 修改评论接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "修改评论接口")
|
||||||
@PutMapping("comment/{id}")
|
@PutMapping("comment/{id}")
|
||||||
public RestResp<Void> updateComment(@PathVariable Long id, String content) {
|
public RestResp<Void> updateComment(@Parameter(description = "评论ID") @PathVariable Long id,
|
||||||
|
String content) {
|
||||||
return bookService.updateComment(UserHolder.getUserId(), id, content);
|
return bookService.updateComment(UserHolder.getUserId(), id, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除评论接口
|
* 删除评论接口
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "删除评论接口")
|
||||||
@DeleteMapping("comment/{id}")
|
@DeleteMapping("comment/{id}")
|
||||||
public RestResp<Void> deleteComment(@PathVariable Long id) {
|
public RestResp<Void> deleteComment(@Parameter(description = "评论ID") @PathVariable Long id) {
|
||||||
return bookService.deleteComment(UserHolder.getUserId(), id);
|
return bookService.deleteComment(UserHolder.getUserId(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询书架状态接口
|
* 查询书架状态接口 0-不在书架 1-已在书架
|
||||||
* 0-不在书架
|
|
||||||
* 1-已在书架
|
|
||||||
*/
|
*/
|
||||||
|
@Operation(summary = "查询书架状态接口")
|
||||||
@GetMapping("bookshelf_status")
|
@GetMapping("bookshelf_status")
|
||||||
public RestResp<Integer> getBookshelfStatus(@RequestBody String bookId) {
|
public RestResp<Integer> getBookshelfStatus(@Parameter(description = "小说ID") String bookId) {
|
||||||
return userService.getBookshelfStatus(UserHolder.getUserId(), bookId);
|
return userService.getBookshelfStatus(UserHolder.getUserId(), bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.github.xxyopen.novel.core.annotation;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分布式锁-Key 注解
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/6/20
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||||
|
public @interface Key {
|
||||||
|
|
||||||
|
String expr() default "";
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package io.github.xxyopen.novel.core.annotation;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分布式锁 注解
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/6/20
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target(METHOD)
|
||||||
|
public @interface Lock {
|
||||||
|
|
||||||
|
String prefix();
|
||||||
|
|
||||||
|
boolean isWait() default false;
|
||||||
|
|
||||||
|
long waitTime() default 3L;
|
||||||
|
|
||||||
|
ErrorCodeEnum failCode() default ErrorCodeEnum.OK;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package io.github.xxyopen.novel.core.aspect;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.annotation.Key;
|
||||||
|
import io.github.xxyopen.novel.core.annotation.Lock;
|
||||||
|
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.redisson.api.RLock;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.common.TemplateParserContext;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分布式锁 切面
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/6/20
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public record LockAspect(RedissonClient redissonClient) {
|
||||||
|
|
||||||
|
private static final String KEY_PREFIX = "Lock";
|
||||||
|
|
||||||
|
private static final String KEY_SEPARATOR = "::";
|
||||||
|
|
||||||
|
@Around(value = "@annotation(io.github.xxyopen.novel.core.annotation.Lock)")
|
||||||
|
@SneakyThrows
|
||||||
|
public Object doAround(ProceedingJoinPoint joinPoint) {
|
||||||
|
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
Method targetMethod = methodSignature.getMethod();
|
||||||
|
Lock lock = targetMethod.getAnnotation(Lock.class);
|
||||||
|
String lockKey = KEY_PREFIX + buildLockKey(lock.prefix(), targetMethod,
|
||||||
|
joinPoint.getArgs());
|
||||||
|
RLock rLock = redissonClient.getLock(lockKey);
|
||||||
|
if (lock.isWait() ? rLock.tryLock(lock.waitTime(), TimeUnit.SECONDS) : rLock.tryLock()) {
|
||||||
|
try {
|
||||||
|
return joinPoint.proceed();
|
||||||
|
} finally {
|
||||||
|
rLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BusinessException(lock.failCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildLockKey(String prefix, Method method, Object[] args) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (StringUtils.hasText(prefix)) {
|
||||||
|
builder.append(KEY_SEPARATOR).append(prefix);
|
||||||
|
}
|
||||||
|
Parameter[] parameters = method.getParameters();
|
||||||
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
|
builder.append(KEY_SEPARATOR);
|
||||||
|
if (parameters[i].isAnnotationPresent(Key.class)) {
|
||||||
|
Key key = parameters[i].getAnnotation(Key.class);
|
||||||
|
builder.append(parseKeyExpr(key.expr(), args[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseKeyExpr(String expr, Object arg) {
|
||||||
|
if (!StringUtils.hasText(expr)) {
|
||||||
|
return arg.toString();
|
||||||
|
}
|
||||||
|
ExpressionParser parser = new SpelExpressionParser();
|
||||||
|
Expression expression = parser.parseExpression(expr, new TemplateParserContext());
|
||||||
|
return expression.getValue(arg, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 平台后台管理系统 认证策略
|
* 平台后台管理系统 认证授权策略
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/18
|
* @date 2022/5/18
|
||||||
@ -18,4 +18,5 @@ public class AdminAuthStrategy implements AuthStrategy {
|
|||||||
public void auth(String token, String requestUri) throws BusinessException {
|
public void auth(String token, String requestUri) throws BusinessException {
|
||||||
// TODO 平台后台 token 校验
|
// TODO 平台后台 token 校验
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,9 +6,8 @@ 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.cache.UserInfoCacheManager;
|
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 策略模式实现用户认证授权功能
|
* 策略模式实现用户认证授权功能
|
||||||
@ -19,7 +18,7 @@ import java.util.Objects;
|
|||||||
public interface AuthStrategy {
|
public interface AuthStrategy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求用户认证
|
* 用户认证授权
|
||||||
*
|
*
|
||||||
* @param token 登录 token
|
* @param token 登录 token
|
||||||
* @param requestUri 请求的 URI
|
* @param requestUri 请求的 URI
|
||||||
@ -28,14 +27,15 @@ public interface AuthStrategy {
|
|||||||
void auth(String token, String requestUri) throws BusinessException;
|
void auth(String token, String requestUri) throws BusinessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台多系统单点登录统一账号认证(门户系统、作家系统以及后面会扩展的漫画系统和视频系统等)
|
* 前台多系统单点登录统一账号认证授权(门户系统、作家系统以及后面会扩展的漫画系统和视频系统等)
|
||||||
*
|
*
|
||||||
* @param jwtUtils jwt 工具
|
* @param jwtUtils jwt 工具
|
||||||
* @param userInfoCacheManager 用户缓存管理对象
|
* @param userInfoCacheManager 用户缓存管理对象
|
||||||
* @param token token 登录 token
|
* @param token token 登录 token
|
||||||
* @return 用户ID
|
* @return 用户ID
|
||||||
*/
|
*/
|
||||||
default Long authSSO(JwtUtils jwtUtils, UserInfoCacheManager userInfoCacheManager, String token) {
|
default Long authSSO(JwtUtils jwtUtils, UserInfoCacheManager userInfoCacheManager,
|
||||||
|
String token) {
|
||||||
if (!StringUtils.hasText(token)) {
|
if (!StringUtils.hasText(token)) {
|
||||||
// token 为空
|
// token 为空
|
||||||
throw new BusinessException(ErrorCodeEnum.USER_LOGIN_EXPIRED);
|
throw new BusinessException(ErrorCodeEnum.USER_LOGIN_EXPIRED);
|
||||||
@ -55,4 +55,5 @@ public interface AuthStrategy {
|
|||||||
// 返回 userId
|
// 返回 userId
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,13 @@ 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.cache.AuthorInfoCacheManager;
|
import io.github.xxyopen.novel.manager.cache.AuthorInfoCacheManager;
|
||||||
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家后台管理系统 认证策略
|
* 作家后台管理系统 认证授权策略
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/18
|
* @date 2022/5/18
|
||||||
@ -31,8 +30,11 @@ public class AuthorAuthStrategy implements AuthStrategy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 不需要进行作家权限认证的 URI
|
* 不需要进行作家权限认证的 URI
|
||||||
* */
|
*/
|
||||||
private static final List<String> EXCLUDE_URI = List.of(ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/register");
|
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, String requestUri) throws BusinessException {
|
public void auth(String token, String requestUri) throws BusinessException {
|
||||||
@ -52,4 +54,5 @@ public class AuthorAuthStrategy implements AuthStrategy {
|
|||||||
// 设置作家ID到当前线程
|
// 设置作家ID到当前线程
|
||||||
UserHolder.setAuthorId(authorInfo.getId());
|
UserHolder.setAuthorId(authorInfo.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户系统 认证策略
|
* 前台门户系统 认证授权策略
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/18
|
* @date 2022/5/18
|
||||||
@ -25,4 +25,5 @@ public class FrontAuthStrategy implements AuthStrategy {
|
|||||||
// 统一账号认证
|
// 统一账号认证
|
||||||
authSSO(jwtUtils, userInfoCacheManager, token);
|
authSSO(jwtUtils, userInfoCacheManager, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -13,12 +13,12 @@ public class UserHolder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 当前线程用户ID
|
* 当前线程用户ID
|
||||||
* */
|
*/
|
||||||
private static final ThreadLocal<Long> userIdTL = new ThreadLocal<>();
|
private static final ThreadLocal<Long> userIdTL = new ThreadLocal<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当前线程作家ID
|
* 当前线程作家ID
|
||||||
* */
|
*/
|
||||||
private static final ThreadLocal<Long> authorIdTL = new ThreadLocal<>();
|
private static final ThreadLocal<Long> authorIdTL = new ThreadLocal<>();
|
||||||
|
|
||||||
public void setUserId(Long userId) {
|
public void setUserId(Long userId) {
|
||||||
|
@ -11,30 +11,30 @@ public class CommonConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 是
|
* 是
|
||||||
* */
|
*/
|
||||||
public static final Integer YES = 1;
|
public static final Integer YES = 1;
|
||||||
public static final String TRUE = "true";
|
public static final String TRUE = "true";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 否
|
* 否
|
||||||
* */
|
*/
|
||||||
public static final Integer NO = 0;
|
public static final Integer NO = 0;
|
||||||
public static final String FALSE = "false";
|
public static final String FALSE = "false";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 性别常量
|
* 性别常量
|
||||||
* */
|
*/
|
||||||
public enum SexEnum {
|
public enum SexEnum {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 男
|
* 男
|
||||||
* */
|
*/
|
||||||
MALE(0, "男"),
|
MALE(0, "男"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 女
|
* 女
|
||||||
* */
|
*/
|
||||||
FEMALE(1, "女");
|
FEMALE(1, "女");
|
||||||
|
|
||||||
SexEnum(int code, String desc) {
|
SexEnum(int code, String desc) {
|
||||||
|
@ -5,15 +5,12 @@ import lombok.Getter;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误码枚举类。
|
* 错误码枚举类。
|
||||||
*
|
* <p>
|
||||||
* 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。
|
* 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。 错误产生来源分为 A/B/C, A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付 超时等问题; B
|
||||||
* 错误产生来源分为 A/B/C, A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付
|
* 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题; C 表示错误来源 于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 0001 到 9999,大类之间的
|
||||||
* 超时等问题; B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题; C 表示错误来源
|
|
||||||
* 于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 0001 到 9999,大类之间的
|
|
||||||
* 步长间距预留 100。
|
* 步长间距预留 100。
|
||||||
*
|
* <p>
|
||||||
* 错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。
|
* 错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。 在无法更加具体确定的错误场景中,可以直接使用一级宏观错误码。
|
||||||
* 在无法更加具体确定的错误场景中,可以直接使用一级宏观错误码。
|
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/11
|
* @date 2022/5/11
|
||||||
@ -24,113 +21,132 @@ public enum ErrorCodeEnum {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 正确执行后的返回
|
* 正确执行后的返回
|
||||||
* */
|
*/
|
||||||
OK("00000", "一切 ok"),
|
OK("00000", "一切 ok"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一级宏观错误码,用户端错误
|
* 一级宏观错误码,用户端错误
|
||||||
* */
|
*/
|
||||||
USER_ERROR("A0001", "用户端错误"),
|
USER_ERROR("A0001", "用户端错误"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 二级宏观错误码,用户注册错误
|
* 二级宏观错误码,用户注册错误
|
||||||
* */
|
*/
|
||||||
USER_REGISTER_ERROR("A0100", "用户注册错误"),
|
USER_REGISTER_ERROR("A0100", "用户注册错误"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户未同意隐私协议
|
* 用户未同意隐私协议
|
||||||
* */
|
*/
|
||||||
USER_NO_AGREE_PRIVATE_ERROR("A0101", "用户未同意隐私协议"),
|
USER_NO_AGREE_PRIVATE_ERROR("A0101", "用户未同意隐私协议"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册国家或地区受限
|
* 注册国家或地区受限
|
||||||
* */
|
*/
|
||||||
USER_REGISTER_AREA_LIMIT_ERROR("A0102", "注册国家或地区受限"),
|
USER_REGISTER_AREA_LIMIT_ERROR("A0102", "注册国家或地区受限"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户验证码错误
|
* 用户验证码错误
|
||||||
* */
|
*/
|
||||||
USER_VERIFY_CODE_ERROR("A0240", "用户验证码错误"),
|
USER_VERIFY_CODE_ERROR("A0240", "用户验证码错误"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名已存在
|
* 用户名已存在
|
||||||
* */
|
*/
|
||||||
USER_NAME_EXIST("A0111", "用户名已存在"),
|
USER_NAME_EXIST("A0111", "用户名已存在"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户账号不存在
|
* 用户账号不存在
|
||||||
* */
|
*/
|
||||||
USER_ACCOUNT_NOT_EXIST("A0201", "用户账号不存在"),
|
USER_ACCOUNT_NOT_EXIST("A0201", "用户账号不存在"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户密码错误
|
* 用户密码错误
|
||||||
* */
|
*/
|
||||||
USER_PASSWORD_ERROR("A0210", "用户密码错误"),
|
USER_PASSWORD_ERROR("A0210", "用户密码错误"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 二级宏观错误码,用户请求参数错误
|
* 二级宏观错误码,用户请求参数错误
|
||||||
* */
|
*/
|
||||||
USER_REQUEST_PARAM_ERROR("A0400", "用户请求参数错误"),
|
USER_REQUEST_PARAM_ERROR("A0400", "用户请求参数错误"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户登录已过期
|
* 用户登录已过期
|
||||||
* */
|
*/
|
||||||
USER_LOGIN_EXPIRED("A0230", "用户登录已过期"),
|
USER_LOGIN_EXPIRED("A0230", "用户登录已过期"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 访问未授权
|
* 访问未授权
|
||||||
* */
|
*/
|
||||||
USER_UN_AUTH("A0301", "访问未授权"),
|
USER_UN_AUTH("A0301", "访问未授权"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户请求服务异常
|
||||||
|
*/
|
||||||
|
USER_REQ_EXCEPTION("A0500", "用户请求服务异常"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求超出限制
|
||||||
|
*/
|
||||||
|
USER_REQ_MANY("A0501", "请求超出限制"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户评论异常
|
* 用户评论异常
|
||||||
* */
|
*/
|
||||||
USER_COMMENT("A2000", "用户评论异常"),
|
USER_COMMENT("A2000", "用户评论异常"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户评论异常
|
* 用户评论异常
|
||||||
* */
|
*/
|
||||||
USER_COMMENTED("A2001", "用户已发表评论"),
|
USER_COMMENTED("A2001", "用户已发表评论"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家发布异常
|
||||||
|
*/
|
||||||
|
AUTHOR_PUBLISH("A3000", "作家发布异常"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说名已存在
|
||||||
|
*/
|
||||||
|
AUTHOR_BOOK_NAME_EXIST("A3001", "小说名已存在"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户上传文件异常
|
* 用户上传文件异常
|
||||||
* */
|
*/
|
||||||
USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"),
|
USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户上传文件类型不匹配
|
* 用户上传文件类型不匹配
|
||||||
* */
|
*/
|
||||||
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"),
|
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一级宏观错误码,系统执行出错
|
* 一级宏观错误码,系统执行出错
|
||||||
* */
|
*/
|
||||||
SYSTEM_ERROR("B0001", "系统执行出错"),
|
SYSTEM_ERROR("B0001", "系统执行出错"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 二级宏观错误码,系统执行超时
|
* 二级宏观错误码,系统执行超时
|
||||||
* */
|
*/
|
||||||
SYSTEM_TIMEOUT_ERROR("B0100", "系统执行超时"),
|
SYSTEM_TIMEOUT_ERROR("B0100", "系统执行超时"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一级宏观错误码,调用第三方服务出错
|
* 一级宏观错误码,调用第三方服务出错
|
||||||
* */
|
*/
|
||||||
THIRD_SERVICE_ERROR("C0001", "调用第三方服务出错"),
|
THIRD_SERVICE_ERROR("C0001", "调用第三方服务出错"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一级宏观错误码,中间件服务出错
|
* 一级宏观错误码,中间件服务出错
|
||||||
* */
|
*/
|
||||||
MIDDLEWARE_SERVICE_ERROR("C0100","中间件服务出错")
|
MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错");
|
||||||
;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误码
|
* 错误码
|
||||||
* */
|
*/
|
||||||
private String code;
|
private final String code;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中文描述
|
* 中文描述
|
||||||
* */
|
*/
|
||||||
private String message;
|
private final String message;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ public class CommonExceptionHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理数据校验异常
|
* 处理数据校验异常
|
||||||
* */
|
*/
|
||||||
@ExceptionHandler(BindException.class)
|
@ExceptionHandler(BindException.class)
|
||||||
public RestResp<Void> handlerBindException(BindException e) {
|
public RestResp<Void> handlerBindException(BindException e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
@ -28,7 +28,7 @@ public class CommonExceptionHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理业务异常
|
* 处理业务异常
|
||||||
* */
|
*/
|
||||||
@ExceptionHandler(BusinessException.class)
|
@ExceptionHandler(BusinessException.class)
|
||||||
public RestResp<Void> handlerBusinessException(BusinessException e) {
|
public RestResp<Void> handlerBusinessException(BusinessException e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
@ -37,7 +37,7 @@ public class CommonExceptionHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理系统异常
|
* 处理系统异常
|
||||||
* */
|
*/
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public RestResp<Void> handlerException(Exception e) {
|
public RestResp<Void> handlerException(Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.core.common.req;
|
package io.github.xxyopen.novel.core.common.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13,18 +14,20 @@ public class PageReqDto {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求页码,默认第 1 页
|
* 请求页码,默认第 1 页
|
||||||
* */
|
*/
|
||||||
|
@Parameter(description = "请求页码,默认第 1 页")
|
||||||
private int pageNum = 1;
|
private int pageNum = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每页大小,默认每页 10 条
|
* 每页大小,默认每页 10 条
|
||||||
* */
|
*/
|
||||||
|
@Parameter(description = "每页大小,默认每页 10 条")
|
||||||
private int pageSize = 10;
|
private int pageSize = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否查询所有,默认不查所有
|
* 是否查询所有,默认不查所有 为 true 时,pageNum 和 pageSize 无效
|
||||||
* 为 true 时,pageNum 和 pageSize 无效
|
*/
|
||||||
* */
|
@Parameter(hidden = true)
|
||||||
private boolean fetchAll = false;
|
private boolean fetchAll = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package io.github.xxyopen.novel.core.common.resp;
|
package io.github.xxyopen.novel.core.common.resp;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页响应数据格式封装
|
* 分页响应数据格式封装
|
||||||
@ -34,8 +33,7 @@ public class PageRespDto<T> {
|
|||||||
private final List<? extends T> list;
|
private final List<? extends T> list;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 该构造函数用于通用分页查询的场景
|
* 该构造函数用于通用分页查询的场景 接收普通分页数据和普通集合
|
||||||
* 接收普通分页数据和普通集合
|
|
||||||
*/
|
*/
|
||||||
public PageRespDto(long pageNum, long pageSize, long total, List<T> list) {
|
public PageRespDto(long pageNum, long pageSize, long total, List<T> list) {
|
||||||
this.pageNum = pageNum;
|
this.pageNum = pageNum;
|
||||||
@ -50,7 +48,7 @@ public class PageRespDto<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分页数
|
* 获取分页数
|
||||||
* */
|
*/
|
||||||
public long getPages() {
|
public long getPages() {
|
||||||
if (this.pageSize == 0L) {
|
if (this.pageSize == 0L) {
|
||||||
return 0L;
|
return 0L;
|
||||||
@ -59,7 +57,6 @@ public class PageRespDto<T> {
|
|||||||
if (this.total % this.pageSize != 0L) {
|
if (this.total % this.pageSize != 0L) {
|
||||||
++pages;
|
++pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.xxyopen.novel.core.common.resp;
|
package io.github.xxyopen.novel.core.common.resp;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -17,16 +18,19 @@ public class RestResp<T> {
|
|||||||
/**
|
/**
|
||||||
* 响应码
|
* 响应码
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "错误码,00000-没有错误")
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应消息
|
* 响应消息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "响应消息")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应数据
|
* 响应数据
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "响应数据")
|
||||||
private T data;
|
private T data;
|
||||||
|
|
||||||
private RestResp() {
|
private RestResp() {
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package io.github.xxyopen.novel.core.common.util;
|
package io.github.xxyopen.novel.core.common.util;
|
||||||
|
|
||||||
import lombok.experimental.UtilityClass;
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
import javax.imageio.ImageIO;
|
import java.awt.Graphics;
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片验证码工具类
|
* 图片验证码工具类
|
||||||
|
@ -16,6 +16,7 @@ public class IpUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取真实IP
|
* 获取真实IP
|
||||||
|
*
|
||||||
* @return 真实IP
|
* @return 真实IP
|
||||||
*/
|
*/
|
||||||
public String getRealIp(HttpServletRequest request) {
|
public String getRealIp(HttpServletRequest request) {
|
||||||
|
@ -2,6 +2,11 @@ package io.github.xxyopen.novel.core.config;
|
|||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.springframework.cache.CacheManager;
|
import org.springframework.cache.CacheManager;
|
||||||
import org.springframework.cache.caffeine.CaffeineCache;
|
import org.springframework.cache.caffeine.CaffeineCache;
|
||||||
import org.springframework.cache.support.SimpleCacheManager;
|
import org.springframework.cache.support.SimpleCacheManager;
|
||||||
@ -13,12 +18,6 @@ import org.springframework.data.redis.cache.RedisCacheManager;
|
|||||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缓存配置类
|
* 缓存配置类
|
||||||
*
|
*
|
||||||
@ -37,9 +36,11 @@ public class CacheConfig {
|
|||||||
SimpleCacheManager cacheManager = new SimpleCacheManager();
|
SimpleCacheManager cacheManager = new SimpleCacheManager();
|
||||||
|
|
||||||
List<CaffeineCache> caches = new ArrayList<>(CacheConsts.CacheEnum.values().length);
|
List<CaffeineCache> caches = new ArrayList<>(CacheConsts.CacheEnum.values().length);
|
||||||
for (CacheConsts.CacheEnum c : CacheConsts.CacheEnum.values()) {
|
// 类型推断 var 非常适合 for 循环,JDK 10 引入,JDK 11 改进
|
||||||
|
for (var c : CacheConsts.CacheEnum.values()) {
|
||||||
if (c.isLocal()) {
|
if (c.isLocal()) {
|
||||||
Caffeine<Object, Object> caffeine = Caffeine.newBuilder().recordStats().maximumSize(c.getMaxSize());
|
Caffeine<Object, Object> caffeine = Caffeine.newBuilder().recordStats()
|
||||||
|
.maximumSize(c.getMaxSize());
|
||||||
if (c.getTtl() > 0) {
|
if (c.getTtl() > 0) {
|
||||||
caffeine.expireAfterWrite(Duration.ofSeconds(c.getTtl()));
|
caffeine.expireAfterWrite(Duration.ofSeconds(c.getTtl()));
|
||||||
}
|
}
|
||||||
@ -56,25 +57,32 @@ public class CacheConfig {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
|
public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
|
||||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(
|
||||||
|
connectionFactory);
|
||||||
|
|
||||||
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
|
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
|
||||||
.disableCachingNullValues().prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX);
|
.disableCachingNullValues().prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX);
|
||||||
|
|
||||||
Map<String, RedisCacheConfiguration> cacheMap = new LinkedHashMap<>(CacheConsts.CacheEnum.values().length);
|
Map<String, RedisCacheConfiguration> cacheMap = new LinkedHashMap<>(
|
||||||
for (CacheConsts.CacheEnum c : CacheConsts.CacheEnum.values()) {
|
CacheConsts.CacheEnum.values().length);
|
||||||
|
// 类型推断 var 非常适合 for 循环,JDK 10 引入,JDK 11 改进
|
||||||
|
for (var c : CacheConsts.CacheEnum.values()) {
|
||||||
if (c.isRemote()) {
|
if (c.isRemote()) {
|
||||||
if (c.getTtl() > 0) {
|
if (c.getTtl() > 0) {
|
||||||
cacheMap.put(c.getName(), RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
cacheMap.put(c.getName(),
|
||||||
.prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX).entryTtl(Duration.ofSeconds(c.getTtl())));
|
RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
||||||
|
.prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX)
|
||||||
|
.entryTtl(Duration.ofSeconds(c.getTtl())));
|
||||||
} else {
|
} else {
|
||||||
cacheMap.put(c.getName(), RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
cacheMap.put(c.getName(),
|
||||||
|
RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
||||||
.prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX));
|
.prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig, cacheMap);
|
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
|
||||||
|
defaultCacheConfig, cacheMap);
|
||||||
redisCacheManager.setTransactionAware(true);
|
redisCacheManager.setTransactionAware(true);
|
||||||
redisCacheManager.initializeCaches();
|
redisCacheManager.initializeCaches();
|
||||||
return redisCacheManager;
|
return redisCacheManager;
|
||||||
|
@ -25,7 +25,7 @@ public class CorsConfig {
|
|||||||
public CorsFilter corsFilter() {
|
public CorsFilter corsFilter() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
// 允许的域,不要写*,否则cookie就无法使用了
|
// 允许的域,不要写*,否则cookie就无法使用了
|
||||||
for (String allowOrigin : corsProperties.getAllowOrigins()) {
|
for (String allowOrigin : corsProperties.allowOrigins()) {
|
||||||
config.addAllowedOrigin(allowOrigin);
|
config.addAllowedOrigin(allowOrigin);
|
||||||
}
|
}
|
||||||
// 允许的头信息
|
// 允许的头信息
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.xxyopen.novel.core.config;
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -12,11 +11,6 @@ import java.util.List;
|
|||||||
* @date 2022/5/17
|
* @date 2022/5/17
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(prefix = "novel.cors")
|
@ConfigurationProperties(prefix = "novel.cors")
|
||||||
@Data
|
public record CorsProperties(List<String> allowOrigins) {
|
||||||
public class CorsProperties {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 允许跨域的域名
|
|
||||||
* */
|
|
||||||
private List<String> allowOrigins;
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.redisson.Redisson;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.redisson.config.Config;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redisson 配置类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/6/20
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RedissonConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SneakyThrows
|
||||||
|
public RedissonClient redissonClient() {
|
||||||
|
Config config = Config.fromYAML(getClass().getResource("/redisson.yml"));
|
||||||
|
return Redisson.create(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,15 +4,16 @@ import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
|||||||
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
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.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;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Web Mvc 相关配置
|
* Spring Web Mvc 相关配置不要加 @EnableWebMvc 注解,否则会导致 jackson 的全局配置失效。因为 @EnableWebMvc 注解会导致
|
||||||
* 不要加 @EnableWebMvc 注解,否则会导致 jackson 的全局配置失效
|
* WebMvcAutoConfiguration 自动配置失效
|
||||||
* 类上添加 @EnableWebMvc 会导致 WebMvcAutoConfiguration 中的自动配置全部失效
|
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/18
|
* @date 2022/5/18
|
||||||
@ -21,28 +22,46 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class WebConfig implements WebMvcConfigurer {
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final FlowLimitInterceptor flowLimitInterceptor;
|
||||||
|
|
||||||
private final AuthInterceptor authInterceptor;
|
private final AuthInterceptor authInterceptor;
|
||||||
|
|
||||||
private final FileInterceptor fileInterceptor;
|
private final FileInterceptor fileInterceptor;
|
||||||
|
|
||||||
|
private final TokenParseInterceptor tokenParseInterceptor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
|
||||||
|
// 流量限制拦截器
|
||||||
|
registry.addInterceptor(flowLimitInterceptor)
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.order(0);
|
||||||
|
|
||||||
// 文件访问拦截
|
// 文件访问拦截
|
||||||
registry.addInterceptor(fileInterceptor)
|
registry.addInterceptor(fileInterceptor)
|
||||||
.addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "**");
|
.addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "**")
|
||||||
|
.order(1);
|
||||||
|
|
||||||
// 权限认证拦截
|
// 权限认证拦截
|
||||||
registry.addInterceptor(authInterceptor)
|
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_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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package io.github.xxyopen.novel.core.config;
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Xss 过滤配置属性
|
* Xss 过滤配置属性
|
||||||
@ -12,17 +10,6 @@ import java.util.List;
|
|||||||
* @date 2022/5/17
|
* @date 2022/5/17
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(prefix = "novel.xss")
|
@ConfigurationProperties(prefix = "novel.xss")
|
||||||
@Data
|
public record XssProperties(Boolean enabled, List<String> excludes) {
|
||||||
public class XssProperties {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过滤开关
|
|
||||||
* */
|
|
||||||
private Boolean enabled;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 排除链接
|
|
||||||
* */
|
|
||||||
private List<String> excludes;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,22 +10,22 @@ public class AmqpConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息改变 MQ
|
* 小说信息改变 MQ
|
||||||
* */
|
*/
|
||||||
public static class BookChangeMq {
|
public static class BookChangeMq {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息改变交换机
|
* 小说信息改变交换机
|
||||||
* */
|
*/
|
||||||
public static final String EXCHANGE_NAME = "EXCHANGE-BOOK-CHANGE";
|
public static final String EXCHANGE_NAME = "EXCHANGE-BOOK-CHANGE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elasticsearch book 索引更新的队列
|
* Elasticsearch book 索引更新的队列
|
||||||
* */
|
*/
|
||||||
public static final String QUEUE_ES_UPDATE = "QUEUE-ES-BOOK-UPDATE";
|
public static final String QUEUE_ES_UPDATE = "QUEUE-ES-BOOK-UPDATE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redis book 缓存更新的队列
|
* Redis book 缓存更新的队列
|
||||||
* */
|
*/
|
||||||
public static final String QUEUE_REDIS_UPDATE = "QUEUE-REDIS-BOOK-UPDATE";
|
public static final String QUEUE_REDIS_UPDATE = "QUEUE-REDIS-BOOK-UPDATE";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,29 +34,34 @@ public class ApiRouterConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页模块请求路径前缀
|
* 首页模块请求路径前缀
|
||||||
* */
|
*/
|
||||||
public static final String HOME_URL_PREFIX = "/home";
|
public static final String HOME_URL_PREFIX = "/home";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页模块请求路径前缀
|
* 首页模块请求路径前缀
|
||||||
* */
|
*/
|
||||||
public static final String NEWS_URL_PREFIX = "/news";
|
public static final String NEWS_URL_PREFIX = "/news";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说模块请求路径前缀
|
* 小说模块请求路径前缀
|
||||||
* */
|
*/
|
||||||
public static final String BOOK_URL_PREFIX = "/book";
|
public static final String BOOK_URL_PREFIX = "/book";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会员模块请求路径前缀
|
* 会员模块请求路径前缀
|
||||||
* */
|
*/
|
||||||
public static final String USER_URL_PREFIX = "/user";
|
public static final String USER_URL_PREFIX = "/user";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源(图片/视频/文档)模块请求路径前缀
|
* 资源(图片/视频/文档)模块请求路径前缀
|
||||||
* */
|
*/
|
||||||
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请求路径前缀
|
||||||
*/
|
*/
|
||||||
@ -80,6 +85,13 @@ public class ApiRouterConsts {
|
|||||||
/**
|
/**
|
||||||
* 前台门户资源(图片/视频/文档)相关API请求路径前缀
|
* 前台门户资源(图片/视频/文档)相关API请求路径前缀
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ public class CacheConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说分类列表缓存
|
* 小说分类列表缓存
|
||||||
* */
|
*/
|
||||||
public static final String BOOK_CATEGORY_LIST_CACHE_NAME = "bookCategoryListCache";
|
public static final String BOOK_CATEGORY_LIST_CACHE_NAME = "bookCategoryListCache";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,13 +76,14 @@ public class CacheConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 最近更新小说ID列表缓存
|
* 最近更新小说ID列表缓存
|
||||||
* */
|
*/
|
||||||
public static final String LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME = "lastUpdateBookIdListCache";
|
public static final String LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME = "lastUpdateBookIdListCache";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片验证码缓存 KEY
|
* 图片验证码缓存 KEY
|
||||||
* */
|
*/
|
||||||
public static final String IMG_VERIFY_CODE_CACHE_KEY = REDIS_CACHE_PREFIX + "imgVerifyCodeCache::";
|
public static final String IMG_VERIFY_CODE_CACHE_KEY =
|
||||||
|
REDIS_CACHE_PREFIX + "imgVerifyCodeCache::";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户信息缓存
|
* 用户信息缓存
|
||||||
|
@ -10,7 +10,6 @@ import lombok.Getter;
|
|||||||
*/
|
*/
|
||||||
public class DatabaseConsts {
|
public class DatabaseConsts {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户信息表
|
* 用户信息表
|
||||||
*/
|
*/
|
||||||
@ -89,8 +88,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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ public class EsConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说索引
|
* 小说索引
|
||||||
* */
|
*/
|
||||||
public static class BookIndex {
|
public static class BookIndex {
|
||||||
|
|
||||||
private BookIndex() {
|
private BookIndex() {
|
||||||
@ -23,7 +23,7 @@ public class EsConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 索引名
|
* 索引名
|
||||||
* */
|
*/
|
||||||
public static final String INDEX_NAME = "book";
|
public static final String INDEX_NAME = "book";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,32 +14,32 @@ public class SystemConfigConsts {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Http 请求认证 Header
|
* Http 请求认证 Header
|
||||||
* */
|
*/
|
||||||
public static final String HTTP_AUTH_HEADER_NAME = "Authorization";
|
public static final String HTTP_AUTH_HEADER_NAME = "Authorization";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户系统标识
|
* 前台门户系统标识
|
||||||
* */
|
*/
|
||||||
public static final String NOVEL_FRONT_KEY = "front";
|
public static final String NOVEL_FRONT_KEY = "front";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家管理系统标识
|
* 作家管理系统标识
|
||||||
* */
|
*/
|
||||||
public static final String NOVEL_AUTHOR_KEY = "author";
|
public static final String NOVEL_AUTHOR_KEY = "author";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后台管理系统标识
|
* 后台管理系统标识
|
||||||
* */
|
*/
|
||||||
public static final String NOVEL_ADMIN_KEY = "admin";
|
public static final String NOVEL_ADMIN_KEY = "admin";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片上传目录
|
* 图片上传目录
|
||||||
* */
|
*/
|
||||||
public static final String IMAGE_UPLOAD_DIRECTORY = "/image/";
|
public static final String IMAGE_UPLOAD_DIRECTORY = "/image/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 常量类实例化异常信息
|
* 常量类实例化异常信息
|
||||||
* */
|
*/
|
||||||
public static final String CONST_INSTANCE_EXCEPTION_MSG = "Constant class";
|
public static final String CONST_INSTANCE_EXCEPTION_MSG = "Constant class";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,22 @@ package io.github.xxyopen.novel.core.filter;
|
|||||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
import io.github.xxyopen.novel.core.config.XssProperties;
|
import io.github.xxyopen.novel.core.config.XssProperties;
|
||||||
import io.github.xxyopen.novel.core.wrapper.XssHttpServletRequestWrapper;
|
import io.github.xxyopen.novel.core.wrapper.XssHttpServletRequestWrapper;
|
||||||
import jakarta.servlet.*;
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.FilterConfig;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.ServletResponse;
|
||||||
import jakarta.servlet.annotation.WebFilter;
|
import jakarta.servlet.annotation.WebFilter;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 防止 XSS 攻击的过滤器
|
* 防止 XSS 攻击的过滤器
|
||||||
*
|
*
|
||||||
@ -28,7 +32,6 @@ import java.util.regex.Pattern;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class XssFilter implements Filter {
|
public class XssFilter implements Filter {
|
||||||
|
|
||||||
|
|
||||||
private final XssProperties xssProperties;
|
private final XssProperties xssProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -37,22 +40,24 @@ public class XssFilter implements Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
|
||||||
|
FilterChain filterChain) throws IOException, ServletException {
|
||||||
HttpServletRequest req = (HttpServletRequest) servletRequest;
|
HttpServletRequest req = (HttpServletRequest) servletRequest;
|
||||||
if (handleExcludeUrl(req)) {
|
if (handleExcludeUrl(req)) {
|
||||||
filterChain.doFilter(servletRequest, servletResponse);
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest);
|
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
|
||||||
|
(HttpServletRequest) servletRequest);
|
||||||
filterChain.doFilter(xssRequest, servletResponse);
|
filterChain.doFilter(xssRequest, servletResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleExcludeUrl(HttpServletRequest request) {
|
private boolean handleExcludeUrl(HttpServletRequest request) {
|
||||||
if (CollectionUtils.isEmpty(xssProperties.getExcludes())) {
|
if (CollectionUtils.isEmpty(xssProperties.excludes())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String url = request.getServletPath();
|
String url = request.getServletPath();
|
||||||
for (String pattern : xssProperties.getExcludes()) {
|
for (String pattern : xssProperties.excludes()) {
|
||||||
Pattern p = Pattern.compile("^" + pattern);
|
Pattern p = Pattern.compile("^" + pattern);
|
||||||
Matcher m = p.matcher(url);
|
Matcher m = p.matcher(url);
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
|
@ -9,18 +9,16 @@ import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
|||||||
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证 拦截器
|
* 认证授权 拦截器:为了注入其它的 Spring beans,需要通过 @Component 注解将该拦截器注册到 Spring 上下文
|
||||||
* 为了注入其它的 Spring beans,需要通过 @Component 注解将该拦截器注册到 Spring 上下文
|
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/18
|
* @date 2022/5/18
|
||||||
@ -35,7 +33,8 @@ public class AuthInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Object handler) throws Exception {
|
||||||
// 获取登录 JWT
|
// 获取登录 JWT
|
||||||
String token = request.getHeader(SystemConfigConsts.HTTP_AUTH_HEADER_NAME);
|
String token = request.getHeader(SystemConfigConsts.HTTP_AUTH_HEADER_NAME);
|
||||||
|
|
||||||
@ -55,14 +54,16 @@ public class AuthInterceptor implements HandlerInterceptor {
|
|||||||
// 认证失败
|
// 认证失败
|
||||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
response.getWriter().write(objectMapper.writeValueAsString(RestResp.fail(exception.getErrorCodeEnum())));
|
response.getWriter().write(
|
||||||
|
objectMapper.writeValueAsString(RestResp.fail(exception.getErrorCodeEnum())));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||||
|
ModelAndView modelAndView) throws Exception {
|
||||||
// 清理当前线程保存的用户数据
|
// 清理当前线程保存的用户数据
|
||||||
UserHolder.clear();
|
UserHolder.clear();
|
||||||
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
||||||
|
@ -2,15 +2,14 @@ package io.github.xxyopen.novel.core.interceptor;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件 拦截器
|
* 文件 拦截器
|
||||||
*
|
*
|
||||||
@ -26,12 +25,14 @@ public class FileInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Object handler) throws Exception {
|
||||||
// 获取请求的 URI
|
// 获取请求的 URI
|
||||||
String requestUri = request.getRequestURI();
|
String requestUri = request.getRequestURI();
|
||||||
// 缓存10天
|
// 缓存10天
|
||||||
response.setDateHeader("expires", System.currentTimeMillis() + 60 * 60 * 24 * 10 * 1000);
|
response.setDateHeader("expires", System.currentTimeMillis() + 60 * 60 * 24 * 10 * 1000);
|
||||||
try (OutputStream out = response.getOutputStream(); InputStream input = new FileInputStream(fileUploadPath + requestUri)) {
|
try (OutputStream out = response.getOutputStream(); InputStream input = new FileInputStream(
|
||||||
|
fileUploadPath + requestUri)) {
|
||||||
byte[] b = new byte[4096];
|
byte[] b = new byte[4096];
|
||||||
for (int n; (n = input.read(b)) != -1; ) {
|
for (int n; (n = input.read(b)) != -1; ) {
|
||||||
out.write(b, 0, n);
|
out.write(b, 0, n);
|
||||||
|
@ -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 java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流量限制 拦截器:实现接口防刷和限流
|
||||||
|
*
|
||||||
|
* @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,45 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,8 @@ package io.github.xxyopen.novel.core.json.deserializer;
|
|||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import org.springframework.boot.jackson.JsonComponent;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import org.springframework.boot.jackson.JsonComponent;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,13 +17,13 @@ import java.io.IOException;
|
|||||||
public class GlobalJsonDeserializer {
|
public class GlobalJsonDeserializer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串反序列化器
|
* 字符串反序列化器:过滤特殊字符,解决 XSS 攻击
|
||||||
* 过滤特殊字符,解决 XSS 攻击
|
|
||||||
*/
|
*/
|
||||||
public static class StringDeserializer extends JsonDeserializer<String> {
|
public static class StringDeserializer extends JsonDeserializer<String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
public String deserialize(JsonParser jsonParser,
|
||||||
|
DeserializationContext deserializationContext) throws IOException {
|
||||||
return jsonParser.getValueAsString()
|
return jsonParser.getValueAsString()
|
||||||
.replace("<", "<")
|
.replace("<", "<")
|
||||||
.replace(">", ">");
|
.replace(">", ">");
|
||||||
|
@ -3,7 +3,6 @@ package io.github.xxyopen.novel.core.json.serializer;
|
|||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,7 +14,8 @@ import java.io.IOException;
|
|||||||
public class UsernameSerializer extends JsonSerializer<String> {
|
public class UsernameSerializer extends JsonSerializer<String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
public void serialize(String s, JsonGenerator jsonGenerator,
|
||||||
|
SerializerProvider serializerProvider) throws IOException {
|
||||||
jsonGenerator.writeString(s.substring(0, 4) + "****" + s.substring(8));
|
jsonGenerator.writeString(s.substring(0, 4) + "****" + s.substring(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ import org.springframework.stereotype.Component;
|
|||||||
* @date 2022/5/25
|
* @date 2022/5/25
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true")
|
@ConditionalOnProperty(prefix = "spring", name = {"elasticsearch.enable",
|
||||||
|
"amqp.enable"}, havingValue = "true")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RabbitQueueListener {
|
public class RabbitQueueListener {
|
||||||
@ -32,7 +33,7 @@ public class RabbitQueueListener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听小说信息改变的 ES 更新队列,更新最新小说信息到 ES
|
* 监听小说信息改变的 ES 更新队列,更新最新小说信息到 ES
|
||||||
* */
|
*/
|
||||||
@RabbitListener(queues = AmqpConsts.BookChangeMq.QUEUE_ES_UPDATE)
|
@RabbitListener(queues = AmqpConsts.BookChangeMq.QUEUE_ES_UPDATE)
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void updateEsBook(Long bookId) {
|
public void updateEsBook(Long bookId) {
|
||||||
|
@ -6,20 +6,20 @@ import co.elastic.clients.elasticsearch.core.BulkRequest;
|
|||||||
import co.elastic.clients.elasticsearch.core.BulkResponse;
|
import co.elastic.clients.elasticsearch.core.BulkResponse;
|
||||||
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
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.DatabaseConsts;
|
||||||
import io.github.xxyopen.novel.core.constant.EsConsts;
|
import io.github.xxyopen.novel.core.constant.EsConsts;
|
||||||
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||||
import io.github.xxyopen.novel.dto.es.EsBookDto;
|
import io.github.xxyopen.novel.dto.es.EsBookDto;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说数据同步到 elasticsearch 任务
|
* 小说数据同步到 elasticsearch 任务
|
||||||
*
|
*
|
||||||
@ -40,9 +40,10 @@ public class BookToEsTask {
|
|||||||
* 每月凌晨做一次全量数据同步
|
* 每月凌晨做一次全量数据同步
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Scheduled(cron = "0 0 0 1 * ?")
|
@XxlJob("saveToEsJobHandler")
|
||||||
public void saveToEs() {
|
public ReturnT<String> saveToEs() {
|
||||||
|
|
||||||
|
try {
|
||||||
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
||||||
List<BookInfo> bookInfos;
|
List<BookInfo> bookInfos;
|
||||||
long maxId = 0;
|
long maxId = 0;
|
||||||
@ -51,6 +52,7 @@ public class BookToEsTask {
|
|||||||
queryWrapper
|
queryWrapper
|
||||||
.orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
|
.orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
|
||||||
.gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId)
|
.gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId)
|
||||||
|
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||||
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
||||||
bookInfos = bookInfoMapper.selectList(queryWrapper);
|
bookInfos = bookInfoMapper.selectList(queryWrapper);
|
||||||
if (bookInfos.isEmpty()) {
|
if (bookInfos.isEmpty()) {
|
||||||
@ -80,9 +82,12 @@ public class BookToEsTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
return ReturnT.SUCCESS;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return ReturnT.FAIL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,13 @@ import io.jsonwebtoken.Jws;
|
|||||||
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.JwtException;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT 工具类
|
* JWT 工具类
|
||||||
*
|
*
|
||||||
@ -37,6 +36,7 @@ public class JwtUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户ID生成JWT
|
* 根据用户ID生成JWT
|
||||||
|
*
|
||||||
* @param uid 用户ID
|
* @param uid 用户ID
|
||||||
* @param systemKey 系统标识
|
* @param systemKey 系统标识
|
||||||
* @return JWT
|
* @return JWT
|
||||||
@ -51,6 +51,7 @@ public class JwtUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析JWT返回用户ID
|
* 解析JWT返回用户ID
|
||||||
|
*
|
||||||
* @param token JWT
|
* @param token JWT
|
||||||
* @param systemKey 系统标识
|
* @param systemKey 系统标识
|
||||||
* @return 用户ID
|
* @return 用户ID
|
||||||
|
@ -2,7 +2,6 @@ package io.github.xxyopen.novel.core.wrapper;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -34,7 +33,8 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
escapeValues[i] = values[i];
|
escapeValues[i] = values[i];
|
||||||
int index = i;
|
int index = i;
|
||||||
REPLACE_RULE.forEach((k, v)-> escapeValues[index] = escapeValues[index].replaceAll(k, v));
|
REPLACE_RULE.forEach(
|
||||||
|
(k, v) -> escapeValues[index] = escapeValues[index].replaceAll(k, v));
|
||||||
}
|
}
|
||||||
return escapeValues;
|
return escapeValues;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.req;
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.*;
|
import jakarta.validation.constraints.*;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -12,17 +13,20 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class AuthorRegisterReqDto {
|
public class AuthorRegisterReqDto {
|
||||||
|
|
||||||
|
@Schema(hidden = true)
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 笔名
|
* 笔名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "笔名", required = true)
|
||||||
@NotBlank(message = "笔名不能为空!")
|
@NotBlank(message = "笔名不能为空!")
|
||||||
private String penName;
|
private String penName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手机号码
|
* 手机号码
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "手机号码", required = true)
|
||||||
@NotBlank(message = "手机号不能为空!")
|
@NotBlank(message = "手机号不能为空!")
|
||||||
@Pattern(regexp = "^1[3|4|5|6|7|8|9][0-9]{9}$", message = "手机号格式不正确!")
|
@Pattern(regexp = "^1[3|4|5|6|7|8|9][0-9]{9}$", message = "手机号格式不正确!")
|
||||||
private String telPhone;
|
private String telPhone;
|
||||||
@ -30,12 +34,14 @@ public class AuthorRegisterReqDto {
|
|||||||
/**
|
/**
|
||||||
* QQ或微信账号
|
* QQ或微信账号
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "QQ或微信账号", required = true)
|
||||||
@NotBlank(message = "QQ或微信账号不能为空!")
|
@NotBlank(message = "QQ或微信账号不能为空!")
|
||||||
private String chatAccount;
|
private String chatAccount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 电子邮箱
|
* 电子邮箱
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "电子邮箱", required = true)
|
||||||
@NotBlank(message = "电子邮箱不能为空!")
|
@NotBlank(message = "电子邮箱不能为空!")
|
||||||
@Email(message = "邮箱格式不正确!")
|
@Email(message = "邮箱格式不正确!")
|
||||||
private String email;
|
private String email;
|
||||||
@ -43,6 +49,7 @@ public class AuthorRegisterReqDto {
|
|||||||
/**
|
/**
|
||||||
* 作品方向;0-男频 1-女频
|
* 作品方向;0-男频 1-女频
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "作品方向;0-男频 1-女频", required = true)
|
||||||
@NotNull(message = "作品方向不能为空!")
|
@NotNull(message = "作品方向不能为空!")
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@Max(1)
|
@Max(1)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.req;
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -16,42 +17,49 @@ public class BookAddReqDto {
|
|||||||
/**
|
/**
|
||||||
* 作品方向;0-男频 1-女频
|
* 作品方向;0-男频 1-女频
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "作品方向;0-男频 1-女频", required = true)
|
||||||
@NotNull
|
@NotNull
|
||||||
private Integer workDirection;
|
private Integer workDirection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别ID
|
* 类别ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别ID", required = true)
|
||||||
@NotNull
|
@NotNull
|
||||||
private Long categoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别名
|
* 类别名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别名", required = true)
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String categoryName;
|
private String categoryName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说封面地址
|
* 小说封面地址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说封面地址", required = true)
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String picUrl;
|
private String picUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说名
|
* 小说名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说名", required = true)
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String bookName;
|
private String bookName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 书籍描述
|
* 书籍描述
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "书籍描述", required = true)
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String bookDesc;
|
private String bookDesc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否收费;1-收费 0-免费
|
* 是否收费;1-收费 0-免费
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "是否收费;1-收费 0-免费", required = true)
|
||||||
@NotNull
|
@NotNull
|
||||||
private Integer isVip;
|
private Integer isVip;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package io.github.xxyopen.novel.dto.req;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import io.github.xxyopen.novel.core.common.req.PageReqDto;
|
import io.github.xxyopen.novel.core.common.req.PageReqDto;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
@ -19,36 +20,43 @@ public class BookSearchReqDto extends PageReqDto {
|
|||||||
/**
|
/**
|
||||||
* 搜索关键字
|
* 搜索关键字
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "搜索关键字")
|
||||||
private String keyword;
|
private String keyword;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作品方向
|
* 作品方向
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "作品方向")
|
||||||
private Integer workDirection;
|
private Integer workDirection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分类ID
|
* 分类ID
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "分类ID")
|
||||||
private Integer categoryId;
|
private Integer categoryId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否收费,1:收费,0:免费
|
* 是否收费,1:收费,0:免费
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "是否收费,1:收费,0:免费")
|
||||||
private Integer isVip;
|
private Integer isVip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说更新状态,0:连载中,1:已完结
|
* 小说更新状态,0:连载中,1:已完结
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "小说更新状态,0:连载中,1:已完结")
|
||||||
private Integer bookStatus;
|
private Integer bookStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字数最小值
|
* 字数最小值
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "字数最小值")
|
||||||
private Integer wordCountMin;
|
private Integer wordCountMin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字数最大值
|
* 字数最大值
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "字数最大值")
|
||||||
private Integer wordCountMax;
|
private Integer wordCountMax;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,6 +65,7 @@ public class BookSearchReqDto extends PageReqDto {
|
|||||||
* 如果使用Post请求,@RequestBody接收请求体参数,默认解析日期格式为yyyy-MM-dd HH:mm:ss ,
|
* 如果使用Post请求,@RequestBody接收请求体参数,默认解析日期格式为yyyy-MM-dd HH:mm:ss ,
|
||||||
* 如果需要接收其他格式的参数,则可以使用@JsonFormat注解
|
* 如果需要接收其他格式的参数,则可以使用@JsonFormat注解
|
||||||
* */
|
* */
|
||||||
|
@Parameter(description = "最小更新时间")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private Date updateTimeMin;
|
private Date updateTimeMin;
|
||||||
@ -64,5 +73,6 @@ public class BookSearchReqDto extends PageReqDto {
|
|||||||
/**
|
/**
|
||||||
* 排序字段
|
* 排序字段
|
||||||
*/
|
*/
|
||||||
|
@Parameter(description = "排序字段")
|
||||||
private String sort;
|
private String sort;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.req;
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -17,18 +18,20 @@ public class ChapterAddReqDto {
|
|||||||
/**
|
/**
|
||||||
* 小说ID
|
* 小说ID
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@Schema(description = "小说ID", required = true)
|
||||||
private Long bookId;
|
private Long bookId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节名
|
* 章节名
|
||||||
*/
|
*/
|
||||||
@NotBlank
|
@NotBlank
|
||||||
|
@Schema(description = "章节名", required = true)
|
||||||
private String chapterName;
|
private String chapterName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节内容
|
* 章节内容
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节内容", required = true)
|
||||||
@NotBlank
|
@NotBlank
|
||||||
@Length(min = 50)
|
@Length(min = 50)
|
||||||
private String chapterContent;
|
private String chapterContent;
|
||||||
@ -36,6 +39,7 @@ public class ChapterAddReqDto {
|
|||||||
/**
|
/**
|
||||||
* 是否收费;1-收费 0-免费
|
* 是否收费;1-收费 0-免费
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "是否收费;1-收费 0-免费", required = true)
|
||||||
@NotNull
|
@NotNull
|
||||||
private Integer isVip;
|
private Integer isVip;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.req;
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -15,9 +16,11 @@ public class UserCommentReqDto {
|
|||||||
|
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "小说ID", required = true)
|
||||||
@NotNull(message="小说ID不能为空!")
|
@NotNull(message="小说ID不能为空!")
|
||||||
private Long bookId;
|
private Long bookId;
|
||||||
|
|
||||||
|
@Schema(description = "评论内容", required = true)
|
||||||
@NotBlank(message="评论不能为空!")
|
@NotBlank(message="评论不能为空!")
|
||||||
@Length(min = 10,max = 512)
|
@Length(min = 10,max = 512)
|
||||||
private String commentContent;
|
private String commentContent;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.req;
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.Max;
|
import jakarta.validation.constraints.Max;
|
||||||
import jakarta.validation.constraints.Min;
|
import jakarta.validation.constraints.Min;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
@ -16,12 +17,15 @@ public class UserInfoUptReqDto {
|
|||||||
|
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "昵称")
|
||||||
@Length(min = 2,max = 10)
|
@Length(min = 2,max = 10)
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
|
@Schema(description = "头像地址")
|
||||||
@Pattern(regexp="^/[^\s]{10,}\\.(png|PNG|jpg|JPG|jpeg|JPEG|gif|GIF|bpm|BPM)$")
|
@Pattern(regexp="^/[^\s]{10,}\\.(png|PNG|jpg|JPG|jpeg|JPEG|gif|GIF|bpm|BPM)$")
|
||||||
private String userPhoto;
|
private String userPhoto;
|
||||||
|
|
||||||
|
@Schema(description = "性别")
|
||||||
@Min(value = 0)
|
@Min(value = 0)
|
||||||
@Max(value = 1)
|
@Max(value = 1)
|
||||||
private Integer userSex;
|
private Integer userSex;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.req;
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -13,10 +14,12 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class UserLoginReqDto {
|
public class UserLoginReqDto {
|
||||||
|
|
||||||
|
@Schema(description = "手机号", required = true, example = "18888888888")
|
||||||
@NotBlank(message = "手机号不能为空!")
|
@NotBlank(message = "手机号不能为空!")
|
||||||
@Pattern(regexp = "^1[3|4|5|6|7|8|9][0-9]{9}$", message = "手机号格式不正确!")
|
@Pattern(regexp = "^1[3|4|5|6|7|8|9][0-9]{9}$", message = "手机号格式不正确!")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "密码", required = true, example = "123456")
|
||||||
@NotBlank(message = "密码不能为空!")
|
@NotBlank(message = "密码不能为空!")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.req;
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -14,13 +15,16 @@ import org.hibernate.validator.constraints.Length;
|
|||||||
@Data
|
@Data
|
||||||
public class UserRegisterReqDto {
|
public class UserRegisterReqDto {
|
||||||
|
|
||||||
|
@Schema(description = "手机号", required = true)
|
||||||
@NotBlank(message="手机号不能为空!")
|
@NotBlank(message="手机号不能为空!")
|
||||||
@Pattern(regexp="^1[3|4|5|6|7|8|9][0-9]{9}$",message="手机号格式不正确!")
|
@Pattern(regexp="^1[3|4|5|6|7|8|9][0-9]{9}$",message="手机号格式不正确!")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "密码", required = true)
|
||||||
@NotBlank(message="密码不能为空!")
|
@NotBlank(message="密码不能为空!")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@Schema(description = "验证码", required = true)
|
||||||
@NotBlank(message="验证码不能为空!")
|
@NotBlank(message="验证码不能为空!")
|
||||||
@Pattern(regexp="^\\d{4}$",message="验证码格式不正确!")
|
@Pattern(regexp="^\\d{4}$",message="验证码格式不正确!")
|
||||||
private String velCode;
|
private String velCode;
|
||||||
@ -28,6 +32,7 @@ public class UserRegisterReqDto {
|
|||||||
/**
|
/**
|
||||||
* 请求会话标识,用来标识图形验证码属于哪个会话
|
* 请求会话标识,用来标识图形验证码属于哪个会话
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "sessionId", required = true)
|
||||||
@NotBlank
|
@NotBlank
|
||||||
@Length(min = 32,max = 32)
|
@Length(min = 32,max = 32)
|
||||||
private String sessionId;
|
private String sessionId;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -16,11 +17,13 @@ public class BookCategoryRespDto {
|
|||||||
/**
|
/**
|
||||||
* 类别ID
|
* 类别ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别名
|
* 类别名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别名")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -18,11 +19,13 @@ public class BookChapterAboutRespDto {
|
|||||||
/**
|
/**
|
||||||
* 章节总数
|
* 章节总数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节总数")
|
||||||
private Long chapterTotal;
|
private Long chapterTotal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内容概要(30字)
|
* 内容概要(30字)
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = " 内容概要(30字)")
|
||||||
private String contentSummary;
|
private String contentSummary;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -24,32 +25,44 @@ public class BookChapterRespDto implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 章节ID
|
* 章节ID
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "章节ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说ID
|
* 小说ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说ID")
|
||||||
private Long bookId;
|
private Long bookId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节号
|
* 章节号
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节号")
|
||||||
private Integer chapterNum;
|
private Integer chapterNum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节名
|
* 章节名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节名")
|
||||||
private String chapterName;
|
private String chapterName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节字数
|
* 章节字数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节字数")
|
||||||
private Integer chapterWordCount;
|
private Integer chapterWordCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节更新时间
|
* 章节更新时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节更新时间")
|
||||||
@JsonFormat(pattern = "yyyy/MM/dd HH:dd")
|
@JsonFormat(pattern = "yyyy/MM/dd HH:dd")
|
||||||
private LocalDateTime chapterUpdateTime;
|
private LocalDateTime chapterUpdateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
@Schema(description = "是否收费;1-收费 0-免费")
|
||||||
|
private Integer isVip;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package io.github.xxyopen.novel.dto.resp;
|
|||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import io.github.xxyopen.novel.core.json.serializer.UsernameSerializer;
|
import io.github.xxyopen.novel.core.json.serializer.UsernameSerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -18,25 +19,33 @@ import java.util.List;
|
|||||||
@Builder
|
@Builder
|
||||||
public class BookCommentRespDto {
|
public class BookCommentRespDto {
|
||||||
|
|
||||||
|
@Schema(description = "评论总数")
|
||||||
private Long commentTotal;
|
private Long commentTotal;
|
||||||
|
|
||||||
|
@Schema(description = "评论列表")
|
||||||
private List<CommentInfo> comments;
|
private List<CommentInfo> comments;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
public static class CommentInfo {
|
public static class CommentInfo {
|
||||||
|
|
||||||
|
@Schema(description = "评论ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "评论内容")
|
||||||
private String commentContent;
|
private String commentContent;
|
||||||
|
|
||||||
|
@Schema(description = "评论用户")
|
||||||
@JsonSerialize(using = UsernameSerializer.class)
|
@JsonSerialize(using = UsernameSerializer.class)
|
||||||
private String commentUser;
|
private String commentUser;
|
||||||
|
|
||||||
|
@Schema(description = "评论用户ID")
|
||||||
private Long commentUserId;
|
private Long commentUserId;
|
||||||
|
|
||||||
|
@Schema(description = "评论用户头像")
|
||||||
private String commentUserPhoto;
|
private String commentUserPhoto;
|
||||||
|
|
||||||
|
@Schema(description = "评论时间")
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private LocalDateTime commentTime;
|
private LocalDateTime commentTime;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -16,16 +17,19 @@ public class BookContentAboutRespDto {
|
|||||||
/**
|
/**
|
||||||
* 小说信息
|
* 小说信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说信息")
|
||||||
private BookInfoRespDto bookInfo;
|
private BookInfoRespDto bookInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节信息
|
* 章节信息
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节信息")
|
||||||
private BookChapterRespDto chapterInfo;
|
private BookChapterRespDto chapterInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节内容
|
* 章节内容
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "章节内容")
|
||||||
private String bookContent;
|
private String bookContent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息 响应DTO
|
* 小说信息 响应DTO
|
||||||
*
|
*
|
||||||
@ -17,77 +21,99 @@ public class BookInfoRespDto {
|
|||||||
/**
|
/**
|
||||||
* ID
|
* ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别ID
|
* 类别ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别ID")
|
||||||
private Long categoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别名
|
* 类别名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别名")
|
||||||
private String categoryName;
|
private String categoryName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说封面地址
|
* 小说封面地址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说封面地址")
|
||||||
private String picUrl;
|
private String picUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说名
|
* 小说名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说名")
|
||||||
private String bookName;
|
private String bookName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家id
|
* 作家id
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "作家id")
|
||||||
private Long authorId;
|
private Long authorId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家名
|
* 作家名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "作家名")
|
||||||
private String authorName;
|
private String authorName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 书籍描述
|
* 书籍描述
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "书籍描述")
|
||||||
private String bookDesc;
|
private String bookDesc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 书籍状态;0-连载中 1-已完结
|
* 书籍状态;0-连载中 1-已完结
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "书籍状态;0-连载中 1-已完结")
|
||||||
private Integer bookStatus;
|
private Integer bookStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点击量
|
* 点击量
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "点击量")
|
||||||
private Long visitCount;
|
private Long visitCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 总字数
|
* 总字数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "总字数")
|
||||||
private Integer wordCount;
|
private Integer wordCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 评论数
|
* 评论数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "评论数")
|
||||||
private Integer commentCount;
|
private Integer commentCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首章节ID
|
* 首章节ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "首章节ID")
|
||||||
private Long firstChapterId;
|
private Long firstChapterId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最新章节ID
|
* 最新章节ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "最新章节ID")
|
||||||
private Long lastChapterId;
|
private Long lastChapterId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最新章节名
|
* 最新章节名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "最新章节名")
|
||||||
private String lastChapterName;
|
private String lastChapterName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最新章节更新时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "最新章节更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@ -22,51 +23,61 @@ public class BookRankRespDto implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* ID
|
* ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别ID
|
* 类别ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别ID")
|
||||||
private Long categoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别名
|
* 类别名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别名")
|
||||||
private String categoryName;
|
private String categoryName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说封面地址
|
* 小说封面地址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说封面地址")
|
||||||
private String picUrl;
|
private String picUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说名
|
* 小说名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说名")
|
||||||
private String bookName;
|
private String bookName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家名
|
* 作家名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "作家名")
|
||||||
private String authorName;
|
private String authorName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 书籍描述
|
* 书籍描述
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "书籍描述")
|
||||||
private String bookDesc;
|
private String bookDesc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 总字数
|
* 总字数
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "总字数")
|
||||||
private Integer wordCount;
|
private Integer wordCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最新章节名
|
* 最新章节名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "最新章节名")
|
||||||
private String lastChapterName;
|
private String lastChapterName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最新章节更新时间
|
* 最新章节更新时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "最新章节更新时间")
|
||||||
@JsonFormat(pattern = "MM/dd HH:mm")
|
@JsonFormat(pattern = "MM/dd HH:mm")
|
||||||
private LocalDateTime lastChapterUpdateTime;
|
private LocalDateTime lastChapterUpdateTime;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,31 +15,37 @@ public class HomeBookRespDto {
|
|||||||
/**
|
/**
|
||||||
* 类型;0-轮播图 1-顶部栏 2-本周强推 3-热门推荐 4-精品推荐
|
* 类型;0-轮播图 1-顶部栏 2-本周强推 3-热门推荐 4-精品推荐
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类型;0-轮播图 1-顶部栏 2-本周强推 3-热门推荐 4-精品推荐")
|
||||||
private Integer type;
|
private Integer type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推荐小说ID
|
* 推荐小说ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说ID")
|
||||||
private Long bookId;
|
private Long bookId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说封面地址
|
* 小说封面地址
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说封面地址")
|
||||||
private String picUrl;
|
private String picUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说名
|
* 小说名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "小说名")
|
||||||
private String bookName;
|
private String bookName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家名
|
* 作家名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "作家名")
|
||||||
private String authorName;
|
private String authorName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 书籍描述
|
* 书籍描述
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "书籍描述")
|
||||||
private String bookDesc;
|
private String bookDesc;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@ -20,10 +21,12 @@ public class HomeFriendLinkRespDto implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 链接名
|
* 链接名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "链接名")
|
||||||
private String linkName;
|
private String linkName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 链接url
|
* 链接url
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "链接url")
|
||||||
private String linkUrl;
|
private String linkUrl;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -15,11 +16,13 @@ public class ImgVerifyCodeRespDto {
|
|||||||
/**
|
/**
|
||||||
* 当前会话ID,用于标识改图形验证码属于哪个会话
|
* 当前会话ID,用于标识改图形验证码属于哪个会话
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "sessionId")
|
||||||
private String sessionId;
|
private String sessionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base64 编码的验证码图片
|
* Base64 编码的验证码图片
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "Base64 编码的验证码图片")
|
||||||
private String img;
|
private String img;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -19,37 +20,44 @@ public class NewsInfoRespDto {
|
|||||||
/**
|
/**
|
||||||
* ID
|
* ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "新闻ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别ID
|
* 类别ID
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别ID")
|
||||||
private Long categoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类别名
|
* 类别名
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "类别名")
|
||||||
private String categoryName;
|
private String categoryName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新闻来源
|
* 新闻来源
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "新闻来源")
|
||||||
private String sourceName;
|
private String sourceName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新闻标题
|
* 新闻标题
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "新闻标题")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新时间
|
* 更新时间
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "更新时间")
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDateTime updateTime;
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新闻内容
|
* 新闻内容
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "新闻内容")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -16,15 +17,18 @@ public class UserInfoRespDto {
|
|||||||
/**
|
/**
|
||||||
* 昵称
|
* 昵称
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "昵称")
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户头像
|
* 用户头像
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "用户头像")
|
||||||
private String userPhoto;
|
private String userPhoto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户性别
|
* 用户性别
|
||||||
* */
|
* */
|
||||||
|
@Schema(description = "用户性别")
|
||||||
private Integer userSex;
|
private Integer userSex;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -12,9 +13,12 @@ import lombok.Data;
|
|||||||
@Builder
|
@Builder
|
||||||
public class UserLoginRespDto {
|
public class UserLoginRespDto {
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
private Long uid;
|
private Long uid;
|
||||||
|
|
||||||
|
@Schema(description = "用户昵称")
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
|
@Schema(description = "用户token")
|
||||||
private String token;
|
private String token;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.dto.resp;
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -12,7 +13,9 @@ import lombok.Data;
|
|||||||
@Builder
|
@Builder
|
||||||
public class UserRegisterRespDto {
|
public class UserRegisterRespDto {
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
private Long uid;
|
private Long uid;
|
||||||
|
|
||||||
|
@Schema(description = "用户token")
|
||||||
private String token;
|
private String token;
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,12 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
|||||||
import io.github.xxyopen.novel.dao.entity.AuthorInfo;
|
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 java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.cache.annotation.CacheEvict;
|
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;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家信息 缓存管理类
|
* 作家信息 缓存管理类
|
||||||
*
|
*
|
||||||
@ -28,8 +27,8 @@ public class AuthorInfoCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 查询作家信息,并放入缓存中
|
* 查询作家信息,并放入缓存中
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER
|
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
, value = CacheConsts.AUTHOR_INFO_CACHE_NAME, unless = "#result == null")
|
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
|
||||||
@ -45,8 +44,8 @@ public class AuthorInfoCacheManager {
|
|||||||
.status(authorInfo.getStatus()).build();
|
.status(authorInfo.getStatus()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(cacheManager = CacheConsts.REDIS_CACHE_MANAGER
|
@CacheEvict(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
, value = CacheConsts.AUTHOR_INFO_CACHE_NAME)
|
value = CacheConsts.AUTHOR_INFO_CACHE_NAME)
|
||||||
public void evictAuthorCache() {
|
public void evictAuthorCache() {
|
||||||
// 调用此方法自动清除作家信息的缓存
|
// 调用此方法自动清除作家信息的缓存
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
|||||||
import io.github.xxyopen.novel.dao.entity.BookCategory;
|
import io.github.xxyopen.novel.dao.entity.BookCategory;
|
||||||
import io.github.xxyopen.novel.dao.mapper.BookCategoryMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookCategoryMapper;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookCategoryRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookCategoryRespDto;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说分类 缓存管理类
|
* 小说分类 缓存管理类
|
||||||
*
|
*
|
||||||
@ -27,8 +26,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);
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -8,13 +8,13 @@ import io.github.xxyopen.novel.dao.entity.BookInfo;
|
|||||||
import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
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 java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
import org.springframework.cache.annotation.CachePut;
|
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;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息 缓存管理类
|
* 小说信息 缓存管理类
|
||||||
*
|
*
|
||||||
@ -32,17 +32,17 @@ public class BookInfoCacheManager {
|
|||||||
/**
|
/**
|
||||||
* 从缓存中查询小说信息(先判断缓存中是否已存在,存在则直接从缓存中取,否则执行方法体中的逻辑后缓存结果)
|
* 从缓存中查询小说信息(先判断缓存中是否已存在,存在则直接从缓存中取,否则执行方法体中的逻辑后缓存结果)
|
||||||
*/
|
*/
|
||||||
@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);
|
return cachePutBookInfo(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缓存小说信息(不管缓存中是否存在都执行方法体中的逻辑,然后缓存起来)
|
* 缓存小说信息(不管缓存中是否存在都执行方法体中的逻辑,然后缓存起来)
|
||||||
* */
|
*/
|
||||||
@CachePut(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER
|
@CachePut(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
, value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||||
public BookInfoRespDto cachePutBookInfo(Long id) {
|
public BookInfoRespDto cachePutBookInfo(Long id) {
|
||||||
// 查询基础信息
|
// 查询基础信息
|
||||||
BookInfo bookInfo = bookInfoMapper.selectById(id);
|
BookInfo bookInfo = bookInfoMapper.selectById(id);
|
||||||
@ -72,16 +72,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();
|
||||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
|||||||
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookRankRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookRankRespDto;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说排行榜 缓存管理类
|
* 小说排行榜 缓存管理类
|
||||||
*
|
*
|
||||||
@ -27,40 +26,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());
|
||||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
|||||||
import io.github.xxyopen.novel.dao.entity.HomeFriendLink;
|
import io.github.xxyopen.novel.dao.entity.HomeFriendLink;
|
||||||
import io.github.xxyopen.novel.dao.mapper.HomeFriendLinkMapper;
|
import io.github.xxyopen.novel.dao.mapper.HomeFriendLinkMapper;
|
||||||
import io.github.xxyopen.novel.dto.resp.HomeFriendLinkRespDto;
|
import io.github.xxyopen.novel.dto.resp.HomeFriendLinkRespDto;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 友情链接 缓存管理类
|
* 友情链接 缓存管理类
|
||||||
*
|
*
|
||||||
@ -27,8 +26,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<>();
|
||||||
|
@ -8,16 +8,15 @@ import io.github.xxyopen.novel.dao.entity.HomeBook;
|
|||||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||||
import io.github.xxyopen.novel.dao.mapper.HomeBookMapper;
|
import io.github.xxyopen.novel.dao.mapper.HomeBookMapper;
|
||||||
import io.github.xxyopen.novel.dto.resp.HomeBookRespDto;
|
import io.github.xxyopen.novel.dto.resp.HomeBookRespDto;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页推荐小说 缓存管理类
|
* 首页推荐小说 缓存管理类
|
||||||
@ -36,8 +35,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<>();
|
||||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
|||||||
import io.github.xxyopen.novel.dao.entity.NewsInfo;
|
import io.github.xxyopen.novel.dao.entity.NewsInfo;
|
||||||
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 java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新闻 缓存管理类
|
* 新闻 缓存管理类
|
||||||
*
|
*
|
||||||
@ -27,8 +26,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<>();
|
||||||
|
@ -4,12 +4,11 @@ import io.github.xxyopen.novel.core.constant.CacheConsts;
|
|||||||
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
||||||
import io.github.xxyopen.novel.dao.mapper.UserInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.UserInfoMapper;
|
||||||
import io.github.xxyopen.novel.dto.UserInfoDto;
|
import io.github.xxyopen.novel.dto.UserInfoDto;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户信息 缓存管理类
|
* 用户信息 缓存管理类
|
||||||
*
|
*
|
||||||
@ -25,8 +24,8 @@ 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)) {
|
||||||
|
@ -4,13 +4,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||||||
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||||
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
||||||
import io.github.xxyopen.novel.dao.mapper.UserInfoMapper;
|
import io.github.xxyopen.novel.dao.mapper.UserInfoMapper;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户模块 DAO管理类
|
* 用户模块 DAO管理类
|
||||||
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/20
|
* @date 2022/5/20
|
||||||
*/
|
*/
|
||||||
@ -22,9 +22,10 @@ public class UserDaoManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户ID集合批量查询用户信息列表
|
* 根据用户ID集合批量查询用户信息列表
|
||||||
|
*
|
||||||
* @param userIds 需要查询的用户ID集合
|
* @param userIds 需要查询的用户ID集合
|
||||||
* @return 满足条件的用户信息列表
|
* @return 满足条件的用户信息列表
|
||||||
* */
|
*/
|
||||||
public List<UserInfo> listUsers(List<Long> userIds) {
|
public List<UserInfo> listUsers(List<Long> userIds) {
|
||||||
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(), userIds);
|
queryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(), userIds);
|
||||||
|
@ -2,6 +2,7 @@ package io.github.xxyopen.novel.manager.mq;
|
|||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.constant.CommonConsts;
|
import io.github.xxyopen.novel.core.common.constant.CommonConsts;
|
||||||
import io.github.xxyopen.novel.core.constant.AmqpConsts;
|
import io.github.xxyopen.novel.core.constant.AmqpConsts;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.amqp.core.AmqpTemplate;
|
import org.springframework.amqp.core.AmqpTemplate;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@ -9,8 +10,6 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AMQP 消息管理类
|
* AMQP 消息管理类
|
||||||
*
|
*
|
||||||
@ -35,10 +34,12 @@ public class AmqpMsgManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAmqpMessage(AmqpTemplate amqpTemplate, String exchange, String routingKey, Object message) {
|
private void sendAmqpMessage(AmqpTemplate amqpTemplate, String exchange, String routingKey,
|
||||||
|
Object message) {
|
||||||
// 如果在事务中则在事务执行完成后再发送,否则可以直接发送
|
// 如果在事务中则在事务执行完成后再发送,否则可以直接发送
|
||||||
if (TransactionSynchronizationManager.isActualTransactionActive()) {
|
if (TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
TransactionSynchronizationManager.registerSynchronization(
|
||||||
|
new TransactionSynchronization() {
|
||||||
@Override
|
@Override
|
||||||
public void afterCommit() {
|
public void afterCommit() {
|
||||||
amqpTemplate.convertAndSend(exchange, routingKey, message);
|
amqpTemplate.convertAndSend(exchange, routingKey, message);
|
||||||
|
@ -2,15 +2,14 @@ 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;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证码 管理类
|
* 验证码 管理类
|
||||||
*
|
*
|
||||||
@ -25,27 +24,26 @@ 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()
|
||||||
stringRedisTemplate.opsForValue().get(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId)
|
.get(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId), verifyCode);
|
||||||
, 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);
|
||||||
|
@ -18,4 +18,12 @@ public interface AuthorService {
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
RestResp<Void> register(AuthorRegisterReqDto dto);
|
RestResp<Void> register(AuthorRegisterReqDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询作家状态
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 作家状态
|
||||||
|
*/
|
||||||
|
RestResp<Integer> getStatus(Long userId);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
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.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
||||||
@ -128,32 +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 userId 用户ID
|
||||||
* @param id 评论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 小说信息
|
* @param dto 小说信息
|
||||||
* @return void
|
* @return void
|
||||||
* */
|
*/
|
||||||
RestResp<Void> saveBook(BookAddReqDto dto);
|
RestResp<Void> saveBook(BookAddReqDto dto);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说章节信息保存
|
* 小说章节信息保存
|
||||||
|
*
|
||||||
* @param dto 章节信息
|
* @param dto 章节信息
|
||||||
* @return void
|
* @return void
|
||||||
* */
|
*/
|
||||||
RestResp<Void> saveBookChapter(ChapterAddReqDto dto);
|
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);
|
||||||
}
|
}
|
||||||
|
@ -54,4 +54,10 @@ public class AuthorServiceImpl implements AuthorService {
|
|||||||
return RestResp.ok();
|
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,11 +1,21 @@
|
|||||||
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 io.github.xxyopen.novel.core.annotation.Key;
|
||||||
|
import io.github.xxyopen.novel.core.annotation.Lock;
|
||||||
import io.github.xxyopen.novel.core.auth.UserHolder;
|
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.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.*;
|
import io.github.xxyopen.novel.dao.entity.BookChapter;
|
||||||
|
import io.github.xxyopen.novel.dao.entity.BookComment;
|
||||||
|
import io.github.xxyopen.novel.dao.entity.BookContent;
|
||||||
|
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.BookContentMapper;
|
||||||
@ -14,23 +24,39 @@ import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
|||||||
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
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.BookCategoryRespDto;
|
||||||
import io.github.xxyopen.novel.manager.cache.*;
|
import io.github.xxyopen.novel.dto.resp.BookChapterAboutRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookCommentRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookContentAboutRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.BookRankRespDto;
|
||||||
|
import io.github.xxyopen.novel.manager.cache.AuthorInfoCacheManager;
|
||||||
|
import io.github.xxyopen.novel.manager.cache.BookCategoryCacheManager;
|
||||||
|
import io.github.xxyopen.novel.manager.cache.BookChapterCacheManager;
|
||||||
|
import io.github.xxyopen.novel.manager.cache.BookContentCacheManager;
|
||||||
|
import io.github.xxyopen.novel.manager.cache.BookInfoCacheManager;
|
||||||
|
import io.github.xxyopen.novel.manager.cache.BookRankCacheManager;
|
||||||
import io.github.xxyopen.novel.manager.dao.UserDaoManager;
|
import io.github.xxyopen.novel.manager.dao.UserDaoManager;
|
||||||
import io.github.xxyopen.novel.manager.mq.AmqpMsgManager;
|
import io.github.xxyopen.novel.manager.mq.AmqpMsgManager;
|
||||||
import io.github.xxyopen.novel.service.BookService;
|
import io.github.xxyopen.novel.service.BookService;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
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 org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说模块 服务实现类
|
* 小说模块 服务实现类
|
||||||
*
|
*
|
||||||
@ -94,7 +120,8 @@ public class BookServiceImpl implements BookService {
|
|||||||
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookId);
|
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookId);
|
||||||
|
|
||||||
// 查询最新章节信息
|
// 查询最新章节信息
|
||||||
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(bookInfo.getLastChapterId());
|
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(
|
||||||
|
bookInfo.getLastChapterId());
|
||||||
|
|
||||||
// 查询章节内容
|
// 查询章节内容
|
||||||
String content = bookContentCacheManager.getBookContent(bookInfo.getLastChapterId());
|
String content = bookContentCacheManager.getBookContent(bookInfo.getLastChapterId());
|
||||||
@ -113,7 +140,8 @@ public class BookServiceImpl implements BookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId) throws NoSuchAlgorithmException {
|
public RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
Long categoryId = bookInfoCacheManager.getBookInfo(bookId).getCategoryId();
|
Long categoryId = bookInfoCacheManager.getBookInfo(bookId).getCategoryId();
|
||||||
List<Long> lastUpdateIdList = bookInfoCacheManager.getLastUpdateIdList(categoryId);
|
List<Long> lastUpdateIdList = bookInfoCacheManager.getLastUpdateIdList(categoryId);
|
||||||
List<BookInfoRespDto> respDtoList = new ArrayList<>();
|
List<BookInfoRespDto> respDtoList = new ArrayList<>();
|
||||||
@ -184,9 +212,11 @@ public class BookServiceImpl implements BookService {
|
|||||||
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
|
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
|
||||||
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
|
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
|
||||||
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,8 +225,10 @@ public class BookServiceImpl implements BookService {
|
|||||||
return RestResp.ok(bookCategoryCacheManager.listCategory(workDirection));
|
return RestResp.ok(bookCategoryCacheManager.listCategory(workDirection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Lock(prefix = "userComment")
|
||||||
@Override
|
@Override
|
||||||
public RestResp<Void> saveComment(UserCommentReqDto dto) {
|
public RestResp<Void> saveComment(
|
||||||
|
@Key(expr = "#{userId + '::' + bookId}") 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())
|
||||||
@ -221,7 +253,8 @@ public class BookServiceImpl implements BookService {
|
|||||||
QueryWrapper<BookComment> commentCountQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<BookComment> commentCountQueryWrapper = new QueryWrapper<>();
|
||||||
commentCountQueryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, bookId);
|
commentCountQueryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, bookId);
|
||||||
Long commentTotal = bookCommentMapper.selectCount(commentCountQueryWrapper);
|
Long commentTotal = bookCommentMapper.selectCount(commentCountQueryWrapper);
|
||||||
BookCommentRespDto bookCommentRespDto = BookCommentRespDto.builder().commentTotal(commentTotal).build();
|
BookCommentRespDto bookCommentRespDto = BookCommentRespDto.builder()
|
||||||
|
.commentTotal(commentTotal).build();
|
||||||
if (commentTotal > 0) {
|
if (commentTotal > 0) {
|
||||||
|
|
||||||
// 查询最新的评论列表
|
// 查询最新的评论列表
|
||||||
@ -234,7 +267,8 @@ 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, UserInfo> userInfoMap = userInfos.stream().collect(Collectors.toMap(UserInfo::getId, Function.identity()));
|
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())
|
||||||
@ -272,6 +306,12 @@ public class BookServiceImpl implements BookService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RestResp<Void> saveBook(BookAddReqDto dto) {
|
public RestResp<Void> saveBook(BookAddReqDto dto) {
|
||||||
|
// 校验小说名是否已存在
|
||||||
|
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq(DatabaseConsts.BookTable.COLUMN_BOOK_NAME, dto.getBookName());
|
||||||
|
if (bookInfoMapper.selectCount(queryWrapper) > 0) {
|
||||||
|
return RestResp.fail(ErrorCodeEnum.AUTHOR_BOOK_NAME_EXIST);
|
||||||
|
}
|
||||||
BookInfo bookInfo = new BookInfo();
|
BookInfo bookInfo = new BookInfo();
|
||||||
// 设置作家信息
|
// 设置作家信息
|
||||||
AuthorInfoDto author = authorInfoCacheManager.getAuthor(UserHolder.getUserId());
|
AuthorInfoDto author = authorInfoCacheManager.getAuthor(UserHolder.getUserId());
|
||||||
@ -296,6 +336,11 @@ public class BookServiceImpl implements BookService {
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@Override
|
@Override
|
||||||
public RestResp<Void> saveBookChapter(ChapterAddReqDto dto) {
|
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) 保存章节相关信息到小说章节表
|
// 1) 保存章节相关信息到小说章节表
|
||||||
// a) 查询最新章节号
|
// a) 查询最新章节号
|
||||||
int chapterNum = 0;
|
int chapterNum = 0;
|
||||||
@ -328,7 +373,6 @@ public class BookServiceImpl implements BookService {
|
|||||||
|
|
||||||
// 3) 更新小说表最新章节信息和小说总字数信息
|
// 3) 更新小说表最新章节信息和小说总字数信息
|
||||||
// a) 更新小说表关于最新章节的信息
|
// a) 更新小说表关于最新章节的信息
|
||||||
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(dto.getBookId());
|
|
||||||
BookInfo newBookInfo = new BookInfo();
|
BookInfo newBookInfo = new BookInfo();
|
||||||
newBookInfo.setId(dto.getBookId());
|
newBookInfo.setId(dto.getBookId());
|
||||||
newBookInfo.setLastChapterId(newBookChapter.getId());
|
newBookInfo.setLastChapterId(newBookChapter.getId());
|
||||||
@ -337,15 +381,55 @@ public class BookServiceImpl implements BookService {
|
|||||||
newBookInfo.setWordCount(bookInfo.getWordCount() + newBookChapter.getWordCount());
|
newBookInfo.setWordCount(bookInfo.getWordCount() + newBookChapter.getWordCount());
|
||||||
newBookChapter.setUpdateTime(LocalDateTime.now());
|
newBookChapter.setUpdateTime(LocalDateTime.now());
|
||||||
bookInfoMapper.updateById(newBookInfo);
|
bookInfoMapper.updateById(newBookInfo);
|
||||||
// b) 刷新小说信息缓存
|
// b) 清除小说信息缓存
|
||||||
bookInfoCacheManager.cachePutBookInfo(dto.getBookId());
|
bookInfoCacheManager.evictBookInfoCache(dto.getBookId());
|
||||||
// c) 发送小说信息更新的 MQ 消息
|
// c) 发送小说信息更新的 MQ 消息
|
||||||
amqpMsgManager.sendBookChangeMsg(dto.getBookId());
|
amqpMsgManager.sendBookChangeMsg(dto.getBookId());
|
||||||
return RestResp.ok();
|
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);
|
||||||
|
|
||||||
|
@ -8,13 +8,12 @@ import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
|||||||
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
import io.github.xxyopen.novel.service.SearchService;
|
import io.github.xxyopen.novel.service.SearchService;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据库搜索 服务实现类
|
* 数据库搜索 服务实现类
|
||||||
*
|
*
|
||||||
@ -35,8 +34,9 @@ public class DbSearchServiceImpl implements SearchService {
|
|||||||
page.setCurrent(condition.getPageNum());
|
page.setCurrent(condition.getPageNum());
|
||||||
page.setSize(condition.getPageSize());
|
page.setSize(condition.getPageSize());
|
||||||
List<BookInfo> bookInfos = bookInfoMapper.searchBooks(page, condition);
|
List<BookInfo> bookInfos = bookInfoMapper.searchBooks(page, condition);
|
||||||
return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal()
|
return RestResp.ok(
|
||||||
, bookInfos.stream().map(v -> BookInfoRespDto.builder()
|
PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal(),
|
||||||
|
bookInfos.stream().map(v -> BookInfoRespDto.builder()
|
||||||
.id(v.getId())
|
.id(v.getId())
|
||||||
.bookName(v.getBookName())
|
.bookName(v.getBookName())
|
||||||
.categoryId(v.getCategoryId())
|
.categoryId(v.getCategoryId())
|
||||||
|
@ -19,16 +19,15 @@ import io.github.xxyopen.novel.dto.es.EsBookDto;
|
|||||||
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
import io.github.xxyopen.novel.service.SearchService;
|
import io.github.xxyopen.novel.service.SearchService;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elasticsearch 搜索 服务实现类
|
* Elasticsearch 搜索 服务实现类
|
||||||
*
|
*
|
||||||
@ -54,9 +53,8 @@ public class EsSearchServiceImpl implements SearchService {
|
|||||||
buildSearchCondition(condition, searchBuilder);
|
buildSearchCondition(condition, searchBuilder);
|
||||||
// 排序
|
// 排序
|
||||||
if (!StringUtils.isBlank(condition.getSort())) {
|
if (!StringUtils.isBlank(condition.getSort())) {
|
||||||
searchBuilder.sort(o ->
|
searchBuilder.sort(o -> o.field(f -> f
|
||||||
o.field(f -> f.field(StringUtils
|
.field(StringUtils.underlineToCamel(condition.getSort().split(" ")[0]))
|
||||||
.underlineToCamel(condition.getSort().split(" ")[0]))
|
|
||||||
.order(SortOrder.Desc))
|
.order(SortOrder.Desc))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -64,10 +62,10 @@ public class EsSearchServiceImpl implements SearchService {
|
|||||||
searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize())
|
searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize())
|
||||||
.size(condition.getPageSize());
|
.size(condition.getPageSize());
|
||||||
// 设置高亮显示
|
// 设置高亮显示
|
||||||
searchBuilder.highlight(h -> h.fields(EsConsts.BookIndex.FIELD_BOOK_NAME
|
searchBuilder.highlight(h -> h.fields(EsConsts.BookIndex.FIELD_BOOK_NAME,
|
||||||
, t -> t.preTags("<em style='color:red'>").postTags("</em>"))
|
t -> t.preTags("<em style='color:red'>").postTags("</em>"))
|
||||||
.fields(EsConsts.BookIndex.FIELD_AUTHOR_NAME
|
.fields(EsConsts.BookIndex.FIELD_AUTHOR_NAME,
|
||||||
, t -> t.preTags("<em style='color:red'>").postTags("</em>")));
|
t -> t.preTags("<em style='color:red'>").postTags("</em>")));
|
||||||
|
|
||||||
return searchBuilder;
|
return searchBuilder;
|
||||||
},
|
},
|
||||||
@ -78,14 +76,17 @@ public class EsSearchServiceImpl implements SearchService {
|
|||||||
|
|
||||||
List<BookInfoRespDto> list = new ArrayList<>();
|
List<BookInfoRespDto> list = new ArrayList<>();
|
||||||
List<Hit<EsBookDto>> hits = response.hits().hits();
|
List<Hit<EsBookDto>> hits = response.hits().hits();
|
||||||
for (Hit<EsBookDto> hit : hits) {
|
// 类型推断 var 非常适合 for 循环,JDK 10 引入,JDK 11 改进
|
||||||
|
for (var hit : hits) {
|
||||||
EsBookDto book = hit.source();
|
EsBookDto book = hit.source();
|
||||||
assert book != null;
|
assert book != null;
|
||||||
if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME))) {
|
if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME))) {
|
||||||
book.setBookName(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME).get(0));
|
book.setBookName(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME).get(0));
|
||||||
}
|
}
|
||||||
if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME))) {
|
if (!CollectionUtils.isEmpty(
|
||||||
book.setAuthorName(hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME).get(0));
|
hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME))) {
|
||||||
|
book.setAuthorName(
|
||||||
|
hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME).get(0));
|
||||||
}
|
}
|
||||||
list.add(BookInfoRespDto.builder()
|
list.add(BookInfoRespDto.builder()
|
||||||
.id(book.getId())
|
.id(book.getId())
|
||||||
@ -99,23 +100,31 @@ public class EsSearchServiceImpl implements SearchService {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
assert total != null;
|
assert total != null;
|
||||||
return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list));
|
return RestResp.ok(
|
||||||
|
PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建检索条件
|
* 构建检索条件
|
||||||
*/
|
*/
|
||||||
private void buildSearchCondition(BookSearchReqDto condition, SearchRequest.Builder searchBuilder) {
|
private void buildSearchCondition(BookSearchReqDto condition,
|
||||||
|
SearchRequest.Builder searchBuilder) {
|
||||||
|
|
||||||
BoolQuery boolQuery = BoolQuery.of(b -> {
|
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())) {
|
if (!StringUtils.isBlank(condition.getKeyword())) {
|
||||||
// 关键词匹配
|
// 关键词匹配
|
||||||
b.must((q -> q.multiMatch(t -> t
|
b.must((q -> q.multiMatch(t -> t
|
||||||
.fields(EsConsts.BookIndex.FIELD_BOOK_NAME + "^2"
|
.fields(EsConsts.BookIndex.FIELD_BOOK_NAME + "^2",
|
||||||
, EsConsts.BookIndex.FIELD_AUTHOR_NAME + "^1.8"
|
EsConsts.BookIndex.FIELD_AUTHOR_NAME + "^1.8",
|
||||||
, EsConsts.BookIndex.FIELD_BOOK_DESC + "^0.1")
|
EsConsts.BookIndex.FIELD_BOOK_DESC + "^0.1")
|
||||||
.query(condition.getKeyword())
|
.query(condition.getKeyword())
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
@ -10,11 +10,10 @@ 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.cache.NewsCacheManager;
|
import io.github.xxyopen.novel.manager.cache.NewsCacheManager;
|
||||||
import io.github.xxyopen.novel.service.NewsService;
|
import io.github.xxyopen.novel.service.NewsService;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新闻模块 服务实现类
|
* 新闻模块 服务实现类
|
||||||
*
|
*
|
||||||
|
@ -8,20 +8,19 @@ 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.redis.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.SneakyThrows;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
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.web.multipart.MultipartFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源(图片/视频/文档)相关服务实现类
|
* 资源(图片/视频/文档)相关服务实现类
|
||||||
|
@ -22,13 +22,12 @@ 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.redis.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 org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.DigestUtils;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会员模块 服务实现类
|
* 会员模块 服务实现类
|
||||||
@ -69,7 +68,8 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
// 注册成功,保存用户信息
|
// 注册成功,保存用户信息
|
||||||
UserInfo userInfo = new UserInfo();
|
UserInfo userInfo = new UserInfo();
|
||||||
userInfo.setPassword(DigestUtils.md5DigestAsHex(dto.getPassword().getBytes(StandardCharsets.UTF_8)));
|
userInfo.setPassword(
|
||||||
|
DigestUtils.md5DigestAsHex(dto.getPassword().getBytes(StandardCharsets.UTF_8)));
|
||||||
userInfo.setUsername(dto.getUsername());
|
userInfo.setUsername(dto.getUsername());
|
||||||
userInfo.setNickName(dto.getUsername());
|
userInfo.setNickName(dto.getUsername());
|
||||||
userInfo.setCreateTime(LocalDateTime.now());
|
userInfo.setCreateTime(LocalDateTime.now());
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user