mirror of
https://github.com/201206030/novel.git
synced 2025-07-07 13:26:38 +00:00
Compare commits
53 Commits
v3.3.0.REL
...
master
Author | SHA1 | Date | |
---|---|---|---|
a23f4b202e | |||
cd3a7206a9 | |||
ab166a392a | |||
9d8709ed2d | |||
60488258f5 | |||
b7bb98db16 | |||
e54b656799 | |||
dccce83d1c | |||
b2c0340048 | |||
9f71aa4a59 | |||
295a9096b5 | |||
c46864bbb6 | |||
d63be23aca | |||
8da6f8263c | |||
b4ce4dd35d | |||
63760c8e90 | |||
e7005b9008 | |||
876d9b8cbe | |||
9da5064a9e | |||
03b3ca1d83 | |||
b0d2adebf6 | |||
f547a8b7d8 | |||
e09aad2415 | |||
7b4b97569b | |||
2270072d7e | |||
a31edb0c69 | |||
d6df259e94 | |||
1f2d8dc49a | |||
90f5780796 | |||
fc7983236b | |||
d5282a3974 | |||
74f5b58252 | |||
df1719e14c | |||
7de1dc6370 | |||
65274eae80 | |||
46d62d6aa6 | |||
afeadde581 | |||
62d7169304 | |||
c866702e48 | |||
46d03883ed | |||
3c93f90fad | |||
b5be909415 | |||
a318b05bee | |||
fefa5d94ca | |||
0ae939da16 | |||
4b3bcff05f | |||
a3a2384c95 | |||
31bd2c0bf8 | |||
87fdd2e6fc | |||
3dc7ed59a1 | |||
570ef7e7cb | |||
3e89d2a363 | |||
7c0ff5e9ce |
194
README.md
194
README.md
@ -1,24 +1,24 @@
|
|||||||
[]( https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=736e609d66e0ac4e57813316cec6fd0b&from=console )
|
[]( https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=736e609d66e0ac4e57813316cec6fd0b&from=console )
|
||||||
|
|
||||||
<p align="center">
|
<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 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://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 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://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>
|
</p>
|
||||||
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
|
|
||||||
novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开发的前后端分离的**学习型**小说项目,配备详细的项目教程手把手教你**从零开始**开发上线一个生产级别的 Java 系统,由小说门户系统、作家后台管理系统、平台后台管理系统、爬虫管理系统等多个子系统构成。包括小说推荐、作品检索、小说排行榜、小说阅读、小说评论、充值订阅、新闻发布等功能。
|
novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开发的前后端分离**学习型**
|
||||||
|
小说项目,配备[保姆级教程](https://docs.xxyopen.com/course/novel)手把手教你**从零开始**开发上线一套生产级别的 Java
|
||||||
|
系统,由小说门户系统、作家后台管理系统、平台后台管理系统等多个子系统构成。包括小说推荐、作品检索、小说排行榜、小说阅读、小说评论、会员中心、作家专区、充值订阅、新闻发布等功能。
|
||||||
|
|
||||||
## 项目地址
|
## 项目地址
|
||||||
|
|
||||||
- 后端项目(更新中):[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-front-web) | [码云](https://gitee.com/novel_dev_team/novel-front-web)
|
- 前端项目(更新中):[GitHub](https://github.com/201206030/novel-front-web)
|
||||||
- 线上应用版:[GitHub](https://github.com/201206030/novel-plus) | [码云](https://gitee.com/novel_dev_team/novel-plus) | [演示站点](http://47.106.243.172:8888/)
|
| [码云](https://gitee.com/novel_dev_team/novel-front-web)
|
||||||
|
- 线上应用版:[GitHub](https://github.com/201206030/novel-plus) | [码云](https://gitee.com/novel_dev_team/novel-plus)
|
||||||
- 微服务版:[GitHub](https://github.com/201206030/novel-cloud) | [码云](https://gitee.com/novel_dev_team/novel-cloud)
|
- 微服务版:[GitHub](https://github.com/201206030/novel-cloud) | [码云](https://gitee.com/novel_dev_team/novel-cloud)
|
||||||
|
|
||||||
## 开发环境
|
## 开发环境
|
||||||
@ -28,49 +28,51 @@ novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开
|
|||||||
- Elasticsearch 8.2.0(可选)
|
- Elasticsearch 8.2.0(可选)
|
||||||
- RabbitMQ 3.10.2(可选)
|
- RabbitMQ 3.10.2(可选)
|
||||||
- XXL-JOB 2.3.1(可选)
|
- XXL-JOB 2.3.1(可选)
|
||||||
- JDK 17
|
- JDK 21
|
||||||
- Maven 3.8
|
- Maven 3.8
|
||||||
- IntelliJ IDEA 2021.3(可选)
|
- IntelliJ IDEA(可选)
|
||||||
- Node 16.14
|
- Node 16.14
|
||||||
|
|
||||||
**注:Elasticsearch、RabbitMQ 和 XXL-JOB 默认关闭,可通过 application.yml 配置文件中相应的`enable`配置属性开启。**
|
**注: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.3.0 | 容器 + MVC 框架 | [进入](https://spring.io/projects/spring-boot) | [进入](https://docs.spring.io/spring-boot/docs/3.0.0/reference/html) |
|
||||||
| MyBatis | 3.5.9 | ORM 框架 | http://www.mybatis.org | [进入](https://mybatis.org/mybatis-3/zh/index.html) |
|
| Spring AI | 1.0.0-M6 | Spring 官方 AI 框架 | [进入](https://spring.io/projects/spring-ai) | [进入](https://docs.spring.io/spring-ai/reference/) |
|
||||||
| MyBatis-Plus | 3.5.1 | MyBatis 增强工具 | https://baomidou.com/ | [进入](https://baomidou.com/pages/24112f/) |
|
| MyBatis | 3.5.9 | ORM 框架 | [进入](http://www.mybatis.org) | [进入](https://mybatis.org/mybatis-3/zh/index.html) |
|
||||||
| JJWT | 0.11.5 | JWT 登录支持 | https://github.com/jwtk/jjwt | - |
|
| MyBatis-Plus | 3.5.3 | MyBatis 增强工具 | [进入](https://baomidou.com/) | [进入](https://baomidou.com/pages/24112f/) |
|
||||||
| Lombok | 1.18.24 | 简化对象封装工具 | https://github.com/projectlombok/lombok | [进入](https://projectlombok.org/features/all) |
|
| JJWT | 0.11.5 | JWT 登录支持 | [进入](https://github.com/jwtk/jjwt) | - |
|
||||||
| Caffeine | 3.1.0 | 本地缓存支持 | https://github.com/ben-manes/caffeine | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
|
| Lombok | 1.18.24 | 简化对象封装工具 | [进入](https://github.com/projectlombok/lombok) | [进入](https://projectlombok.org/features/all) |
|
||||||
| Redis | 7.0 | 分布式缓存支持 | https://redis.io | [进入](https://redis.io/docs) |
|
| Caffeine | 3.1.0 | 本地缓存支持 | [进入](https://github.com/ben-manes/caffeine) | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
|
||||||
| Redisson | 3.17.4 | 分布式锁实现 | https://github.com/redisson/redisson | [进入](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
|
| Redis | 7.0 | 分布式缓存支持 | [进入](https://redis.io) | [进入](https://redis.io/docs) |
|
||||||
| MySQL | 8.0 | 数据库服务 | https://www.mysql.com | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
|
| Redisson | 3.17.4 | 分布式锁实现 | [进入](https://github.com/redisson/redisson) | [进入](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
|
||||||
| ShardingSphere-JDBC | 5.1.1 | 数据库分库分表支持 | https://shardingsphere.apache.org | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
|
| MySQL | 8.0 | 数据库服务 | [进入](https://www.mysql.com) | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
|
||||||
| Elasticsearch | 8.2.0 | 搜索引擎服务 | https://www.elastic.co | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
|
| ShardingSphere-JDBC | 5.5.1 | 数据库分库分表支持 | [进入](https://shardingsphere.apache.org) | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
|
||||||
| RabbitMQ | 3.10.2 | 开源消息中间件 | https://www.rabbitmq.com | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
|
| Elasticsearch | 8.2.0 | 搜索引擎服务 | [进入](https://www.elastic.co) | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
|
||||||
| XXL-JOB | 2.3.1 | 分布式任务调度平台 | https://www.xuxueli.com/xxl-job | [进入](https://www.xuxueli.com/xxl-job) |
|
| RabbitMQ | 3.10.2 | 开源消息中间件 | [进入](https://www.rabbitmq.com) | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
|
||||||
| Sentinel | 1.8.4 | 流量控制组件 | https://github.com/alibaba/Sentinel | [进入](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5) |
|
| XXL-JOB | 2.3.1 | 分布式任务调度平台 | [进入](https://www.xuxueli.com/xxl-job) | [进入](https://www.xuxueli.com/xxl-job) |
|
||||||
| Springdoc-openapi | 2.0.0-M4-SNAPSHOT | Swagger 3 接口文档自动生成 | https://github.com/springdoc/springdoc-openapi | [进入](https://springdoc.org/) |
|
| Sentinel | 1.8.4 | 流量控制组件 | [进入](https://github.com/alibaba/Sentinel) | [进入](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5) |
|
||||||
| 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) |
|
| Springdoc-openapi | 2.0.0 | Swagger 3 接口文档自动生成 | [进入](https://github.com/springdoc/springdoc-openapi) | [进入](https://springdoc.org/) |
|
||||||
| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | https://undertow.io | [进入](https://undertow.io/documentation.html) |
|
| 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) |
|
||||||
| Docker | - | 应用容器引擎 | https://www.docker.com/ | - |
|
| Tomcat | 10.1.24 | Spring Boot 默认内嵌 Web 容器 | [进入](https://tomcat.apache.org) | [进入](https://tomcat.apache.org/tomcat-10.1-doc/index.html) |
|
||||||
| Jenkins | - | 自动化部署工具 | https://github.com/jenkinsci/jenkins | - |
|
| Docker | - | 应用容器引擎 | [进入](https://www.docker.com/) | - |
|
||||||
| Sonarqube | - | 代码质量控制 | https://www.sonarqube.org/ | - |
|
| Jenkins | - | 自动化部署工具 | [进入](https://github.com/jenkinsci/jenkins) | - |
|
||||||
|
| Sonarqube | - | 代码质量控制 | [进入](https://www.sonarqube.org/) | - |
|
||||||
|
|
||||||
**注:更多热门新技术待集成。**
|
**注:更多热门新技术待集成。**
|
||||||
|
|
||||||
## 前端技术选型
|
## 前端技术选型
|
||||||
|
|
||||||
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
| 技术 | 版本 | 说明 | 官网 | 学习 |
|
||||||
| :----------------- | :-----: | -------------------------- | --------------------------------------- | :-------------------------------------------------: |
|
| :----------------- | :-----: | -------------------------- | --------------------------------------- | :-------------------------------------------------: |
|
||||||
| Vue.js | 3.2.13 | 渐进式 JavaScript 框架 | https://vuejs.org | [进入](https://staging-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 | [进入](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 | [进入](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 | [进入](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) |
|
||||||
|
|
||||||
## 编码规范
|
## 编码规范
|
||||||
|
|
||||||
- 规范方式:严格遵守阿里编码规约。
|
- 规范方式:严格遵守阿里编码规约。
|
||||||
- 命名统一:简介最大程度上达到了见名知意。
|
- 命名统一:简介最大程度上达到了见名知意。
|
||||||
@ -183,116 +185,48 @@ io
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 安装步骤
|
## 安装步骤
|
||||||
|
|
||||||
此安装步骤的前提是需要保证上一节的开发环境可用。
|
👉 [立即查看](https://docs.xxyopen.com/course/novel/#%E5%AE%89%E8%A3%85%E6%AD%A5%E9%AA%A4)
|
||||||
|
|
||||||
- 下载后端源码
|
## 联系我们
|
||||||
|
|
||||||
```bash
|
👉 [立即查看](https://novel.xxyopen.com/service.htm)
|
||||||
git clone https://gitee.com/novel_dev_team/novel.git
|
|
||||||
```
|
|
||||||
|
|
||||||
- 数据库文件导入
|
## 问题
|
||||||
|
|
||||||
1. 新建数据库(建议 novel)
|
### 为什么有 novel/novel-cloud 学习版?
|
||||||
|
|
||||||
2. 解压后端源码`doc/sql/novel.sql.zip`压缩包,得到数据库结构文件`novel_struc.sql`和数据库小说数据文件`novel_data.sql`
|
最开始是没有学习版的,只有一个爬虫/原创小说项目(最终发展成为 [novel-plus](https://github.com/201206030/novel-plus)
|
||||||
|
项目),用户群体大部分是对小说有兴趣,想自建一个干净无广告的小说网站的个人和站长。
|
||||||
|
|
||||||
3. 导入`novel_struct.sql`数据库结构文件
|
后面随着使用人数逐渐增加,想通过这个项目来学习 Java 技术的人数也多了起来,对这部分用户来说,之前的项目用来学习很困难,具体原因如下:
|
||||||
|
|
||||||
4. 导入`novel_data.sql`数据库小说数据文件
|
1. novel-plus 功能模块比较多,重复性的增删改查占了大部分,而用户时间是有限的,很难在有限的时间内筛选出对自己有帮助的功能模块来学习。
|
||||||
|
2. novel-plus 追求的是系统稳定,用户很难在其中学习到最新的技术。
|
||||||
|
3. novel-plus 代码规范性不够,受限于开发时间限制,代码开发时没有选择一个标准化的规范去参考。
|
||||||
|
4. novel-plus 文档缺失,由于功能比较多,整个系统的教程编写需要花费大量时间,即使教程最终上线成功,用户也不可能有那么多时间也没有意义去学习所有的功能。
|
||||||
|
|
||||||
- novel 后端服务安装
|
最终,novel(单体架构) 和 novel-cloud(微服务架构)诞生了,这两个项目在保证核心流程完整的同时,从 novel-plus
|
||||||
|
中选用了一些有代表性的功能,使用最新技术栈(不间断地更新和集成新技术),在[保姆级教程](https://docs.xxyopen.com/course/novel)的帮助下,尽量保证每一个功能都能让你学到不重复的技术。
|
||||||
|
|
||||||
1. 修改`src/resources/application.yml`配置文件中的数据源配置
|
所以这两个项目我的重点是去堆技术而不是去堆功能,功能只是其中的辅助,堆太多的重复性增删改查功能没有意义,对学习的帮助也不大。
|
||||||
|
|
||||||
```yaml
|
### 谁适合使用 novel/novel-cloud 学习版项目?
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:mysql://localhost:3306/novel_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
|
||||||
username: root
|
|
||||||
password: test123456
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 修改`src/resources/application.yml` 和 `src/resources/redisson.yml` 配置文件中的`redis`连接配置
|
如果对下面任何一个问题你能回答 "是":
|
||||||
|
|
||||||
```yaml
|
1. 你没有项目经验,想学习如何从零开始开发上线一个生产级别的 Java 项目?
|
||||||
spring:
|
2. 你有项目经验,但是公司技术栈太落后,想学习最新的主流开发技术?
|
||||||
redis:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
password: 123456
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
那么,本项目正是你需要的。
|
||||||
singleServerConfig:
|
|
||||||
address: "redis://127.0.0.1:6379"
|
|
||||||
password: 123456
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 根据前后端的实际部署情况,修改`application.yml`中的跨域配置(默认情况可忽略此步骤)
|
### 谁暂时还不适合使用 novel/novel-cloud 学习版项目?
|
||||||
|
|
||||||
4. 项目根目录下运行如下命令来启动后端服务(有安装 IDE 的可以导入源码到 IDE 中运行)
|
如果对下面任何一个问题你能回答 "是":
|
||||||
|
|
||||||
```bash
|
1. 你不懂 Java ?
|
||||||
mvn spring-boot:run
|
2. 你只是想搭建一个小说网站使用?
|
||||||
```
|
3. 你想找一个完整的 Java 商用项目,有时间也有耐心去学习项目中的方方面面?
|
||||||
5. 接口文档访问地址:`http://server:port/swagger-ui/index.html`
|
|
||||||
|
|
||||||
|
那么,太遗憾了,本项目暂时不适合你,请使用 [novel-plus](https://github.com/201206030/novel-plus)。
|
||||||
- 下载前端前台门户系统源码
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://gitee.com/novel_dev_team/novel-front-web.git
|
|
||||||
```
|
|
||||||
|
|
||||||
- novel-front-web 前端前台门户系统安装
|
|
||||||
|
|
||||||
1. 根据前后端的实际部署情况,修改`.env.development`中的`VUE_APP_BASE_API_URL`属性(默认情况可忽略此步骤)
|
|
||||||
|
|
||||||
2. `yarn`安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g yarn
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 项目根目录下运行如下命令来安装项目依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn install
|
|
||||||
```
|
|
||||||
4. 项目根目录下运行如下命令启动
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn serve
|
|
||||||
```
|
|
||||||
5. 浏览器通过`http://localhost:1024`来访问
|
|
||||||
|
|
||||||
## 项目教程
|
|
||||||
|
|
||||||
[手把手教你从零开始开发上线一个生产级别的小说系统](https://docs.xxyopen.com/course/novel/3.html)
|
|
||||||
|
|
||||||
## 公众号
|
|
||||||
|
|
||||||
- 关注公众号接收`项目`和`文档`的更新动态
|
|
||||||
|
|
||||||
- 加微信群学习交流,公众号后台回复「**微信群**」即可
|
|
||||||
|
|
||||||
- 回复「**资料**」获取`Java 学习面试资料`
|
|
||||||
|
|
||||||
- 回复「**笔记**」获取`Spring Boot 3 学习笔记`
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 赞赏支持
|
|
||||||
|
|
||||||
开源项目不易,若此项目能得到你的青睐,那么你可以赞赏支持作者持续开发与维护。
|
|
||||||
|
|
||||||
- 更完善的文档教程
|
|
||||||
- 服务器的费用也是一笔开销
|
|
||||||
- 为用户提供更好的开发环境
|
|
||||||
- 一杯咖啡
|
|
||||||
|
|
||||||

|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
1. 初始状态下,MySQL 只需要执行 `novel.sql` 文件即可正常运行本系统
|
1. 初始状态下,MySQL 只需要执行 `novel.sql` 文件即可正常运行本系统
|
||||||
2. 代码更新后再执行以日期命名的增量 SQL 文件
|
2. 只有开启 XXL-JOB 的功能,才需要执行 `xxl-job.sql` 文件
|
||||||
3. 只有开启 XXL-JOB 的功能,才需要执行 `xxl-job.sql` 和以 xxl-job 开头日期结尾的增量 SQL 文件
|
3. 只有开启 ShardingSphere-JDBC 的功能,才需要执行 `shardingsphere-jdbc.sql` 文件
|
||||||
4. 只有开启 ShardingSphere-JDBC 的功能,才需要执行 `shardingsphere-jdbc.sql` 和以 shardingsphere-jdbc 开头日期结尾的增量 SQL
|
|
||||||
文件
|
|
||||||
|
|
||||||
|
162
pom.xml
162
pom.xml
@ -6,51 +6,38 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.0.0-SNAPSHOT</version>
|
<version>3.3.0</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>io.github.xxyopen</groupId>
|
<groupId>io.github.xxyopen</groupId>
|
||||||
<artifactId>novel</artifactId>
|
<artifactId>novel</artifactId>
|
||||||
<version>3.3.0</version>
|
<version>3.5.1-SNAPSHOT</version>
|
||||||
<name>novel</name>
|
<name>novel</name>
|
||||||
<description>Spring Boot 3 + Vue 3 构建的前后端分离小说系统</description>
|
<description>Spring Boot 3 + Vue 3 构建的前后端分离小说系统</description>
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>17</java.version>
|
<java.version>21</java.version>
|
||||||
<mybatis-plus.version>3.5.1</mybatis-plus.version>
|
<mybatis-plus.version>3.5.6</mybatis-plus.version>
|
||||||
<spring.version>6.0.0-SNAPSHOT</spring.version>
|
<mybatis-plus-generator.version>3.5.1</mybatis-plus-generator.version>
|
||||||
<jjwt.version>0.11.5</jjwt.version>
|
<jjwt.version>0.11.5</jjwt.version>
|
||||||
<elasticsearch.version>8.2.0</elasticsearch.version>
|
|
||||||
<xxl-job.version>2.3.1</xxl-job.version>
|
<xxl-job.version>2.3.1</xxl-job.version>
|
||||||
<sentinel.version>1.8.4</sentinel.version>
|
<sentinel.version>1.8.4</sentinel.version>
|
||||||
<shardingsphere-jdbc.version>5.1.1</shardingsphere-jdbc.version>
|
<shardingsphere-jdbc.version>5.5.1</shardingsphere-jdbc.version>
|
||||||
<redisson.version>3.17.4</redisson.version>
|
<redisson.version>3.19.1</redisson.version>
|
||||||
<spring-boot-admin.version>3.0.0-M1</spring-boot-admin.version>
|
<spring-boot-admin.version>3.0.0-M1</spring-boot-admin.version>
|
||||||
<springdoc-openapi.version>2.0.0-M4-SNAPSHOT</springdoc-openapi.version>
|
<springdoc-openapi.version>2.5.0</springdoc-openapi.version>
|
||||||
|
<logbook.version>3.9.0</logbook.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
<exclusions>
|
|
||||||
<!-- Exclude the Tomcat dependency -->
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Use Undertow instead -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- mybatis-plus -->
|
<!-- mybatis-plus -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
<version>${mybatis-plus.version}</version>
|
<version>${mybatis-plus.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@ -58,7 +45,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-generator</artifactId>
|
<artifactId>mybatis-plus-generator</artifactId>
|
||||||
<version>${mybatis-plus.version}</version>
|
<version>${mybatis-plus-generator.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -107,15 +94,9 @@
|
|||||||
<artifactId>hibernate-validator</artifactId>
|
<artifactId>hibernate-validator</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- elasticsearch 相关 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>co.elastic.clients</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>elasticsearch-java</artifactId>
|
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
|
||||||
<version>${elasticsearch.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- MQ 相关 -->
|
<!-- MQ 相关 -->
|
||||||
@ -146,9 +127,14 @@
|
|||||||
<!-- ShardingSphere-JDBC -->
|
<!-- ShardingSphere-JDBC -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.shardingsphere</groupId>
|
<groupId>org.apache.shardingsphere</groupId>
|
||||||
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
|
<artifactId>shardingsphere-jdbc</artifactId>
|
||||||
<version>${shardingsphere-jdbc.version}</version>
|
<version>${shardingsphere-jdbc.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Boot 管理和监控 -->
|
<!-- Spring Boot 管理和监控 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -168,7 +154,7 @@
|
|||||||
<!-- Redisson 相关 -->
|
<!-- Redisson 相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.redisson</groupId>
|
<groupId>org.redisson</groupId>
|
||||||
<artifactId>redisson</artifactId>
|
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||||
<version>${redisson.version}</version>
|
<version>${redisson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@ -185,9 +171,21 @@
|
|||||||
<version>${springdoc-openapi.version}</version>
|
<version>${springdoc-openapi.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 邮件 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AI -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -205,8 +203,33 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-mysql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.zalando</groupId>
|
||||||
|
<artifactId>logbook-spring-boot-starter</artifactId>
|
||||||
|
<version>${logbook.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-bom</artifactId>
|
||||||
|
<version>1.0.0-M6</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
@ -221,11 +244,20 @@
|
|||||||
</excludes>
|
</excludes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<!--指定 Java 编译器的 -source 参数 -->
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<!--指定 Java 编译器的 -target 参数 -->
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>ali</id>
|
<id>aliyun</id>
|
||||||
<url>https://maven.aliyun.com/repository/public</url>
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
@ -234,48 +266,10 @@
|
|||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
</snapshots>
|
</snapshots>
|
||||||
</repository>
|
</repository>
|
||||||
<repository>
|
|
||||||
<id>spring-milestones</id>
|
|
||||||
<name>Spring Milestones</name>
|
|
||||||
<url>https://repo.spring.io/milestone</url>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</snapshots>
|
|
||||||
</repository>
|
|
||||||
<repository>
|
|
||||||
<id>spring-snapshots</id>
|
|
||||||
<name>Spring Snapshots</name>
|
|
||||||
<url>https://repo.spring.io/snapshot</url>
|
|
||||||
<releases>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</releases>
|
|
||||||
</repository>
|
|
||||||
<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>
|
<pluginRepository>
|
||||||
<id>ali</id>
|
<id>aliyun</id>
|
||||||
<url>https://maven.aliyun.com/repository/public</url>
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
@ -284,22 +278,6 @@
|
|||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
</snapshots>
|
</snapshots>
|
||||||
</pluginRepository>
|
</pluginRepository>
|
||||||
<pluginRepository>
|
|
||||||
<id>spring-milestones</id>
|
|
||||||
<name>Spring Milestones</name>
|
|
||||||
<url>https://repo.spring.io/milestone</url>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</snapshots>
|
|
||||||
</pluginRepository>
|
|
||||||
<pluginRepository>
|
|
||||||
<id>spring-snapshots</id>
|
|
||||||
<name>Spring Snapshots</name>
|
|
||||||
<url>https://repo.spring.io/snapshot</url>
|
|
||||||
<releases>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</releases>
|
|
||||||
</pluginRepository>
|
|
||||||
</pluginRepositories>
|
</pluginRepositories>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
package io.github.xxyopen.novel;
|
package io.github.xxyopen.novel;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
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;
|
||||||
@ -22,8 +15,10 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
@OpenAPIDefinition(info = @Info(title = "novel 项目接口文档", version = "v3.2.0", license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0")))
|
import javax.sql.DataSource;
|
||||||
@SecurityScheme(type = SecuritySchemeType.APIKEY, in = SecuritySchemeIn.HEADER, name = SystemConfigConsts.HTTP_AUTH_HEADER_NAME, description = "登录 token")
|
import java.sql.Connection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@MapperScan("io.github.xxyopen.novel.dao.mapper")
|
@MapperScan("io.github.xxyopen.novel.dao.mapper")
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
@ -36,7 +31,7 @@ public class NovelApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CommandLineRunner commandLineRunner(ApplicationContext context) {
|
public CommandLineRunner commandLineRunner(ApplicationContext context, DataSource dataSource) {
|
||||||
return args -> {
|
return args -> {
|
||||||
Map<String, CacheManager> beans = context.getBeansOfType(CacheManager.class);
|
Map<String, CacheManager> beans = context.getBeansOfType(CacheManager.class);
|
||||||
log.info("加载了如下缓存管理器:");
|
log.info("加载了如下缓存管理器:");
|
||||||
@ -44,15 +39,25 @@ public class NovelApplication {
|
|||||||
log.info("{}:{}", k, v.getClass().getName());
|
log.info("{}:{}", k, v.getClass().getName());
|
||||||
log.info("缓存:{}", v.getCacheNames());
|
log.info("缓存:{}", v.getCacheNames());
|
||||||
});
|
});
|
||||||
|
if(dataSource instanceof HikariDataSource hikariDataSource) {
|
||||||
|
// 如果使用的是HikariDataSource,需要提前创建连接池,而不是在第一次访问数据库时才创建,提高第一次访问接口的速度
|
||||||
|
log.info("创建连接池...");
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
log.info("最小空闲连接数:{}", hikariDataSource.getMinimumIdle());
|
||||||
|
log.info("最大连接数:{}", hikariDataSource.getMaximumPoolSize());
|
||||||
|
log.info("创建连接池完成.");
|
||||||
|
log.info("数据库:{}", connection.getMetaData().getDatabaseProductName());
|
||||||
|
log.info("数据库版本:{}", connection.getMetaData().getDatabaseProductVersion());
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http.csrf().disable()
|
http.csrf().disable()
|
||||||
.requestMatcher(EndpointRequest.toAnyEndpoint())
|
.securityMatcher(EndpointRequest.toAnyEndpoint())
|
||||||
.authorizeRequests(requests -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
|
.authorizeHttpRequests(requests -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
|
||||||
http.httpBasic();
|
http.httpBasic();
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
package io.github.xxyopen.novel.controller.author;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
|
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||||
|
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家后台-AI模块API控制器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2025/2/19
|
||||||
|
*/
|
||||||
|
@Tag(name = "AiController", description = "作家后台-AI模块")
|
||||||
|
@SecurityRequirement(name = SystemConfigConsts.HTTP_AUTH_HEADER_NAME)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(ApiRouterConsts.API_AUTHOR_AI_URL_PREFIX)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthorAiController {
|
||||||
|
|
||||||
|
private final ChatClient chatClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI扩写
|
||||||
|
*/
|
||||||
|
@Operation(summary = "AI扩写接口")
|
||||||
|
@PostMapping("/expand")
|
||||||
|
public RestResp<String> expandText(@RequestParam("text") String text, @RequestParam("ratio") Double ratio) {
|
||||||
|
String prompt = "请将以下文本扩写为原长度的" + ratio/100 + "倍:" + text;
|
||||||
|
return RestResp.ok(chatClient.prompt()
|
||||||
|
.user(prompt)
|
||||||
|
.call()
|
||||||
|
.content());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI缩写
|
||||||
|
*/
|
||||||
|
@Operation(summary = "AI缩写接口")
|
||||||
|
@PostMapping("/condense")
|
||||||
|
public RestResp<String> condenseText(@RequestParam("text") String text, @RequestParam("ratio") Integer ratio) {
|
||||||
|
String prompt = "请将以下文本缩写为原长度的" + 100/ratio + "分之一:" + text;
|
||||||
|
return RestResp.ok(chatClient.prompt()
|
||||||
|
.user(prompt)
|
||||||
|
.call()
|
||||||
|
.content());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI续写
|
||||||
|
*/
|
||||||
|
@Operation(summary = "AI续写接口")
|
||||||
|
@PostMapping("/continue")
|
||||||
|
public RestResp<String> continueText(@RequestParam("text") String text, @RequestParam("length") Integer length) {
|
||||||
|
String prompt = "请续写以下文本,续写长度约为" + length + "字:" + text;
|
||||||
|
return RestResp.ok(chatClient.prompt()
|
||||||
|
.user(prompt)
|
||||||
|
.call()
|
||||||
|
.content());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI润色
|
||||||
|
*/
|
||||||
|
@Operation(summary = "AI润色接口")
|
||||||
|
@PostMapping("/polish")
|
||||||
|
public RestResp<String> polishText(@RequestParam("text") String text) {
|
||||||
|
String prompt = "请润色优化以下文本,保持原意:" + text;
|
||||||
|
return RestResp.ok(chatClient.prompt()
|
||||||
|
.user(prompt)
|
||||||
|
.call()
|
||||||
|
.content());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,8 +9,10 @@ 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.req.ChapterUpdateReqDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.ChapterContentRespDto;
|
||||||
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.Operation;
|
||||||
@ -20,12 +22,7 @@ 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.springdoc.core.annotations.ParameterObject;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 作家后台-作家模块 API 控制器
|
* 作家后台-作家模块 API 控制器
|
||||||
@ -93,6 +90,37 @@ public class AuthorController {
|
|||||||
return bookService.saveBookChapter(dto);
|
return bookService.saveBookChapter(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节删除接口
|
||||||
|
*/
|
||||||
|
@Operation(summary = "小说章节删除接口")
|
||||||
|
@DeleteMapping("book/chapter/{chapterId}")
|
||||||
|
public RestResp<Void> deleteBookChapter(
|
||||||
|
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||||
|
return bookService.deleteBookChapter(chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节查询接口
|
||||||
|
*/
|
||||||
|
@Operation(summary = "小说章节查询接口")
|
||||||
|
@GetMapping("book/chapter/{chapterId}")
|
||||||
|
public RestResp<ChapterContentRespDto> getBookChapter(
|
||||||
|
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||||
|
return bookService.getBookChapter(chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节更新接口
|
||||||
|
*/
|
||||||
|
@Operation(summary = "小说章节更新接口")
|
||||||
|
@PutMapping("book/chapter/{chapterId}")
|
||||||
|
public RestResp<Void> updateBookChapter(
|
||||||
|
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId,
|
||||||
|
@Valid @RequestBody ChapterUpdateReqDto dto) {
|
||||||
|
return bookService.updateBookChapter(chapterId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说章节发布列表查询接口
|
* 小说章节发布列表查询接口
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +8,7 @@ import io.github.xxyopen.novel.service.HomeService;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
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;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@ -24,6 +25,7 @@ import java.util.List;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(ApiRouterConsts.API_FRONT_HOME_URL_PREFIX)
|
@RequestMapping(ApiRouterConsts.API_FRONT_HOME_URL_PREFIX)
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class HomeController {
|
public class HomeController {
|
||||||
|
|
||||||
private final HomeService homeService;
|
private final HomeService homeService;
|
||||||
@ -34,6 +36,8 @@ public class HomeController {
|
|||||||
@Operation(summary = "首页小说推荐查询接口")
|
@Operation(summary = "首页小说推荐查询接口")
|
||||||
@GetMapping("books")
|
@GetMapping("books")
|
||||||
public RestResp<List<HomeBookRespDto>> listHomeBooks() {
|
public RestResp<List<HomeBookRespDto>> listHomeBooks() {
|
||||||
|
// 测试虚拟线程处理请求
|
||||||
|
log.debug("处理请求的线程:{}", Thread.currentThread());
|
||||||
return homeService.listHomeBooks();
|
return homeService.listHomeBooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package io.github.xxyopen.novel.controller.front;
|
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.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.core.constant.SystemConfigConsts;
|
||||||
@ -8,6 +10,7 @@ import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
|||||||
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
import io.github.xxyopen.novel.dto.req.UserInfoUptReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
import io.github.xxyopen.novel.dto.req.UserLoginReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
import io.github.xxyopen.novel.dto.req.UserRegisterReqDto;
|
||||||
|
import io.github.xxyopen.novel.dto.resp.UserCommentRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserInfoRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserInfoRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
||||||
@ -19,14 +22,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.DeleteMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
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 控制器
|
||||||
@ -138,4 +134,13 @@ public class UserController {
|
|||||||
return userService.getBookshelfStatus(UserHolder.getUserId(), bookId);
|
return userService.getBookshelfStatus(UserHolder.getUserId(), bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询评论
|
||||||
|
*/
|
||||||
|
@Operation(summary = "查询会员评论列表接口")
|
||||||
|
@GetMapping("comments")
|
||||||
|
public RestResp<PageRespDto<UserCommentRespDto>> listComments(PageReqDto pageReqDto) {
|
||||||
|
return bookService.listComments(UserHolder.getUserId(), pageReqDto);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ package io.github.xxyopen.novel.core.aspect;
|
|||||||
import io.github.xxyopen.novel.core.annotation.Key;
|
import io.github.xxyopen.novel.core.annotation.Key;
|
||||||
import io.github.xxyopen.novel.core.annotation.Lock;
|
import io.github.xxyopen.novel.core.annotation.Lock;
|
||||||
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
||||||
import java.lang.reflect.Method;
|
import lombok.RequiredArgsConstructor;
|
||||||
import java.lang.reflect.Parameter;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
@ -20,6 +18,10 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分布式锁 切面
|
* 分布式锁 切面
|
||||||
*
|
*
|
||||||
@ -28,7 +30,10 @@ import org.springframework.util.StringUtils;
|
|||||||
*/
|
*/
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public record LockAspect(RedissonClient redissonClient) {
|
@RequiredArgsConstructor
|
||||||
|
public class LockAspect {
|
||||||
|
|
||||||
|
private final RedissonClient redissonClient;
|
||||||
|
|
||||||
private static final String KEY_PREFIX = "Lock";
|
private static final String KEY_PREFIX = "Lock";
|
||||||
|
|
||||||
|
@ -3,9 +3,12 @@ package io.github.xxyopen.novel.core.common.exception;
|
|||||||
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.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用的异常处理器
|
* 通用的异常处理器
|
||||||
@ -17,6 +20,15 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
public class CommonExceptionHandler {
|
public class CommonExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理404异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
public String handlerNotFound() {
|
||||||
|
return "404";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理数据校验异常
|
* 处理数据校验异常
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ai 相关配置
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2025/2/19
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class AiConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目的:配置自定义的 RestClientBuilder 对象
|
||||||
|
* <p>
|
||||||
|
* 原因:Spring AI 框架的 ChatClient 内部通过 RestClient(Spring Framework 6 和 Spring Boot 3 中引入) 发起 HTTP REST 请求与远程的大模型服务进行通信,
|
||||||
|
* 如果项目中没有配置自定义的 RestClientBuilder 对象, 那么在 RestClient 的自动配置类 org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration
|
||||||
|
* 中配置的 RestClientBuilder 对象会使用 Spring 容器中提供的 HttpMessageConverters, 由于本项目中配置了 spring.jackson.generator.write-numbers-as-strings
|
||||||
|
* = true, 所以 Spring 容器中的 HttpMessageConverters 在 RestClient 发起 HTTP REST 请求转换 Java 对象为 JSON 字符串时会自动将 Number 类型的
|
||||||
|
* Java 对象属性转换为字符串而导致请求参数错误
|
||||||
|
* <p>
|
||||||
|
* 示例:"temperature": 0.7 =》"temperature": "0.7"
|
||||||
|
* {"code":20015,"message":"The parameter is invalid. Please check again.","data":null}
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public RestClient.Builder restClientBuilder() {
|
||||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||||
|
// 连接超时时间
|
||||||
|
factory.setConnectTimeout(5000);
|
||||||
|
// 读取超时时间
|
||||||
|
factory.setReadTimeout(60000);
|
||||||
|
return RestClient.builder().requestFactory(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
|
||||||
|
return chatClientBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,35 +1,73 @@
|
|||||||
package io.github.xxyopen.novel.core.config;
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
|
||||||
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.elasticsearch.client.RestClient;
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.elasticsearch.client.RestClientBuilder;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.autoconfigure.elasticsearch.RestClientBuilderCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* elasticsearch 相关配置
|
* Elasticsearch 相关配置
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/23
|
* @date 2022/5/23
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true")
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class EsConfig {
|
public class EsConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fix `sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
|
||||||
|
* unable to find valid certification path to requested target`
|
||||||
|
*/
|
||||||
|
@ConditionalOnProperty(value = "spring.elasticsearch.ssl.verification-mode", havingValue = "none")
|
||||||
@Bean
|
@Bean
|
||||||
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
|
RestClient elasticsearchRestClient(RestClientBuilder restClientBuilder,
|
||||||
|
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
|
||||||
|
restClientBuilder.setHttpClientConfigCallback((HttpAsyncClientBuilder clientBuilder) -> {
|
||||||
|
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||||
|
|
||||||
// Create the transport with a Jackson mapper
|
}
|
||||||
ElasticsearchTransport transport = new RestClientTransport(
|
|
||||||
restClient, new JacksonJsonpMapper());
|
|
||||||
|
|
||||||
// And create the API client
|
@Override
|
||||||
return new ElasticsearchClient(transport);
|
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[0];
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
SSLContext sc = null;
|
||||||
|
try {
|
||||||
|
sc = SSLContext.getInstance("SSL");
|
||||||
|
sc.init(null, trustAllCerts, new SecureRandom());
|
||||||
|
} catch (KeyManagementException | NoSuchAlgorithmException e) {
|
||||||
|
log.error("Elasticsearch RestClient 配置失败!", e);
|
||||||
|
}
|
||||||
|
assert sc != null;
|
||||||
|
clientBuilder.setSSLContext(sc);
|
||||||
|
clientBuilder.setSSLHostnameVerifier((hostname, session) -> true);
|
||||||
|
|
||||||
|
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(clientBuilder));
|
||||||
|
return clientBuilder;
|
||||||
|
});
|
||||||
|
return restClientBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.zalando.logbook.Logbook;
|
||||||
|
|
||||||
|
import static org.zalando.logbook.core.Conditions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logbook 配置
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2024/9/13
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class LogbookConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Logbook logbook() {
|
||||||
|
return Logbook.builder()
|
||||||
|
.condition(exclude(
|
||||||
|
// 忽略 OPTIONS 请求
|
||||||
|
requestWithMethod("OPTIONS"),
|
||||||
|
// 忽略 /actuator 以及其子路径(Spring Boot Actuator 提供的端点)的请求
|
||||||
|
requestTo("/actuator/**"),
|
||||||
|
// 忽略 Swagger 文档路径
|
||||||
|
requestTo("/swagger-ui/**"),
|
||||||
|
requestTo("/v3/api-docs/**"),
|
||||||
|
// 忽略二进制文件请求
|
||||||
|
contentType("application/octet-stream"),
|
||||||
|
// 忽略文件上传请求
|
||||||
|
contentType("multipart/form-data")
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mail 配置属性
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/25
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties(prefix = "spring.mail")
|
||||||
|
public record MailProperties(String nickname, String username) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
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 org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenApi 配置类
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/9/1
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Profile("dev")
|
||||||
|
@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")
|
||||||
|
public class OpenApiConfig {
|
||||||
|
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
package io.github.xxyopen.novel.core.config;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
|
||||||
|
import org.apache.shardingsphere.infra.url.core.ShardingSphereURL;
|
||||||
|
import org.apache.shardingsphere.infra.url.core.ShardingSphereURLLoadEngine;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShardingSphere 配置类,控制是否开启 ShardingSphere
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/12/21
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
prefix = "spring.shardingsphere",
|
||||||
|
name = {"enabled"},
|
||||||
|
havingValue = "true"
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class ShardingSphereConfiguration {
|
||||||
|
|
||||||
|
private static final String URL = "classpath:shardingsphere-jdbc.yml";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SneakyThrows
|
||||||
|
public DataSource shardingSphereDataSource() {
|
||||||
|
log.info(">>>>>>>>>>> shardingSphereDataSource init.");
|
||||||
|
ShardingSphereURLLoadEngine urlLoadEngine = new ShardingSphereURLLoadEngine(
|
||||||
|
ShardingSphereURL.parse(URL));
|
||||||
|
return YamlShardingSphereDataSourceFactory.createDataSource(urlLoadEngine.loadContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,6 @@ package io.github.xxyopen.novel.core.config;
|
|||||||
|
|
||||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
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.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -16,7 +14,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
* @date 2022/5/31
|
* @date 2022/5/31
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnProperty(prefix = "xxl.job", name = "enable", havingValue = "true")
|
@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class XxlJobConfig {
|
public class XxlJobConfig {
|
||||||
|
|
||||||
|
@ -62,6 +62,11 @@ public class ApiRouterConsts {
|
|||||||
*/
|
*/
|
||||||
public static final String SEARCH_URL_PREFIX = "/search";
|
public static final String SEARCH_URL_PREFIX = "/search";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI模块请求路径前缀
|
||||||
|
*/
|
||||||
|
public static final String AI_URL_PREFIX = "/ai";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台门户首页API请求路径前缀
|
* 前台门户首页API请求路径前缀
|
||||||
*/
|
*/
|
||||||
@ -94,4 +99,10 @@ public class ApiRouterConsts {
|
|||||||
public static final String API_FRONT_SEARCH_URL_PREFIX =
|
public static final String API_FRONT_SEARCH_URL_PREFIX =
|
||||||
API_FRONT_URL_PREFIX + SEARCH_URL_PREFIX;
|
API_FRONT_URL_PREFIX + SEARCH_URL_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作家后台AI相关API请求路径前缀
|
||||||
|
*/
|
||||||
|
public static final String API_AUTHOR_AI_URL_PREFIX = API_AUTHOR_URL_PREFIX + AI_URL_PREFIX;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package io.github.xxyopen.novel.core.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息发送器的类型
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/24
|
||||||
|
*/
|
||||||
|
public class MessageSenderTypeConsts {
|
||||||
|
|
||||||
|
private MessageSenderTypeConsts() {
|
||||||
|
throw new IllegalStateException("Constant class");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册成功的邮件发送器
|
||||||
|
*/
|
||||||
|
public static final String REGISTER_MAIL_SENDER = "registerMailSender";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秒杀活动的系统通知发送器
|
||||||
|
*/
|
||||||
|
public static final String SECKILL_SYS_NOTICE_SENDER = "seckillSysNoticeSender";
|
||||||
|
|
||||||
|
}
|
@ -9,14 +9,15 @@ 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 上下文
|
||||||
*
|
*
|
||||||
@ -31,6 +32,9 @@ public class AuthInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handle 执行前调用
|
||||||
|
*/
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||||
@ -60,12 +64,26 @@ public class AuthInterceptor implements HandlerInterceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handler 执行后调用,出现异常不调用
|
||||||
|
*/
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||||
ModelAndView modelAndView) throws Exception {
|
ModelAndView modelAndView) throws Exception {
|
||||||
// 清理当前线程保存的用户数据
|
|
||||||
UserHolder.clear();
|
|
||||||
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DispatcherServlet 完全处理完请求后调用,出现异常照常调用
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
|
||||||
|
throws Exception {
|
||||||
|
// 清理当前线程保存的用户数据
|
||||||
|
UserHolder.clear();
|
||||||
|
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token 解析拦截器
|
* Token 解析拦截器
|
||||||
@ -23,6 +22,7 @@ public class TokenParseInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
private final JwtUtils jwtUtils;
|
private final JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||||
Object handler) throws Exception {
|
Object handler) throws Exception {
|
||||||
@ -35,11 +35,16 @@ public class TokenParseInterceptor implements HandlerInterceptor {
|
|||||||
return HandlerInterceptor.super.preHandle(request, response, handler);
|
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DispatcherServlet 完全处理完请求后调用,出现异常照常调用
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
@Override
|
@Override
|
||||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
|
||||||
ModelAndView modelAndView) throws Exception {
|
throws Exception {
|
||||||
// 清理当前线程保存的用户数据
|
// 清理当前线程保存的用户数据
|
||||||
UserHolder.clear();
|
UserHolder.clear();
|
||||||
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@ import org.springframework.stereotype.Component;
|
|||||||
* @date 2022/5/25
|
* @date 2022/5/25
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@ConditionalOnProperty(prefix = "spring", name = {"elasticsearch.enable",
|
@ConditionalOnProperty(prefix = "spring", name = {"elasticsearch.enabled",
|
||||||
"amqp.enable"}, havingValue = "true")
|
"amqp.enabled"}, havingValue = "true")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RabbitQueueListener {
|
public class RabbitQueueListener {
|
||||||
|
@ -13,20 +13,21 @@ 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.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说数据同步到 elasticsearch 任务
|
* 小说数据同步到 elasticsearch 任务
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/23
|
* @date 2022/5/23
|
||||||
*/
|
*/
|
||||||
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true")
|
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enabled", havingValue = "true")
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节发布 请求DTO
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/23
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ChapterUpdateReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节名
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
@Schema(description = "章节名", required = true)
|
||||||
|
private String chapterName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节内容
|
||||||
|
*/
|
||||||
|
@Schema(description = "章节内容", required = true)
|
||||||
|
@NotBlank
|
||||||
|
@Length(min = 50)
|
||||||
|
private String chapterContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
@Schema(description = "是否收费;1-收费 0-免费", required = true)
|
||||||
|
@NotNull
|
||||||
|
private Integer isVip;
|
||||||
|
|
||||||
|
}
|
@ -24,7 +24,7 @@ public class BookChapterRespDto implements Serializable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 章节ID
|
* 章节ID
|
||||||
* */
|
*/
|
||||||
@Schema(description = "章节ID")
|
@Schema(description = "章节ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class BookChapterRespDto implements Serializable {
|
|||||||
* 章节更新时间
|
* 章节更新时间
|
||||||
*/
|
*/
|
||||||
@Schema(description = "章节更新时间")
|
@Schema(description = "章节更新时间")
|
||||||
@JsonFormat(pattern = "yyyy/MM/dd HH:dd")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
|
||||||
private LocalDateTime chapterUpdateTime;
|
private LocalDateTime chapterUpdateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说内容 响应DTO
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2022/5/15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ChapterContentRespDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节标题
|
||||||
|
*/
|
||||||
|
@Schema(description = "章节名")
|
||||||
|
private String chapterName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节内容
|
||||||
|
*/
|
||||||
|
@Schema(description = "章节内容")
|
||||||
|
private String chapterContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否收费;1-收费 0-免费
|
||||||
|
*/
|
||||||
|
@Schema(description = "是否收费;1-收费 0-免费")
|
||||||
|
private Integer isVip;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package io.github.xxyopen.novel.dto.resp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户评论响应 Dto
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/4/25
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class UserCommentRespDto {
|
||||||
|
|
||||||
|
@Schema(description = "评论内容")
|
||||||
|
private String commentContent;
|
||||||
|
|
||||||
|
@Schema(description = "评论小说封面")
|
||||||
|
private String commentBookPic;
|
||||||
|
|
||||||
|
@Schema(description = "评论小说")
|
||||||
|
private String commentBook;
|
||||||
|
|
||||||
|
@Schema(description = "评论时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime commentTime;
|
||||||
|
|
||||||
|
}
|
@ -5,6 +5,7 @@ import io.github.xxyopen.novel.dao.entity.BookChapter;
|
|||||||
import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -34,8 +35,14 @@ public class BookChapterCacheManager {
|
|||||||
.chapterName(bookChapter.getChapterName())
|
.chapterName(bookChapter.getChapterName())
|
||||||
.chapterWordCount(bookChapter.getWordCount())
|
.chapterWordCount(bookChapter.getWordCount())
|
||||||
.chapterUpdateTime(bookChapter.getUpdateTime())
|
.chapterUpdateTime(bookChapter.getUpdateTime())
|
||||||
|
.isVip(bookChapter.getIsVip())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheEvict(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
|
value = CacheConsts.BOOK_CHAPTER_CACHE_NAME)
|
||||||
|
public void evictBookChapterCache(Long chapterId) {
|
||||||
|
// 调用此方法自动清除小说章节信息的缓存
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
|||||||
import io.github.xxyopen.novel.dao.entity.BookContent;
|
import io.github.xxyopen.novel.dao.entity.BookContent;
|
||||||
import io.github.xxyopen.novel.dao.mapper.BookContentMapper;
|
import io.github.xxyopen.novel.dao.mapper.BookContentMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -34,5 +35,11 @@ public class BookContentCacheManager {
|
|||||||
return bookContent.getContent();
|
return bookContent.getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheEvict(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||||
|
value = CacheConsts.BOOK_CONTENT_CACHE_NAME)
|
||||||
|
public void evictBookContentCache(Long chapterId) {
|
||||||
|
// 调用此方法自动清除小说内容信息的缓存
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,14 @@ 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.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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说信息 缓存管理类
|
* 小说信息 缓存管理类
|
||||||
*
|
*
|
||||||
@ -74,7 +75,7 @@ public class BookInfoCacheManager {
|
|||||||
|
|
||||||
@CacheEvict(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
@CacheEvict(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||||
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||||
public void evictBookInfoCache(Long ignoredId) {
|
public void evictBookInfoCache(Long bookId) {
|
||||||
// 调用此方法自动清除小说信息的缓存
|
// 调用此方法自动清除小说信息的缓存
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package io.github.xxyopen.novel.manager.message;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.config.MailProperties;
|
||||||
|
import jakarta.mail.internet.InternetAddress;
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象的邮件消息发送者
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/24
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class AbstractMailSender extends AbstractMessageSender {
|
||||||
|
|
||||||
|
private final MailProperties mailProperties;
|
||||||
|
|
||||||
|
private final JavaMailSender mailSender;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendMessage(Long toUserId, String messageTitle, String messageContent) {
|
||||||
|
// TODO 根据消息接收方的用户ID查询出消息接收方的邮件地址
|
||||||
|
String toEmail = "xxyopen@foxmail.com";
|
||||||
|
// 开始发送邮件
|
||||||
|
log.info("发送 HTML 邮件开始:{},{},{}", toEmail, messageTitle, messageContent);
|
||||||
|
// 使用 MimeMessage,MIME 协议
|
||||||
|
MimeMessage message = mailSender.createMimeMessage();
|
||||||
|
MimeMessageHelper helper;
|
||||||
|
// MimeMessageHelper 帮助我们设置更丰富的内容
|
||||||
|
try {
|
||||||
|
helper = new MimeMessageHelper(message, true);
|
||||||
|
helper.setFrom(new InternetAddress(mailProperties.username(), mailProperties.nickname(), "UTF-8"));
|
||||||
|
helper.setTo(toEmail);
|
||||||
|
helper.setSubject(messageTitle);
|
||||||
|
// 第二个参数 true 代表支持 html
|
||||||
|
helper.setText(messageContent, true);
|
||||||
|
mailSender.send(message);
|
||||||
|
log.info("发送 HTML 邮件 to {} 成功", toEmail);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 邮件发送失败不会重试
|
||||||
|
log.error("发送 HTML 邮件 to {} 失败", toEmail, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package io.github.xxyopen.novel.manager.message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象的消息发送器
|
||||||
|
* <p>
|
||||||
|
* 遵循松耦合的设计原则,所有的属性都使用构造函数注入,与 Spring 框架解藕
|
||||||
|
* <p>
|
||||||
|
* 所有的消息发送器既可以注册到 Spring 容器中,作为 Spring 的一个组件使用,也可以直接通过 new 对象的方式使用
|
||||||
|
* <p>
|
||||||
|
* 每种类型的消息发送时机可能都不一样,不同类型和发送时机的消息格式可能也不一样,所以由各个子类去拓展消息的格式
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/24
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMessageSender implements MessageSender {
|
||||||
|
|
||||||
|
private static final String PLACEHOLDER = "{}";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义消息发送的模版,子类不能修改此模版
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final void sendMessage(Long toUserId, Object... args) {
|
||||||
|
// 1.获取消息标题模版
|
||||||
|
String titleTemplate = getTitleTemplate();
|
||||||
|
// 2.获取消息内容模版
|
||||||
|
String contentTemplate = getContentTemplate();
|
||||||
|
// 3.解析消息模版,得到最终需要发送的消息标题
|
||||||
|
String title = resolveTitle(titleTemplate, args);
|
||||||
|
// 4.解析消息内容,得到最终需要发送的消息内容
|
||||||
|
String content = resolveContent(contentTemplate, args);
|
||||||
|
// 5.发送消息
|
||||||
|
sendMessage(toUserId, title, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息,具体发送到哪里由子类决定
|
||||||
|
*
|
||||||
|
* @param toUserId 消息接收方的用户ID
|
||||||
|
* @param messageTitle 消息标题
|
||||||
|
* @param messageContent 消息内容
|
||||||
|
*/
|
||||||
|
protected abstract void sendMessage(Long toUserId, String messageTitle, String messageContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息标题的模版,具体如何制定模版由子类决定
|
||||||
|
*
|
||||||
|
* @return 消息标题
|
||||||
|
*/
|
||||||
|
protected abstract String getTitleTemplate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息内容的模版,具体如何制定模版由子类决定
|
||||||
|
*
|
||||||
|
* @return 消息内容
|
||||||
|
*/
|
||||||
|
protected abstract String getContentTemplate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过给定的参数列表解析消息标题模版,默认固定标题,不需要解析,可以由子类来拓展它的功能
|
||||||
|
*
|
||||||
|
* @param titleTemplate 消息标题模版
|
||||||
|
* @param arguments 用来解析的参数列表
|
||||||
|
* @return 解析后的消息标题
|
||||||
|
*/
|
||||||
|
protected String resolveTitle(String titleTemplate, Object... arguments) {
|
||||||
|
return titleTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过给定的参数列表解析消息内容模版,默认实现是使用参数列表来替换消息内容模版中的占位符,可以由子类来拓展它的功能
|
||||||
|
* <p>
|
||||||
|
* 子类可以根据第一个/前几个参数去数据库中查询动态内容,然后重组参数列表
|
||||||
|
*
|
||||||
|
* @param contentTemplate 消息内容模版
|
||||||
|
* @param args 用来解析的参数列表
|
||||||
|
* @return 解析后的消息内容
|
||||||
|
*/
|
||||||
|
protected String resolveContent(String contentTemplate, Object... args) {
|
||||||
|
if (args.length > 0) {
|
||||||
|
StringBuilder formattedContent = new StringBuilder(contentTemplate);
|
||||||
|
for (Object arg : args) {
|
||||||
|
int start = formattedContent.indexOf(PLACEHOLDER);
|
||||||
|
formattedContent.replace(start, start + PLACEHOLDER.length(),
|
||||||
|
String.valueOf(arg));
|
||||||
|
}
|
||||||
|
return formattedContent.toString();
|
||||||
|
}
|
||||||
|
return contentTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package io.github.xxyopen.novel.manager.message;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象的系统通知发送者
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/24
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractSysNoticeSender extends AbstractMessageSender {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendMessage(Long toUserId, String messageTitle, String messageContent) {
|
||||||
|
// 生成消息的发送时间
|
||||||
|
LocalDateTime messageDateTime = LocalDateTime.now();
|
||||||
|
// TODO 在数据库系统通知表中插入一条记录
|
||||||
|
log.info("系统通知发送成功,{},{},{},{}", toUserId, messageDateTime.format(DateTimeFormatter.ISO_DATE_TIME),
|
||||||
|
messageTitle, messageContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package io.github.xxyopen.novel.manager.message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息发送器接口,用来发送各种消息
|
||||||
|
* <p>
|
||||||
|
* 消息按类型分系统通知、邮件、短信、小程序通知等,按发送时机分注册成功消息、充值成功消息、活动通知消息、账户封禁消息、小说下架消息等
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/25
|
||||||
|
*/
|
||||||
|
public interface MessageSender {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息,支持动态消息标题和动态消息内容
|
||||||
|
*
|
||||||
|
* @param toUserId 消息接收方的用户ID
|
||||||
|
* @param args 用来动态生成消息标题和消息内容的参数列表
|
||||||
|
*/
|
||||||
|
void sendMessage(Long toUserId, Object... args);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package io.github.xxyopen.novel.manager.message;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.config.MailProperties;
|
||||||
|
import io.github.xxyopen.novel.core.constant.MessageSenderTypeConsts;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册成功的邮件发送器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/24
|
||||||
|
*/
|
||||||
|
@Component(value = MessageSenderTypeConsts.REGISTER_MAIL_SENDER)
|
||||||
|
@EnableConfigurationProperties(MailProperties.class)
|
||||||
|
public class RegisterMailSender extends AbstractMailSender {
|
||||||
|
|
||||||
|
public RegisterMailSender(MailProperties mailProperties, JavaMailSender mailSender) {
|
||||||
|
super(mailProperties, mailSender);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTitleTemplate() {
|
||||||
|
return "欢迎来到小说精品屋";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getContentTemplate() {
|
||||||
|
return """
|
||||||
|
<div>
|
||||||
|
感谢你注册小说精品屋!你的账户现在处于活动状态。
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li> 你的账户电子邮件:{}
|
||||||
|
<li> 你的账户用户名:{}
|
||||||
|
</ul>
|
||||||
|
<div style="padding: 10px 0 50px 0; text-align: center;">
|
||||||
|
<a style="background: #0274be; color: #fff; padding: 12px 30px; text-decoration: none; border-radius: 3px; letter-spacing: 0.3px;" href="{}" target="_blank" rel="noopener">
|
||||||
|
登录我们的网站
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
如果你有任何问题,请通过 {} 与我们联系。
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String resolveContent(String content, Object... args) {
|
||||||
|
// TODO 去数据库/配置文件中查询网站配置
|
||||||
|
String websiteLink = "https://www.xxyopen.com";
|
||||||
|
String websiteEmail = "xxyopen@foxmail.com";
|
||||||
|
return super.resolveContent(content,
|
||||||
|
Stream.of(args, new Object[]{websiteLink, websiteEmail}).flatMap(Arrays::stream).toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package io.github.xxyopen.novel.manager.message;
|
||||||
|
|
||||||
|
import io.github.xxyopen.novel.core.constant.MessageSenderTypeConsts;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秒杀活动的系统通知发送器
|
||||||
|
*
|
||||||
|
* @author xiongxiaoyang
|
||||||
|
* @date 2023/3/24
|
||||||
|
*/
|
||||||
|
@Component(value = MessageSenderTypeConsts.SECKILL_SYS_NOTICE_SENDER)
|
||||||
|
public class SeckillSystemNoticeSender extends AbstractSysNoticeSender {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTitleTemplate() {
|
||||||
|
return "秒杀即将开始";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getContentTemplate() {
|
||||||
|
return "{}秒杀,{}即将开始,不要错过哦!点击 {} 前往。";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package io.github.xxyopen.novel.manager.mq;
|
package io.github.xxyopen.novel.manager.mq;
|
||||||
|
|
||||||
import io.github.xxyopen.novel.core.common.constant.CommonConsts;
|
|
||||||
import io.github.xxyopen.novel.core.constant.AmqpConsts;
|
import 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;
|
||||||
@ -22,14 +20,14 @@ public class AmqpMsgManager {
|
|||||||
|
|
||||||
private final AmqpTemplate amqpTemplate;
|
private final AmqpTemplate amqpTemplate;
|
||||||
|
|
||||||
@Value("${spring.amqp.enable}")
|
@Value("${spring.amqp.enabled:false}")
|
||||||
private String enableAmqp;
|
private boolean amqpEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送小说信息改变消息
|
* 发送小说信息改变消息
|
||||||
*/
|
*/
|
||||||
public void sendBookChangeMsg(Long bookId) {
|
public void sendBookChangeMsg(Long bookId) {
|
||||||
if (Objects.equals(enableAmqp, CommonConsts.TRUE)) {
|
if (amqpEnabled) {
|
||||||
sendAmqpMessage(amqpTemplate, AmqpConsts.BookChangeMq.EXCHANGE_NAME, null, bookId);
|
sendAmqpMessage(amqpTemplate, AmqpConsts.BookChangeMq.EXCHANGE_NAME, null, bookId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ 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;
|
||||||
|
import io.github.xxyopen.novel.dto.req.ChapterUpdateReqDto;
|
||||||
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.*;
|
||||||
|
|
||||||
@ -179,4 +180,38 @@ public interface BookService {
|
|||||||
* @return 章节分页列表数据
|
* @return 章节分页列表数据
|
||||||
*/
|
*/
|
||||||
RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(Long bookId, PageReqDto dto);
|
RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(Long bookId, PageReqDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询评论
|
||||||
|
*
|
||||||
|
* @param userId 会员ID
|
||||||
|
* @param pageReqDto 分页参数
|
||||||
|
* @return 评论分页列表数据
|
||||||
|
*/
|
||||||
|
RestResp<PageRespDto<UserCommentRespDto>> listComments(Long userId, PageReqDto pageReqDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节删除
|
||||||
|
*
|
||||||
|
* @param chapterId 章节ID
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
RestResp<Void> deleteBookChapter(Long chapterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节查询
|
||||||
|
*
|
||||||
|
* @param chapterId 章节ID
|
||||||
|
* @return 章节内容
|
||||||
|
*/
|
||||||
|
RestResp<ChapterContentRespDto> getBookChapter(Long chapterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小说章节更新
|
||||||
|
*
|
||||||
|
* @param chapterId 章节ID
|
||||||
|
* @param dto 更新内容
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
RestResp<Void> updateBookChapter(Long chapterId, ChapterUpdateReqDto dto);
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,7 @@ import io.github.xxyopen.novel.core.common.req.PageReqDto;
|
|||||||
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||||
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||||
import io.github.xxyopen.novel.dao.entity.BookChapter;
|
import io.github.xxyopen.novel.dao.entity.*;
|
||||||
import io.github.xxyopen.novel.dao.entity.BookComment;
|
|
||||||
import io.github.xxyopen.novel.dao.entity.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;
|
||||||
@ -23,39 +19,25 @@ import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
|||||||
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
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.ChapterUpdateReqDto;
|
||||||
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookCategoryRespDto;
|
import io.github.xxyopen.novel.dto.resp.*;
|
||||||
import io.github.xxyopen.novel.dto.resp.BookChapterAboutRespDto;
|
import io.github.xxyopen.novel.manager.cache.*;
|
||||||
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 org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小说模块 服务实现类
|
* 小说模块 服务实现类
|
||||||
@ -427,6 +409,141 @@ public class BookServiceImpl implements BookService {
|
|||||||
.build()).toList()));
|
.build()).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestResp<PageRespDto<UserCommentRespDto>> listComments(Long userId, PageReqDto pageReqDto) {
|
||||||
|
IPage<BookComment> page = new Page<>();
|
||||||
|
page.setCurrent(pageReqDto.getPageNum());
|
||||||
|
page.setSize(pageReqDto.getPageSize());
|
||||||
|
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, userId)
|
||||||
|
.orderByDesc(DatabaseConsts.CommonColumnEnum.UPDATE_TIME.getName());
|
||||||
|
IPage<BookComment> bookCommentPage = bookCommentMapper.selectPage(page, queryWrapper);
|
||||||
|
List<BookComment> comments = bookCommentPage.getRecords();
|
||||||
|
if (!CollectionUtils.isEmpty(comments)) {
|
||||||
|
List<Long> bookIds = comments.stream().map(BookComment::getBookId).toList();
|
||||||
|
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||||
|
bookInfoQueryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(), bookIds);
|
||||||
|
Map<Long, BookInfo> bookInfoMap = bookInfoMapper.selectList(bookInfoQueryWrapper).stream()
|
||||||
|
.collect(Collectors.toMap(BookInfo::getId, Function.identity()));
|
||||||
|
return RestResp.ok(PageRespDto.of(pageReqDto.getPageNum(), pageReqDto.getPageSize(), page.getTotal(),
|
||||||
|
comments.stream().map(v -> UserCommentRespDto.builder()
|
||||||
|
.commentContent(v.getCommentContent())
|
||||||
|
.commentBook(bookInfoMap.get(v.getBookId()).getBookName())
|
||||||
|
.commentBookPic(bookInfoMap.get(v.getBookId()).getPicUrl())
|
||||||
|
.commentTime(v.getCreateTime())
|
||||||
|
.build()).toList()));
|
||||||
|
|
||||||
|
}
|
||||||
|
return RestResp.ok(PageRespDto.of(pageReqDto.getPageNum(), pageReqDto.getPageSize(), page.getTotal(),
|
||||||
|
Collections.emptyList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public RestResp<Void> deleteBookChapter(Long chapterId) {
|
||||||
|
// 1.查询章节信息
|
||||||
|
BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
|
||||||
|
// 2.查询小说信息
|
||||||
|
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(chapter.getBookId());
|
||||||
|
// 3.删除章节信息
|
||||||
|
bookChapterMapper.deleteById(chapterId);
|
||||||
|
// 4.删除章节内容
|
||||||
|
QueryWrapper<BookContent> bookContentQueryWrapper = new QueryWrapper<>();
|
||||||
|
bookContentQueryWrapper.eq(DatabaseConsts.BookContentTable.COLUMN_CHAPTER_ID, chapterId);
|
||||||
|
bookContentMapper.delete(bookContentQueryWrapper);
|
||||||
|
// 5.更新小说信息
|
||||||
|
BookInfo newBookInfo = new BookInfo();
|
||||||
|
newBookInfo.setId(chapter.getBookId());
|
||||||
|
newBookInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
newBookInfo.setWordCount(bookInfo.getWordCount() - chapter.getChapterWordCount());
|
||||||
|
if (Objects.equals(bookInfo.getLastChapterId(), chapterId)) {
|
||||||
|
// 设置最新章节信息
|
||||||
|
QueryWrapper<BookChapter> bookChapterQueryWrapper = new QueryWrapper<>();
|
||||||
|
bookChapterQueryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, chapter.getBookId())
|
||||||
|
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||||
|
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||||
|
BookChapter bookChapter = bookChapterMapper.selectOne(bookChapterQueryWrapper);
|
||||||
|
Long lastChapterId = 0L;
|
||||||
|
String lastChapterName = "";
|
||||||
|
LocalDateTime lastChapterUpdateTime = null;
|
||||||
|
if (Objects.nonNull(bookChapter)) {
|
||||||
|
lastChapterId = bookChapter.getId();
|
||||||
|
lastChapterName = bookChapter.getChapterName();
|
||||||
|
lastChapterUpdateTime = bookChapter.getUpdateTime();
|
||||||
|
}
|
||||||
|
newBookInfo.setLastChapterId(lastChapterId);
|
||||||
|
newBookInfo.setLastChapterName(lastChapterName);
|
||||||
|
newBookInfo.setLastChapterUpdateTime(lastChapterUpdateTime);
|
||||||
|
}
|
||||||
|
bookInfoMapper.updateById(newBookInfo);
|
||||||
|
// 6.清理章节信息缓存
|
||||||
|
bookChapterCacheManager.evictBookChapterCache(chapterId);
|
||||||
|
// 7.清理章节内容缓存
|
||||||
|
bookContentCacheManager.evictBookContentCache(chapterId);
|
||||||
|
// 8.清理小说信息缓存
|
||||||
|
bookInfoCacheManager.evictBookInfoCache(chapter.getBookId());
|
||||||
|
// 9.发送小说信息更新的 MQ 消息
|
||||||
|
amqpMsgManager.sendBookChangeMsg(chapter.getBookId());
|
||||||
|
return RestResp.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestResp<ChapterContentRespDto> getBookChapter(Long chapterId) {
|
||||||
|
BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
|
||||||
|
String bookContent = bookContentCacheManager.getBookContent(chapterId);
|
||||||
|
return RestResp.ok(
|
||||||
|
ChapterContentRespDto.builder()
|
||||||
|
.chapterName(chapter.getChapterName())
|
||||||
|
.chapterContent(bookContent)
|
||||||
|
.isVip(chapter.getIsVip())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public RestResp<Void> updateBookChapter(Long chapterId, ChapterUpdateReqDto dto) {
|
||||||
|
// 1.查询章节信息
|
||||||
|
BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
|
||||||
|
// 2.查询小说信息
|
||||||
|
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(chapter.getBookId());
|
||||||
|
// 3.更新章节信息
|
||||||
|
BookChapter newChapter = new BookChapter();
|
||||||
|
newChapter.setId(chapterId);
|
||||||
|
newChapter.setChapterName(dto.getChapterName());
|
||||||
|
newChapter.setWordCount(dto.getChapterContent().length());
|
||||||
|
newChapter.setIsVip(dto.getIsVip());
|
||||||
|
newChapter.setUpdateTime(LocalDateTime.now());
|
||||||
|
bookChapterMapper.updateById(newChapter);
|
||||||
|
// 4.更新章节内容
|
||||||
|
BookContent newContent = new BookContent();
|
||||||
|
newContent.setContent(dto.getChapterContent());
|
||||||
|
newContent.setUpdateTime(LocalDateTime.now());
|
||||||
|
QueryWrapper<BookContent> bookContentQueryWrapper = new QueryWrapper<>();
|
||||||
|
bookContentQueryWrapper.eq(DatabaseConsts.BookContentTable.COLUMN_CHAPTER_ID, chapterId);
|
||||||
|
bookContentMapper.update(newContent, bookContentQueryWrapper);
|
||||||
|
// 5.更新小说信息
|
||||||
|
BookInfo newBookInfo = new BookInfo();
|
||||||
|
newBookInfo.setId(chapter.getBookId());
|
||||||
|
newBookInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
newBookInfo.setWordCount(
|
||||||
|
bookInfo.getWordCount() - chapter.getChapterWordCount() + dto.getChapterContent().length());
|
||||||
|
if (Objects.equals(bookInfo.getLastChapterId(), chapterId)) {
|
||||||
|
// 更新最新章节信息
|
||||||
|
newBookInfo.setLastChapterName(dto.getChapterName());
|
||||||
|
newBookInfo.setLastChapterUpdateTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
bookInfoMapper.updateById(newBookInfo);
|
||||||
|
// 6.清理章节信息缓存
|
||||||
|
bookChapterCacheManager.evictBookChapterCache(chapterId);
|
||||||
|
// 7.清理章节内容缓存
|
||||||
|
bookContentCacheManager.evictBookContentCache(chapterId);
|
||||||
|
// 8.清理小说信息缓存
|
||||||
|
bookInfoCacheManager.evictBookInfoCache(chapter.getBookId());
|
||||||
|
// 9.发送小说信息更新的 MQ 消息
|
||||||
|
amqpMsgManager.sendBookChangeMsg(chapter.getBookId());
|
||||||
|
return RestResp.ok();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RestResp<BookContentAboutRespDto> getBookContentAbout(Long chapterId) {
|
public RestResp<BookContentAboutRespDto> getBookContentAbout(Long chapterId) {
|
||||||
log.debug("userId:{}", UserHolder.getUserId());
|
log.debug("userId:{}", UserHolder.getUserId());
|
||||||
|
@ -8,19 +8,20 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据库搜索 服务实现类
|
* 数据库搜索 服务实现类
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/23
|
* @date 2022/5/23
|
||||||
*/
|
*/
|
||||||
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "false")
|
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enabled", havingValue = "false")
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -19,22 +19,23 @@ 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 搜索 服务实现类
|
||||||
*
|
*
|
||||||
* @author xiongxiaoyang
|
* @author xiongxiaoyang
|
||||||
* @date 2022/5/23
|
* @date 2022/5/23
|
||||||
*/
|
*/
|
||||||
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true")
|
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enabled", havingValue = "true")
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package org.springframework.core;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 兼容 mybatis-plus 3.5.1
|
|
||||||
* mybatis-plus 的 MybatisSqlSessionFactoryBean 中使用到了这个异常
|
|
||||||
* Spring 6 开始移除了该异常
|
|
||||||
*
|
|
||||||
* @author xiongxiaoyang
|
|
||||||
* @date 2022/5/12
|
|
||||||
*/
|
|
||||||
public class NestedIOException extends IOException {
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "spring.elasticsearch.enabled",
|
||||||
|
"description": "Whether enable elasticsearch or not.",
|
||||||
|
"type": "java.lang.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultValue": false,
|
||||||
|
"name": "spring.amqp.enabled",
|
||||||
|
"description": "Whether enable amqp or not.",
|
||||||
|
"type": "java.lang.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xxl.job.enabled",
|
||||||
|
"description": "Whether enable xxl-job or not.",
|
||||||
|
"type": "java.lang.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "novel.jwt.secret",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "JWT 密钥."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "novel.xss.enabled",
|
||||||
|
"type": "java.lang.Boolean",
|
||||||
|
"description": "是否开启 XSS 过滤."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "novel.xss.excludes",
|
||||||
|
"type": "java.util.List<java.lang.String>",
|
||||||
|
"description": "XSS 过滤排除链接."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "novel.file.upload.path",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "上传文件目录."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "novel.cors.allow-origins",
|
||||||
|
"type": "java.util.List<java.lang.String>",
|
||||||
|
"description": "允许跨域的域名."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xxl.job.admin.addresses",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "调度中心部署根地址."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xxl.job.executor.appname",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "执行器 AppName."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xxl.job.executor.logpath",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "执行器运行日志文件存储磁盘路径."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xxl.job.accessToken",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "xxl-job accessToken."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.elasticsearch.ssl.verification-mode",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "设置 ssl 的认证模式,如果该配置项为 none ,说明不需要认证,信任所有的 ssl 证书."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultValue": true,
|
||||||
|
"name": "spring.shardingsphere.enabled",
|
||||||
|
"description": "Whether enable shardingsphere or not.",
|
||||||
|
"type": "java.lang.Boolean"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,24 +1,59 @@
|
|||||||
|
#--------------------------通用配置-------------------------
|
||||||
spring:
|
spring:
|
||||||
|
application:
|
||||||
|
# 应用名
|
||||||
|
name: novel
|
||||||
profiles:
|
profiles:
|
||||||
|
# 激活特定配置
|
||||||
active: dev
|
active: dev
|
||||||
# 将所有数字转为 String 类型返回,避免前端数据精度丢失的问题
|
|
||||||
jackson:
|
jackson:
|
||||||
generator:
|
generator:
|
||||||
|
# JSON 序列化时,将所有 Number 类型的属性都转为 String 类型返回,避免前端数据精度丢失的问题。
|
||||||
|
# 由于 Javascript 标准规定所有数字处理都应使用 64 位 IEEE 754 浮点值完成,
|
||||||
|
# 结果是某些 64 位整数值无法准确表示(尾数只有 51 位宽)
|
||||||
write-numbers-as-strings: true
|
write-numbers-as-strings: true
|
||||||
servlet:
|
servlet:
|
||||||
# 上传文件最大大小
|
|
||||||
multipart:
|
multipart:
|
||||||
|
# 上传文件最大大小
|
||||||
max-file-size: 5MB
|
max-file-size: 5MB
|
||||||
application:
|
# 启用虚拟线程
|
||||||
name: novel
|
threads:
|
||||||
|
virtual:
|
||||||
|
enabled: true
|
||||||
|
# 即使所有的用户线程(包括虚拟线程)都是守护线程的情况下,JVM 也不会立即退出,
|
||||||
|
# Spring Boot 官方建议在开启虚拟线程时设置该属性
|
||||||
|
main:
|
||||||
|
keep-alive: true
|
||||||
|
|
||||||
|
flyway:
|
||||||
|
# 是否开启 Flyway
|
||||||
|
enabled: false
|
||||||
|
# initialize the schema history table
|
||||||
|
baseline-on-migrate: true
|
||||||
|
# url: jdbc:mysql://localhost:3306/novel?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
|
# user: root
|
||||||
|
# password: test123456
|
||||||
|
|
||||||
server:
|
server:
|
||||||
|
# 端口号
|
||||||
port: 8888
|
port: 8888
|
||||||
|
|
||||||
---
|
--- #--------------------- Spring AI 配置----------------------
|
||||||
|
spring:
|
||||||
|
ai:
|
||||||
|
openai:
|
||||||
|
api-key: sk-rrrupturhdofbiqzjutduuiceecpvfqlnvmgcyiaipbdikoi
|
||||||
|
base-url: https://api.siliconflow.cn
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- #---------------------数据库配置---------------------------
|
||||||
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?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
username: root
|
username: root
|
||||||
password: test123456
|
password: test123456
|
||||||
# ShardingSphere-JDBC 配置
|
# ShardingSphere-JDBC 配置
|
||||||
@ -27,79 +62,32 @@ spring:
|
|||||||
shardingsphere:
|
shardingsphere:
|
||||||
# 是否开启分库分表
|
# 是否开启分库分表
|
||||||
enabled: false
|
enabled: false
|
||||||
props:
|
|
||||||
# 是否在日志中打印 SQL
|
|
||||||
sql-show: true
|
|
||||||
# 模式配置
|
|
||||||
mode:
|
|
||||||
# 单机模式
|
|
||||||
type: Standalone
|
|
||||||
repository:
|
|
||||||
# 文件持久化
|
|
||||||
type: File
|
|
||||||
props:
|
|
||||||
# 元数据存储路径
|
|
||||||
path: .shardingsphere
|
|
||||||
# 使用本地配置覆盖持久化配置
|
|
||||||
overwrite: true
|
|
||||||
# 数据源配置
|
|
||||||
datasource:
|
|
||||||
names: ds_0
|
|
||||||
ds_0:
|
|
||||||
type: com.zaxxer.hikari.HikariDataSource
|
|
||||||
driverClassName: com.mysql.cj.jdbc.Driver
|
|
||||||
jdbcUrl: jdbc:mysql://localhost:3306/novel_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
|
||||||
username: root
|
|
||||||
password: test123456
|
|
||||||
# 规则配置
|
|
||||||
rules:
|
|
||||||
# 数据分片
|
|
||||||
sharding:
|
|
||||||
tables:
|
|
||||||
# book_content 表
|
|
||||||
book_content:
|
|
||||||
# 数据节点
|
|
||||||
actual-data-nodes: ds_$->{0}.book_content$->{0..9}
|
|
||||||
# 分表策略
|
|
||||||
table-strategy:
|
|
||||||
standard:
|
|
||||||
# 分片列名称
|
|
||||||
sharding-column: chapter_id
|
|
||||||
# 分片算法名称
|
|
||||||
sharding-algorithm-name: bookContentSharding
|
|
||||||
sharding-algorithms:
|
|
||||||
bookContentSharding:
|
|
||||||
# 行表达式分片算法,使用 Groovy 的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持
|
|
||||||
type: INLINE
|
|
||||||
props:
|
|
||||||
# 分片算法的行表达式
|
|
||||||
algorithm-expression: book_content$->{chapter_id % 10}
|
|
||||||
|
|
||||||
config:
|
--- #---------------------中间件配置---------------------------
|
||||||
activate:
|
|
||||||
on-profile: dev
|
|
||||||
|
|
||||||
---
|
|
||||||
spring:
|
spring:
|
||||||
# Redis 配置
|
data:
|
||||||
redis:
|
# Redis 配置
|
||||||
host: 127.0.0.1
|
redis:
|
||||||
port: 6379
|
host: 127.0.0.1
|
||||||
password: 123456
|
port: 6379
|
||||||
config:
|
password: test123456
|
||||||
activate:
|
|
||||||
on-profile: dev
|
|
||||||
# Elasticsearch 配置
|
# Elasticsearch 配置
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
# 是否开启 elasticsearch 搜索引擎功能:true-开启 false-不开启
|
# 是否开启 Elasticsearch 搜索引擎功能:true-开启 false-不开启
|
||||||
enable: false
|
enabled: false
|
||||||
uris:
|
uris:
|
||||||
- https://my-deployment-ce7ca3.es.us-central1.gcp.cloud.es.io:9243
|
- https://my-deployment-ce7ca3.es.us-central1.gcp.cloud.es.io:9243
|
||||||
username: elastic
|
username: elastic
|
||||||
password: qTjgYVKSuExX6tWAsDuvuvwl
|
password: qTjgYVKSuExX6tWAsDuvuvwl
|
||||||
|
# 设置 ssl 的认证模式,如果该配置项为 none ,说明不需要认证,信任所有的 ssl 证书。
|
||||||
|
# ssl:
|
||||||
|
# verification-mode: none
|
||||||
|
|
||||||
|
# Spring AMQP 配置
|
||||||
amqp:
|
amqp:
|
||||||
# 是否开启 Spring AMQP:true-开启 false-不开启
|
# 是否开启 Spring AMQP:true-开启 false-不开启
|
||||||
enable: false
|
enabled: false
|
||||||
# RabbitMQ 配置
|
# RabbitMQ 配置
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
addresses: "amqp://guest:guest@47.106.243.172"
|
addresses: "amqp://guest:guest@47.106.243.172"
|
||||||
@ -117,7 +105,7 @@ spring:
|
|||||||
xxl:
|
xxl:
|
||||||
job:
|
job:
|
||||||
# 是否开启 XXL-JOB:true-开启 false-不开启
|
# 是否开启 XXL-JOB:true-开启 false-不开启
|
||||||
enable: false
|
enabled: false
|
||||||
admin:
|
admin:
|
||||||
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
|
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
|
||||||
addresses: http://127.0.0.1:8080/xxl-job-admin
|
addresses: http://127.0.0.1:8080/xxl-job-admin
|
||||||
@ -129,13 +117,9 @@ xxl:
|
|||||||
### xxl-job, access token
|
### xxl-job, access token
|
||||||
accessToken: 123
|
accessToken: 123
|
||||||
|
|
||||||
|
--- #----------------------安全配置----------------------------
|
||||||
---
|
|
||||||
spring:
|
spring:
|
||||||
config:
|
# Spring Boot 应用管理和监控
|
||||||
activate:
|
|
||||||
on-profile: dev
|
|
||||||
# Spring Boot 应用管理和监控
|
|
||||||
boot:
|
boot:
|
||||||
admin:
|
admin:
|
||||||
client:
|
client:
|
||||||
@ -167,7 +151,6 @@ management:
|
|||||||
exposure:
|
exposure:
|
||||||
# 公开所有的 Web 端点
|
# 公开所有的 Web 端点
|
||||||
include: "*"
|
include: "*"
|
||||||
|
|
||||||
# 端点启用配置
|
# 端点启用配置
|
||||||
endpoint:
|
endpoint:
|
||||||
logfile:
|
logfile:
|
||||||
@ -175,7 +158,6 @@ management:
|
|||||||
enabled: true
|
enabled: true
|
||||||
# 外部日志文件路径
|
# 外部日志文件路径
|
||||||
external-file: logs/novel.log
|
external-file: logs/novel.log
|
||||||
|
|
||||||
info:
|
info:
|
||||||
env:
|
env:
|
||||||
# 公开所有以 info. 开头的环境属性
|
# 公开所有以 info. 开头的环境属性
|
||||||
@ -187,16 +169,55 @@ management:
|
|||||||
elasticsearch:
|
elasticsearch:
|
||||||
# 关闭 elasticsearch 的健康检查
|
# 关闭 elasticsearch 的健康检查
|
||||||
enabled: false
|
enabled: false
|
||||||
|
mail:
|
||||||
|
# 关闭 mail 的健康检查
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
--- #--------------------接口文档配置---------------------------
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
--- #----------------------邮箱配置-----------------------------
|
||||||
---
|
#邮箱服务器
|
||||||
spring:
|
spring:
|
||||||
config:
|
mail:
|
||||||
activate:
|
host: smtp.163.com
|
||||||
on-profile: dev
|
#发件人昵称
|
||||||
|
nickname: xxyopen
|
||||||
|
#邮箱账户
|
||||||
|
username: xxx@163.com
|
||||||
|
#邮箱第三方授权码
|
||||||
|
password: xxx
|
||||||
|
#编码类型
|
||||||
|
default-encoding: UTF-8
|
||||||
|
port: 465
|
||||||
|
properties:
|
||||||
|
mail:
|
||||||
|
smtp:
|
||||||
|
auth: true
|
||||||
|
starttls:
|
||||||
|
enable: true
|
||||||
|
required: rue
|
||||||
|
socketFactory:
|
||||||
|
port: 465
|
||||||
|
class: javax.net.ssl.SSLSocketFactory
|
||||||
|
fallback: false
|
||||||
|
|
||||||
# 项目配置
|
--- #----------------------Logbook配置-----------------------------
|
||||||
|
logbook:
|
||||||
|
format:
|
||||||
|
# 输出格式
|
||||||
|
style: http
|
||||||
|
obfuscate:
|
||||||
|
headers:
|
||||||
|
# 隐藏 Authorization 头信息
|
||||||
|
- Authorization
|
||||||
|
parameters:
|
||||||
|
# 隐藏密码参数
|
||||||
|
- password
|
||||||
|
|
||||||
|
--- #---------------------自定义配置----------------------------
|
||||||
novel:
|
novel:
|
||||||
# 跨域配置
|
# 跨域配置
|
||||||
cors:
|
cors:
|
||||||
@ -204,7 +225,7 @@ novel:
|
|||||||
allow-origins:
|
allow-origins:
|
||||||
- http://localhost:1024
|
- http://localhost:1024
|
||||||
- http://localhost:8080
|
- http://localhost:8080
|
||||||
# JWT密钥
|
# JWT 密钥
|
||||||
jwt:
|
jwt:
|
||||||
secret: E66559580A1ADF48CDD928516062F12E
|
secret: E66559580A1ADF48CDD928516062F12E
|
||||||
# XSS 过滤配置
|
# XSS 过滤配置
|
||||||
@ -221,4 +242,37 @@ novel:
|
|||||||
path: /Users/xiongxiaoyang/upload
|
path: /Users/xiongxiaoyang/upload
|
||||||
|
|
||||||
|
|
||||||
|
--- #------------------- dev 特定配置---------------------------
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: dev
|
||||||
|
# 开启 SpringDoc 接口文档
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
# /env 端点显示属性值
|
||||||
|
management:
|
||||||
|
endpoint:
|
||||||
|
env:
|
||||||
|
show-values: when_authorized
|
||||||
|
|
||||||
|
--- #------------------- test 特定配置--------------------------
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: test
|
||||||
|
|
||||||
|
--- #-------------------- prod 特定配置-------------------------
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: prod
|
||||||
|
data:
|
||||||
|
# Redis 配置
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
password:
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,6 +62,10 @@
|
|||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
<logger name="org.zalando.logbook" level="TRACE" additivity="false">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</logger>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
<springProfile name="prod">
|
<springProfile name="prod">
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
singleServerConfig:
|
|
||||||
address: "redis://127.0.0.1:6379"
|
|
||||||
password: 123456
|
|
55
src/main/resources/shardingsphere-jdbc.yml
Normal file
55
src/main/resources/shardingsphere-jdbc.yml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
mode:
|
||||||
|
# 单机模式
|
||||||
|
type: Standalone
|
||||||
|
# 元数据持久化
|
||||||
|
repository:
|
||||||
|
# 数据库持久化
|
||||||
|
type: JDBC
|
||||||
|
props:
|
||||||
|
# 元数据存储类型
|
||||||
|
provider: H2
|
||||||
|
jdbc_url: jdbc:h2:./.h2/shardingsphere
|
||||||
|
|
||||||
|
# 数据源配置
|
||||||
|
dataSources:
|
||||||
|
ds_1:
|
||||||
|
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
|
||||||
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
|
jdbcUrl: jdbc:mysql://localhost:3306/novel?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
|
username: root
|
||||||
|
password: test123456
|
||||||
|
# 配置其他数据源
|
||||||
|
|
||||||
|
# 规则配置
|
||||||
|
rules:
|
||||||
|
# 配置单表规则
|
||||||
|
- !SINGLE
|
||||||
|
tables:
|
||||||
|
- "*.*"
|
||||||
|
# 配置分片规则
|
||||||
|
- !SHARDING
|
||||||
|
tables: # 数据分片规则配置
|
||||||
|
book_content:
|
||||||
|
# 分库策略,缺省表示使用默认分库策略
|
||||||
|
actualDataNodes: ds_${1}.book_content${0..9}
|
||||||
|
# 分表策略
|
||||||
|
tableStrategy:
|
||||||
|
standard:
|
||||||
|
# 分片列名称
|
||||||
|
shardingColumn: chapter_id
|
||||||
|
# 分片算法名称
|
||||||
|
shardingAlgorithmName: bookContentSharding
|
||||||
|
|
||||||
|
shardingAlgorithms:
|
||||||
|
bookContentSharding:
|
||||||
|
# 行表达式分片算法,使用 Groovy 的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持
|
||||||
|
type: INLINE
|
||||||
|
props:
|
||||||
|
# 分片算法的行表达式
|
||||||
|
algorithm-expression: book_content${chapter_id % 10}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
props:
|
||||||
|
# 是否在日志中打印 SQL
|
||||||
|
sql-show: true
|
Reference in New Issue
Block a user