25 Commits

Author SHA1 Message Date
e800836045 build(deps): bump com.xuxueli:xxl-job-core from 2.3.1 to 2.4.2
---
updated-dependencies:
- dependency-name: com.xuxueli:xxl-job-core
  dependency-version: 2.4.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-18 15:40:01 +00:00
8de6a865cb build(AI): Spring AI 升级到 1.0.0 2025-07-18 22:50:42 +08:00
df0c6b84e2 feat: 添加基于 Spring AOP 的排序字段安全校验模块
- 使用 Spring AOP 创建切面类 SortOrderValidationAspect
- 引入 @ValidateSortOrder 注解标记需处理的参数
- 集成 SortWhitelistUtil 白名单工具类
- 支持多种参数类型的排序字段统一校验
- 提升系统安全性,防止非法排序字段注入
2025-07-17 22:36:23 +08:00
a23f4b202e feat: 处理404异常 2025-04-02 08:19:20 +08:00
cd3a7206a9 perf: instanceof 智能转型 2025-03-19 09:58:04 +08:00
ab166a392a fix: 兼容非HikariDataSource数据源 2025-03-19 08:00:50 +08:00
9d8709ed2d perf: 提前创建数据库连接池
Spring Boot 新版本默认会在第一次请求数据库时创建连接池
2025-03-19 07:53:03 +08:00
60488258f5 perf: 提高接口第一次访问速度
Spring Boot 新版本默认会在第一次访问数据库时才创建连接池
2025-03-19 00:14:00 +08:00
b7bb98db16 build: 修改 Spring AI 版本 2025-03-18 22:34:04 +08:00
e54b656799 修改版本号 2025-02-20 16:43:01 +08:00
dccce83d1c v3.5.0 发布 2025-02-20 16:40:05 +08:00
b2c0340048 文档/日志/优化 2025-02-20 12:19:37 +08:00
9f71aa4a59 feat: 集成 Spring AI 框架,实现基础的 AI 写作功能 2025-02-19 23:44:29 +08:00
295a9096b5 build: 升级 ShardingSphere-JDBC 到 5.5.1
支持 Spring Boot 3.3.0
2025-01-18 00:31:24 +08:00
c46864bbb6 feat: 增加 HTTP 请求和响应的日志记录 2024-09-13 22:48:00 +08:00
d63be23aca perf: /env 端点在 dev 环境下显示属性值 2024-07-17 18:02:01 +08:00
8da6f8263c fix: 初始化 Flyway 历史表
org.flywaydb.core.api.FlywayException: Found non-empty schema(s)
`novel_test` but no schema history table. Use baseline() or set
baselineOnMigrate to true to initialize the schema history table.
2024-07-15 18:07:29 +08:00
b4ce4dd35d perf: 优化SQL脚本文件管理 2024-06-28 07:03:14 +08:00
63760c8e90 feat: 集成 Flyway 2024-06-28 01:46:16 +08:00
e7005b9008 build: 更新java版本到21 2024-06-02 14:02:48 +08:00
876d9b8cbe build: 使用默认的 Spring Boot 内嵌 Web 容器,实现虚拟线程处理请求
Spring Boot v3.3.0-M3 删除了对 Undertow 的虚拟线程支持,因为它会泄漏内存
2024-06-02 12:15:05 +08:00
9da5064a9e perf: 启用虚拟线程
需要在 Java 21 上运行
2024-06-02 08:47:57 +08:00
03b3ca1d83 build: 修改版本号 2024-06-02 08:24:28 +08:00
b0d2adebf6 Merge branch '3.4.x' 2024-06-02 08:18:52 +08:00
e09aad2415 build: 升级spring-boot3至3.3.0 2024-06-01 21:12:55 +08:00
22 changed files with 637 additions and 136 deletions

View File

@ -1,14 +1,10 @@
[![index]( https://youdoc.github.io/img/tencent.jpg )]( https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=736e609d66e0ac4e57813316cec6fd0b&from=console )
<p align="center">
<a href='https://docs.oracle.com/en/java/javase/17'><img alt="Java 17" src="https://img.shields.io/badge/Java%2017-%234479A1.svg?logo=data:image/png;base64,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"></a>
<a href='https://docs.spring.io/spring-boot/docs/3.0.0-SNAPSHOT/reference/html'><img alt="Spring Boot 3" src="https://img.shields.io/badge/Spring%20Boot%203-%23000000.svg?logo=springboot"></a>
<a href='https://staging-cn.vuejs.org'><img alt="Vue 3" src="https://img.shields.io/badge/Vue%203%20-%232b3847.svg?logo=vue.js"></a><br/>
<a href='https://github.com/201206030/novel'><img alt="Github stars" src="https://img.shields.io/github/stars/201206030/novel?logo=github"></a>
<a href='https://github.com/201206030/novel'><img alt="Github forks" src="https://img.shields.io/github/forks/201206030/novel?logo=github"></a>
<a href='https://gitee.com/novel_dev_team/novel'><img alt="Gitee stars" src="https://gitee.com/novel_dev_team/novel/badge/star.svg?theme=gitee"></a>
<a href='https://gitee.com/novel_dev_team/novel'><img alt="Gitee forks" src="https://gitee.com/novel_dev_team/novel/badge/fork.svg?theme=gitee"></a>
<a href="https://github.com/201206030/novel"><img src="https://visitor-badge.glitch.me/badge?page_id=201206030.novel" alt="visitors"></a>
</p>
## 项目简介
@ -32,37 +28,38 @@ novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开
- Elasticsearch 8.2.0(可选)
- RabbitMQ 3.10.2(可选)
- XXL-JOB 2.3.1(可选)
- JDK 17
- JDK 21
- Maven 3.8
- IntelliJ IDEA 2021.3(可选)
- IntelliJ IDEA可选
- Node 16.14
**注Elasticsearch、RabbitMQ 和 XXL-JOB 默认关闭,可通过 application.yml 配置文件中相应的`enable`配置属性开启。**
## 后端技术选型
| 技术 | 版本 | 说明 | 官网 | 学习 |
|---------------------|:--------------:|---------------------| --------------------------------------- |:-----------------------------------------------------------------------------------------------------------------------------:|
| Spring Boot | 3.0.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) |
| MyBatis-Plus | 3.5.3 | MyBatis 增强工具 | [进入](https://baomidou.com/) | [进入](https://baomidou.com/pages/24112f/) |
| JJWT | 0.11.5 | JWT 登录支持 | [进入](https://github.com/jwtk/jjwt) | - |
| Lombok | 1.18.24 | 简化对象封装工具 | [进入](https://github.com/projectlombok/lombok) | [进入](https://projectlombok.org/features/all) |
| Caffeine | 3.1.0 | 本地缓存支持 | [进入](https://github.com/ben-manes/caffeine) | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
| Redis | 7.0 | 分布式缓存支持 | [进入](https://redis.io) | [进入](https://redis.io/docs) |
| Redisson | 3.17.4 | 分布式锁实现 | [进入](https://github.com/redisson/redisson) | [进入](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
| MySQL | 8.0 | 数据库服务 | [进入](https://www.mysql.com) | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
| ShardingSphere-JDBC | 5.1.1 | 数据库分库分表支持 | [进入](https://shardingsphere.apache.org) | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
| Elasticsearch | 8.2.0 | 搜索引擎服务 | [进入](https://www.elastic.co) | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
| RabbitMQ | 3.10.2 | 开源消息中间件 | [进入](https://www.rabbitmq.com) | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
| XXL-JOB | 2.3.1 | 分布式任务调度平台 | [进入](https://www.xuxueli.com/xxl-job) | [进入](https://www.xuxueli.com/xxl-job) |
| Sentinel | 1.8.4 | 流量控制组件 | [进入](https://github.com/alibaba/Sentinel) | [进入](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5) |
| Springdoc-openapi | 2.0.0 | Swagger 3 接口文档自动生成 | [进入](https://github.com/springdoc/springdoc-openapi) | [进入](https://springdoc.org/) |
| Spring Boot Admin | 3.0.0-M1 | 应用管理和监控 | [进入](https://github.com/codecentric/spring-boot-admin) | [进入](https://codecentric.github.io/spring-boot-admin/3.0.0-M1) |
| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | [进入](https://undertow.io) | [进入](https://undertow.io/documentation.html) |
| Docker | - | 应用容器引擎 | [进入](https://www.docker.com/) | - |
| Jenkins | - | 自动化部署工具 | [进入](https://github.com/jenkinsci/jenkins) | - |
| Sonarqube | - | 代码质量控制 | [进入](https://www.sonarqube.org/) | - |
| 技术 | 版本 | 说明 | 官网 | 学习 |
|---------------------|:------------:|-------------------------| ------------------------------------ |:------------------------------------------------------------------------------------------------------------------------:|
| Spring Boot | 3.3.0 | 容器 + MVC 框架 | [进入](https://spring.io/projects/spring-boot) | [进入](https://docs.spring.io/spring-boot/docs/3.0.0/reference/html) |
| Spring AI | 1.0.0-M6 | Spring 官方 AI 框架 | [进入](https://spring.io/projects/spring-ai) | [进入](https://docs.spring.io/spring-ai/reference/) |
| MyBatis | 3.5.9 | ORM 框架 | [进入](http://www.mybatis.org) | [进入](https://mybatis.org/mybatis-3/zh/index.html) |
| MyBatis-Plus | 3.5.3 | MyBatis 增强工具 | [进入](https://baomidou.com/) | [进入](https://baomidou.com/pages/24112f/) |
| JJWT | 0.11.5 | JWT 登录支持 | [进入](https://github.com/jwtk/jjwt) | - |
| Lombok | 1.18.24 | 简化对象封装工具 | [进入](https://github.com/projectlombok/lombok) | [进入](https://projectlombok.org/features/all) |
| Caffeine | 3.1.0 | 本地缓存支持 | [进入](https://github.com/ben-manes/caffeine) | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
| Redis | 7.0 | 分布式缓存支持 | [进入](https://redis.io) | [进入](https://redis.io/docs) |
| Redisson | 3.17.4 | 分布式锁实现 | [进入](https://github.com/redisson/redisson) | [进入](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
| MySQL | 8.0 | 数据库服务 | [进入](https://www.mysql.com) | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
| ShardingSphere-JDBC | 5.5.1 | 数据库分库分表支持 | [进入](https://shardingsphere.apache.org) | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
| Elasticsearch | 8.2.0 | 搜索引擎服务 | [进入](https://www.elastic.co) | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
| RabbitMQ | 3.10.2 | 开源消息中间件 | [进入](https://www.rabbitmq.com) | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
| XXL-JOB | 2.3.1 | 分布式任务调度平台 | [进入](https://www.xuxueli.com/xxl-job) | [进入](https://www.xuxueli.com/xxl-job) |
| Sentinel | 1.8.4 | 流量控制组件 | [进入](https://github.com/alibaba/Sentinel) | [进入](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5) |
| Springdoc-openapi | 2.0.0 | Swagger 3 接口文档自动生成 | [进入](https://github.com/springdoc/springdoc-openapi) | [进入](https://springdoc.org/) |
| Spring Boot Admin | 3.0.0-M1 | 应用管理和监控 | [进入](https://github.com/codecentric/spring-boot-admin) | [进入](https://codecentric.github.io/spring-boot-admin/3.0.0-M1) |
| Tomcat | 10.1.24 | Spring Boot 默认内嵌 Web 容器 | [进入](https://tomcat.apache.org) | [进入](https://tomcat.apache.org/tomcat-10.1-doc/index.html) |
| Docker | - | 应用容器引擎 | [进入](https://www.docker.com/) | - |
| Jenkins | - | 自动化部署工具 | [进入](https://github.com/jenkinsci/jenkins) | - |
| Sonarqube | - | 代码质量控制 | [进入](https://www.sonarqube.org/) | - |
**注:更多热门新技术待集成。**

View File

@ -1,6 +1,4 @@
1. 初始状态下MySQL 只需要执行 `novel.sql` 文件即可正常运行本系统
2. 代码更新后再执行以日期命名的增量 SQL 文件
3. 只有开启 XXL-JOB 的功能,才需要执行 `xxl-job.sql` 和以 xxl-job 开头日期结尾的增量 SQL 文件
4. 只有开启 ShardingSphere-JDBC 的功能,才需要执行 `shardingsphere-jdbc.sql` 和以 shardingsphere-jdbc 开头日期结尾的增量 SQL
文件
2. 只有开启 XXL-JOB 的功能,才需要执行 `xxl-job.sql` 文件
3. 只有开启 ShardingSphere-JDBC 的功能,才需要执行 `shardingsphere-jdbc.sql` 文件

75
pom.xml
View File

@ -6,51 +6,39 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<version>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.github.xxyopen</groupId>
<artifactId>novel</artifactId>
<version>3.4.1</version>
<version>3.5.1-SNAPSHOT</version>
<name>novel</name>
<description>Spring Boot 3 + Vue 3 构建的前后端分离小说系统</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.3</mybatis-plus.version>
<java.version>21</java.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.1</mybatis-plus-generator.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.4.2</xxl-job.version>
<sentinel.version>1.8.4</sentinel.version>
<shardingsphere-jdbc.version>5.2.1</shardingsphere-jdbc.version>
<shardingsphere-jdbc.version>5.5.1</shardingsphere-jdbc.version>
<redisson.version>3.19.1</redisson.version>
<spring-boot-admin.version>3.0.0-M1</spring-boot-admin.version>
<springdoc-openapi.version>2.0.0</springdoc-openapi.version>
<springdoc-openapi.version>2.5.0</springdoc-openapi.version>
<logbook.version>3.9.0</logbook.version>
<spring-ai.version>1.0.0</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<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>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
@ -107,15 +95,9 @@
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- elasticsearch 相关 -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- MQ 相关 -->
@ -146,7 +128,7 @@
<!-- ShardingSphere-JDBC -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>${shardingsphere-jdbc.version}</version>
</dependency>
<dependency>
@ -196,6 +178,12 @@
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- AI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
@ -216,8 +204,33 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>

View File

@ -1,5 +1,6 @@
package io.github.xxyopen.novel;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.CommandLineRunner;
@ -14,6 +15,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Map;
@SpringBootApplication
@ -28,7 +31,7 @@ public class NovelApplication {
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext context) {
public CommandLineRunner commandLineRunner(ApplicationContext context, DataSource dataSource) {
return args -> {
Map<String, CacheManager> beans = context.getBeansOfType(CacheManager.class);
log.info("加载了如下缓存管理器:");
@ -36,7 +39,17 @@ public class NovelApplication {
log.info("{}:{}", k, v.getClass().getName());
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());
}
}
};
}

View File

@ -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());
}
}

View File

@ -8,6 +8,7 @@ import io.github.xxyopen.novel.service.HomeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -24,6 +25,7 @@ import java.util.List;
@RestController
@RequestMapping(ApiRouterConsts.API_FRONT_HOME_URL_PREFIX)
@RequiredArgsConstructor
@Slf4j
public class HomeController {
private final HomeService homeService;
@ -34,6 +36,8 @@ public class HomeController {
@Operation(summary = "首页小说推荐查询接口")
@GetMapping("books")
public RestResp<List<HomeBookRespDto>> listHomeBooks() {
// 测试虚拟线程处理请求
log.debug("处理请求的线程:{}", Thread.currentThread());
return homeService.listHomeBooks();
}

View File

@ -0,0 +1,18 @@
package io.github.xxyopen.novel.core.annotation;
import java.lang.annotation.*;
/**
* 自定义注解用于标记需要进行排序字段sort和排序方式order校验的方法参数。
* <p>
* 在接收到请求参数时,可通过 AOP 对标注了该注解的参数进行统一处理, 校验并防止 SQL 注入等安全问题。
*
* @author xiongxiaoyang
* @date 2025/7/17
*/
@Target(ElementType.PARAMETER) // 表示该注解只能用于方法参数上
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时依然可用,便于 AOP 或其他框架读取
@Documented // 该注解将被包含在生成的 Javadoc 中
public @interface ValidateSortOrder {
}

View File

@ -0,0 +1,130 @@
package io.github.xxyopen.novel.core.aspect;
import io.github.xxyopen.novel.core.annotation.ValidateSortOrder;
import io.github.xxyopen.novel.core.common.req.PageReqDto;
import io.github.xxyopen.novel.core.common.util.SortWhitelistUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
/**
* 排序字段和排序方式的安全校验切面类
* <p>
* 该切面用于拦截所有 Mapper 方法的调用,对带有 @ValidateSortOrder 注解的参数进行统一处理,
* 校验并清理其中的排序字段sort和排序方式order参数防止 SQL 注入攻击。
* <p>
* 支持处理以下类型的参数:
* - PageReqDto 类型对象
* - Map 类型参数
* - 任意带有 sort/order 字段的 POJO 对象
*
* @author xiongxiaoyang
* @date 2025/7/17
*/
@Aspect
@Component
@RequiredArgsConstructor
public class SortOrderValidationAspect {
/**
* 拦截所有 Mapper 方法的调用,检查参数中是否包含 @ValidateSortOrder 注解。
* 如果有,则对参数中的 sort 和 order 字段进行安全校验和清理。
*/
@SneakyThrows
@Around("execution(* io.github.xxyopen.*.dao.mapper.*Mapper.*(..))")
public Object processSortOrderFields(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取方法参数上的所有注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 遍历所有参数,检查是否有 @ValidateSortOrder 注解
for (int i = 0; i < parameterAnnotations.length; i++) {
boolean hasAnnotation = Arrays.stream(parameterAnnotations[i])
.anyMatch(a -> a.annotationType().equals(ValidateSortOrder.class));
if (hasAnnotation && args[i] != null) {
// 对带注解的参数进行处理
handleAnnotatedParameter(args[i]);
}
}
// 继续执行原方法
return joinPoint.proceed(args);
}
/**
* 根据参数类型,分别处理不同形式的 sort/order 字段。
*/
@SneakyThrows
private void handleAnnotatedParameter(Object obj) {
if (obj instanceof PageReqDto dto) {
processPageReqDto(dto);
} else if (obj instanceof Map<?, ?> map) {
processMap(map);
} else {
processGenericObject(obj);
}
}
/**
* 处理 PageReqDto 类型参数中的 sort 和 order 字段。
*/
private void processPageReqDto(PageReqDto dto) {
if (dto.getSort() != null) {
dto.setSort(SortWhitelistUtil.sanitizeColumn(dto.getSort()));
}
if (dto.getOrder() != null) {
dto.setOrder(SortWhitelistUtil.sanitizeOrder(dto.getOrder()));
}
}
/**
* 处理 Map 类型参数中的 sort 和 order 字段。
*/
private void processMap(Map map) {
if (map.get("sort") instanceof String sortStr) {
map.put("sort", SortWhitelistUtil.sanitizeColumn(sortStr));
}
if (map.get("order") instanceof String orderStr) {
map.put("order", SortWhitelistUtil.sanitizeOrder(orderStr));
}
}
/**
* 使用反射处理任意对象中的 sort 和 order 字段。
* 支持任何带有这两个字段的 POJO。
*/
@SneakyThrows
private void processGenericObject(Object obj) {
for (Field field : obj.getClass().getDeclaredFields()) {
switch (field.getName()) {
case "sort", "order" -> {
field.setAccessible(true);
Object value = field.get(obj);
if (value instanceof String strValue) {
String sanitized = "sort".equals(field.getName())
? SortWhitelistUtil.sanitizeColumn(strValue)
: SortWhitelistUtil.sanitizeOrder(strValue);
field.set(obj, sanitized);
}
}
default -> {
// 忽略其他字段
}
}
}
}
}

View File

@ -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.resp.RestResp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
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.servlet.resource.NoResourceFoundException;
/**
* 通用的异常处理器
@ -17,6 +20,15 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class CommonExceptionHandler {
/**
* 处理404异常
*/
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handlerNotFound() {
return "404";
}
/**
* 处理数据校验异常
*/

View File

@ -30,4 +30,16 @@ public class PageReqDto {
@Parameter(hidden = true)
private boolean fetchAll = false;
/**
* 排序字段
*/
@Parameter(description = "排序字段")
private String sort;
/**
* 排序方式
*/
@Parameter(description = "排序方式")
private String order;
}

View File

@ -0,0 +1,63 @@
package io.github.xxyopen.novel.core.common.util;
import lombok.experimental.UtilityClass;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 排序字段和排序方式的安全校验工具类
* <p>
* 用于对请求参数中的排序字段sort和排序方式order进行白名单校验
* 防止 SQL 注入攻击,确保传入的字段名和排序方式是系统允许的合法值。
* <p>
* 该工具类使用 Lombok 的 @UtilityClass 注解,确保:
* - 无法被实例化
* - 所有方法为静态方法
*
* @author xiongxiaoyang
* @date 2025/7/17
*/
@UtilityClass
public class SortWhitelistUtil {
/**
* 允许的排序字段白名单集合
* 包含系统中允许作为排序依据的数据库字段名。
*/
private final Set<String> allowedColumns = new HashSet<>(
Arrays.asList( "last_chapter_update_time", "word_count", "visit_count"));
/**
* 允许的排序方式白名单集合
* 支持升序asc和降序desc两种排序方式。
*/
private final Set<String> allowedOrders = new HashSet<>(Arrays.asList("asc", "desc"));
/**
* 校验并清理排序字段
* <p>
* 如果输入的字段在白名单中,则返回小写形式;
* 否则返回默认字段 "id"。
*
* @param input 用户输入的排序字段
* @return 安全的排序字段名
*/
public String sanitizeColumn(String input) {
return allowedColumns.contains(input.toLowerCase()) ? input.toLowerCase() : "id";
}
/**
* 校验并清理排序方式
* <p>
* 如果输入的排序方式是 "asc" 或 "desc",则返回其小写形式;
* 否则返回默认排序方式 "asc"。
*
* @param input 用户输入的排序方式
* @return 安全的排序方式asc 或 desc
*/
public String sanitizeOrder(String input) {
return allowedOrders.contains(input.toLowerCase()) ? input.toLowerCase() : "asc";
}
}

View File

@ -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 内部通过 RestClientSpring 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();
}
}

View File

@ -1,6 +1,5 @@
package io.github.xxyopen.novel.core.config;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.elasticsearch.client.RestClient;
@ -29,14 +28,6 @@ import java.security.cert.X509Certificate;
@Slf4j
public class EsConfig {
/**
* 解决 ElasticsearchClientConfigurations 修改默认 ObjectMapper 配置的问题
*/
@Bean
JacksonJsonpMapper jacksonJsonpMapper() {
return new JacksonJsonpMapper();
}
/**
* fix `sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
* unable to find valid certification path to requested target`

View File

@ -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();
}
}

View File

@ -1,9 +1,16 @@
package io.github.xxyopen.novel.core.config;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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
*
@ -11,14 +18,23 @@ import org.springframework.context.annotation.Configuration;
* @date 2023/12/21
*/
@Configuration
@EnableAutoConfiguration(exclude = {
org.apache.shardingsphere.spring.boot.ShardingSphereAutoConfiguration.class
})
@ConditionalOnProperty(
prefix = "spring.shardingsphere",
name = {"enabled"},
havingValue = "false"
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());
}
}

View File

@ -62,6 +62,11 @@ public class ApiRouterConsts {
*/
public static final String SEARCH_URL_PREFIX = "/search";
/**
* AI模块请求路径前缀
*/
public static final String AI_URL_PREFIX = "/ai";
/**
* 前台门户首页API请求路径前缀
*/
@ -94,4 +99,10 @@ public class ApiRouterConsts {
public static final String API_FRONT_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;
}

View File

@ -1,6 +1,7 @@
package io.github.xxyopen.novel.dao.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.github.xxyopen.novel.core.annotation.ValidateSortOrder;
import io.github.xxyopen.novel.dao.entity.BookInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
@ -32,6 +33,6 @@ public interface BookInfoMapper extends BaseMapper<BookInfo> {
* @param condition 搜索条件
* @return 返回结果
* */
List<BookInfo> searchBooks(IPage<BookInfoRespDto> page, BookSearchReqDto condition);
List<BookInfo> searchBooks(IPage<BookInfoRespDto> page, @ValidateSortOrder BookSearchReqDto condition);
}

View File

@ -70,9 +70,4 @@ public class BookSearchReqDto extends PageReqDto {
@JsonFormat(pattern = "yyyy-MM-dd")
private Date updateTimeMin;
/**
* 排序字段
*/
@Parameter(description = "排序字段")
private String sort;
}

View File

@ -16,14 +16,44 @@ spring:
multipart:
# 上传文件最大大小
max-file-size: 5MB
# 启用虚拟线程
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:
# 端口号
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:
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
password: test123456
# ShardingSphere-JDBC 配置
@ -32,54 +62,6 @@ spring:
shardingsphere:
# 是否开启分库分表
enabled: false
props:
# 是否在日志中打印 SQL
sql-show: true
# 模式配置
mode:
# 单机模式
type: Standalone
# 元数据持久化
repository:
# 数据库持久化
type: JDBC
props:
# 元数据存储类型
provider: H2
jdbc_url: jdbc:h2:./.h2/shardingsphere
# 数据源配置
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}
--- #---------------------中间件配置---------------------------
spring:
@ -222,6 +204,19 @@ spring:
class: javax.net.ssl.SSLSocketFactory
fallback: false
--- #----------------------Logbook配置-----------------------------
logbook:
format:
# 输出格式
style: http
obfuscate:
headers:
# 隐藏 Authorization 头信息
- Authorization
parameters:
# 隐藏密码参数
- password
--- #---------------------自定义配置----------------------------
novel:
# 跨域配置
@ -230,7 +225,7 @@ novel:
allow-origins:
- http://localhost:1024
- http://localhost:8080
# JWT密钥
# JWT 密钥
jwt:
secret: E66559580A1ADF48CDD928516062F12E
# XSS 过滤配置
@ -252,9 +247,15 @@ spring:
config:
activate:
on-profile: dev
# 开启 SpringDoc 接口文档
springdoc:
api-docs:
enabled: true
# /env 端点显示属性值
management:
endpoint:
env:
show-values: when_authorized
--- #------------------- test 特定配置--------------------------
spring:

View File

@ -62,6 +62,10 @@
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</logger>
<logger name="org.zalando.logbook" level="TRACE" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</logger>
</springProfile>
<springProfile name="prod">

View File

@ -32,7 +32,7 @@
and last_chapter_update_time >= #{condition.updateTimeMin}
</if>
<if test="condition.sort != null">
order by ${condition.sort}
order by ${condition.sort} desc
</if>
</select>

View 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