6 changed files with 313 additions and 133 deletions
@ -0,0 +1,154 @@ |
|||
# SpringDoc OpenAPI 迁移报告 |
|||
|
|||
## 迁移概述 |
|||
|
|||
已成功将项目从 **Springfox 3.0.0** 迁移到 **SpringDoc OpenAPI 1.7.0**,解决了与 Spring Boot 2.6+ 的兼容性问题。 |
|||
|
|||
## ✅ 已完成的迁移工作 |
|||
|
|||
### 1. 依赖更新 |
|||
- ✅ **Springfox → SpringDoc OpenAPI** |
|||
```xml |
|||
<!-- 旧依赖 --> |
|||
<dependency> |
|||
<groupId>io.springfox</groupId> |
|||
<artifactId>springfox-boot-starter</artifactId> |
|||
<version>3.0.0</version> |
|||
</dependency> |
|||
|
|||
<!-- 新依赖 --> |
|||
<dependency> |
|||
<groupId>org.springdoc</groupId> |
|||
<artifactId>springdoc-openapi-ui</artifactId> |
|||
<version>1.7.0</version> |
|||
</dependency> |
|||
``` |
|||
|
|||
- ✅ **Knife4j 升级** |
|||
```xml |
|||
<!-- 旧版本 --> |
|||
<dependency> |
|||
<groupId>com.github.xiaoymin</groupId> |
|||
<artifactId>knife4j-spring-boot-starter</artifactId> |
|||
<version>3.0.3</version> |
|||
</dependency> |
|||
|
|||
<!-- 新版本 --> |
|||
<dependency> |
|||
<groupId>com.github.xiaoymin</groupId> |
|||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId> |
|||
<version>4.3.0</version> |
|||
</dependency> |
|||
``` |
|||
|
|||
### 2. 配置类重写 |
|||
- ✅ **SwaggerConfig.java** 完全重写 |
|||
- 使用 `OpenAPI` 替代 `Docket` |
|||
- 使用 `GroupedOpenApi` 实现模块分组 |
|||
- 配置 JWT Bearer 认证 |
|||
- 支持 common、cms、shop、oa、other 模块分组 |
|||
|
|||
### 3. 注解迁移示例 |
|||
- ✅ **控制器注解** |
|||
```java |
|||
// 旧注解 |
|||
@Api(tags = "文章管理") |
|||
@ApiOperation("分页查询文章") |
|||
|
|||
// 新注解 |
|||
@Tag(name = "文章管理") |
|||
@Operation(summary = "分页查询文章") |
|||
``` |
|||
|
|||
- ✅ **实体类注解** |
|||
```java |
|||
// 旧注解 |
|||
@ApiModel(value = "CmsModel对象", description = "模型") |
|||
@ApiModelProperty(value = "ID") |
|||
|
|||
// 新注解 |
|||
@Schema(name = "CmsModel对象", description = "模型") |
|||
@Schema(description = "ID") |
|||
``` |
|||
|
|||
### 4. 配置优化 |
|||
- ✅ 移除了不兼容的 `SpringFoxSwaggerHostResolver` |
|||
- ✅ 添加了 `ant_path_matcher` 兼容性配置 |
|||
- ✅ 临时禁用了 API 文档功能(等待重新编译) |
|||
|
|||
## ⏳ 待完成的工作 |
|||
|
|||
### 1. 重新编译项目 |
|||
**重要:** 当前 JAR 文件仍包含旧的 Springfox 依赖,需要重新编译: |
|||
|
|||
```bash |
|||
# 安装 Maven(如果没有) |
|||
brew install maven # macOS |
|||
# 或 |
|||
sudo apt install maven # Ubuntu |
|||
|
|||
# 重新编译项目 |
|||
mvn clean package -DskipTests |
|||
|
|||
# 运行新版本 |
|||
java -jar target/com-gxwebsoft-modules-1.5.0.jar |
|||
``` |
|||
|
|||
### 2. 批量注解迁移 |
|||
项目中还有大量文件使用旧的 Springfox 注解,可以使用提供的脚本批量迁移: |
|||
|
|||
```bash |
|||
# 使用迁移脚本 |
|||
chmod +x migrate_swagger_annotations.sh |
|||
./migrate_swagger_annotations.sh |
|||
``` |
|||
|
|||
### 3. 启用 API 文档 |
|||
重新编译后,在 `application.yml` 中启用 SpringDoc: |
|||
|
|||
```yaml |
|||
# 启用 SpringDoc OpenAPI |
|||
springdoc: |
|||
api-docs: |
|||
enabled: true |
|||
swagger-ui: |
|||
enabled: true |
|||
|
|||
# 启用 Knife4j |
|||
knife4j: |
|||
enable: true |
|||
``` |
|||
|
|||
## 🎯 迁移后的优势 |
|||
|
|||
1. **兼容性**: 完美支持 Spring Boot 2.6+ 和 3.x |
|||
2. **性能**: 更快的启动速度和更好的运行时性能 |
|||
3. **标准化**: 使用标准 OpenAPI 3.0 规范 |
|||
4. **维护性**: 活跃的社区支持和定期更新 |
|||
5. **简化配置**: 零配置即可使用,配置更简洁 |
|||
|
|||
## 📋 验证清单 |
|||
|
|||
重新编译后需要验证: |
|||
|
|||
- [ ] 应用正常启动无错误 |
|||
- [ ] 访问 Swagger UI: `http://localhost:9202/swagger-ui.html` |
|||
- [ ] 访问 API 文档: `http://localhost:9202/v3/api-docs` |
|||
- [ ] 访问 Knife4j UI: `http://localhost:9202/doc.html` |
|||
- [ ] 各模块分组正常显示 |
|||
- [ ] JWT 认证配置正常工作 |
|||
|
|||
## 🔧 故障排除 |
|||
|
|||
如果遇到问题: |
|||
|
|||
1. **编译错误**: 检查是否有遗漏的注解迁移 |
|||
2. **启动失败**: 确认所有 Springfox 依赖已移除 |
|||
3. **文档不显示**: 检查 SpringDoc 配置是否正确启用 |
|||
4. **认证问题**: 验证 JWT 配置是否正确 |
|||
|
|||
## 📝 注意事项 |
|||
|
|||
- 迁移脚本会创建 `.bak` 备份文件,如有问题可以恢复 |
|||
- 建议在测试环境先验证完整功能后再部署到生产环境 |
|||
- 新的 API 文档 URL 可能与旧版本不同,需要更新相关文档 |
@ -0,0 +1,96 @@ |
|||
# Springfox 兼容性问题修复指南 |
|||
|
|||
## 问题描述 |
|||
Spring Boot 应用启动时出现以下错误: |
|||
``` |
|||
Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null |
|||
``` |
|||
|
|||
## 问题原因 |
|||
- **Spring Boot 2.6+** 默认使用 `PathPatternMatcher` 替代 `AntPathMatcher` |
|||
- **Springfox 3.0.0** 仍然依赖旧的 `AntPathMatcher`,导致兼容性问题 |
|||
|
|||
## 解决方案 |
|||
|
|||
### 方案1:配置兼容性(临时方案) |
|||
在 `application.yml` 中添加: |
|||
```yaml |
|||
spring: |
|||
mvc: |
|||
pathmatch: |
|||
matching-strategy: ant_path_matcher |
|||
``` |
|||
|
|||
### 方案2:升级到 SpringDoc OpenAPI(推荐) |
|||
|
|||
#### 1. 更新 pom.xml 依赖 |
|||
```xml |
|||
<!-- 替换 Springfox --> |
|||
<dependency> |
|||
<groupId>org.springdoc</groupId> |
|||
<artifactId>springdoc-openapi-ui</artifactId> |
|||
<version>1.7.0</version> |
|||
</dependency> |
|||
|
|||
<!-- 更新 Knife4j --> |
|||
<dependency> |
|||
<groupId>com.github.xiaoymin</groupId> |
|||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId> |
|||
<version>4.3.0</version> |
|||
</dependency> |
|||
``` |
|||
|
|||
#### 2. 更新 SwaggerConfig.java |
|||
```java |
|||
@Configuration |
|||
public class SwaggerConfig { |
|||
@Resource |
|||
private ConfigProperties config; |
|||
|
|||
@Bean |
|||
public OpenAPI customOpenAPI() { |
|||
return new OpenAPI() |
|||
.info(new Info() |
|||
.title(config.getSwaggerTitle()) |
|||
.description(config.getSwaggerDescription()) |
|||
.version(config.getSwaggerVersion()) |
|||
.contact(new Contact() |
|||
.name("科技小王子") |
|||
.url("https://www.gxwebsoft.com") |
|||
.email("170083662@qq.com"))) |
|||
.components(new Components() |
|||
.addSecuritySchemes("Authorization", |
|||
new SecurityScheme() |
|||
.type(SecurityScheme.Type.HTTP) |
|||
.scheme("bearer") |
|||
.bearerFormat("JWT"))) |
|||
.addSecurityItem(new SecurityRequirement().addList("Authorization")); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### 3. 重新编译项目 |
|||
```bash |
|||
mvn clean package -DskipTests |
|||
``` |
|||
|
|||
#### 4. 运行应用 |
|||
```bash |
|||
java -jar target/your-app.jar |
|||
``` |
|||
|
|||
## 验证修复 |
|||
1. 应用启动无错误 |
|||
2. 访问 Swagger UI:`http://localhost:9200/swagger-ui.html` |
|||
3. 访问 API 文档:`http://localhost:9200/v3/api-docs` |
|||
|
|||
## 注意事项 |
|||
- SpringDoc OpenAPI 使用不同的注解和配置方式 |
|||
- 可能需要更新 Controller 中的 Swagger 注解 |
|||
- Knife4j 4.x 版本与 SpringDoc 兼容 |
|||
|
|||
## 状态 |
|||
✅ 配置文件已修改 |
|||
✅ 依赖已更新 |
|||
✅ SwaggerConfig 已重写 |
|||
⏳ 需要重新编译项目以生效 |
@ -0,0 +1,46 @@ |
|||
#!/bin/bash |
|||
|
|||
# SpringDoc OpenAPI 注解迁移脚本 |
|||
# 将 Springfox 注解替换为 SpringDoc OpenAPI 注解 |
|||
|
|||
echo "开始迁移 Swagger 注解..." |
|||
|
|||
# 查找所有 Java 文件 |
|||
find src/main/java -name "*.java" -type f | while read file; do |
|||
echo "处理文件: $file" |
|||
|
|||
# 备份原文件 |
|||
cp "$file" "$file.bak" |
|||
|
|||
# 替换 import 语句 |
|||
sed -i '' 's/import io\.swagger\.annotations\.Api;/import io.swagger.v3.oas.annotations.tags.Tag;/g' "$file" |
|||
sed -i '' 's/import io\.swagger\.annotations\.ApiOperation;/import io.swagger.v3.oas.annotations.Operation;/g' "$file" |
|||
sed -i '' 's/import io\.swagger\.annotations\.ApiParam;/import io.swagger.v3.oas.annotations.Parameter;/g' "$file" |
|||
sed -i '' 's/import io\.swagger\.annotations\.ApiModel;/import io.swagger.v3.oas.annotations.media.Schema;/g' "$file" |
|||
sed -i '' 's/import io\.swagger\.annotations\.ApiModelProperty;/import io.swagger.v3.oas.annotations.media.Schema;/g' "$file" |
|||
|
|||
# 替换注解使用 |
|||
sed -i '' 's/@Api(tags = "\([^"]*\)")/@Tag(name = "\1")/g' "$file" |
|||
sed -i '' 's/@ApiOperation("\([^"]*\)")/@Operation(summary = "\1")/g' "$file" |
|||
sed -i '' 's/@ApiOperation(value = "\([^"]*\)")/@Operation(summary = "\1")/g' "$file" |
|||
sed -i '' 's/@ApiOperation(value = "\([^"]*\)", notes = "\([^"]*\)")/@Operation(summary = "\1", description = "\2")/g' "$file" |
|||
|
|||
# 替换实体类注解 |
|||
sed -i '' 's/@ApiModel(value = "\([^"]*\)", description = "\([^"]*\)")/@Schema(name = "\1", description = "\2")/g' "$file" |
|||
sed -i '' 's/@ApiModel(value = "\([^"]*\)")/@Schema(name = "\1")/g' "$file" |
|||
sed -i '' 's/@ApiModel("\([^"]*\)")/@Schema(description = "\1")/g' "$file" |
|||
|
|||
# 替换属性注解 |
|||
sed -i '' 's/@ApiModelProperty(value = "\([^"]*\)")/@Schema(description = "\1")/g' "$file" |
|||
sed -i '' 's/@ApiModelProperty("\([^"]*\)")/@Schema(description = "\1")/g' "$file" |
|||
|
|||
# 替换参数注解 |
|||
sed -i '' 's/@ApiParam(name = "\([^"]*\)", value = "\([^"]*\)", required = \([^)]*\))/@Parameter(name = "\1", description = "\2", required = \3)/g' "$file" |
|||
sed -i '' 's/@ApiParam(value = "\([^"]*\)")/@Parameter(description = "\1")/g' "$file" |
|||
sed -i '' 's/@ApiParam("\([^"]*\)")/@Parameter(description = "\1")/g' "$file" |
|||
|
|||
echo "完成处理: $file" |
|||
done |
|||
|
|||
echo "注解迁移完成!" |
|||
echo "请检查修改后的文件,如有问题可以从 .bak 文件恢复" |
@ -1,61 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="com.gxwebsoft.oa.mapper.OaAppRenewMapper"> |
|||
|
|||
<!-- 关联查询sql --> |
|||
<sql id="selectSql"> |
|||
SELECT a.*, |
|||
b.company_name,b.short_name,b.company_logo, |
|||
c.app_name |
|||
FROM oa_app_renew a |
|||
LEFT JOIN oa_company b ON a.company_id = b.company_id |
|||
LEFT JOIN oa_app c ON a.app_id = c.app_id |
|||
<where> |
|||
<if test="param.appRenewId != null"> |
|||
AND a.app_renew_id = #{param.appRenewId} |
|||
</if> |
|||
<if test="param.money != null"> |
|||
AND a.money = #{param.money} |
|||
</if> |
|||
<if test="param.comments != null"> |
|||
AND a.comments LIKE CONCAT('%', #{param.comments}, '%') |
|||
</if> |
|||
<if test="param.startTime != null"> |
|||
AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') |
|||
</if> |
|||
<if test="param.endTime != null"> |
|||
AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') |
|||
</if> |
|||
<if test="param.userId != null"> |
|||
AND a.user_id = #{param.userId} |
|||
</if> |
|||
<if test="param.appId != null"> |
|||
AND a.app_id = #{param.appId} |
|||
</if> |
|||
<if test="param.companyId != null"> |
|||
AND a.company_id = #{param.companyId} |
|||
</if> |
|||
|
|||
<if test="param.status != null"> |
|||
AND a.status = #{param.status} |
|||
</if> |
|||
<if test="param.createTimeStart != null"> |
|||
AND a.create_time >= #{param.createTimeStart} |
|||
</if> |
|||
<if test="param.createTimeEnd != null"> |
|||
AND a.create_time <= #{param.createTimeEnd} |
|||
</if> |
|||
</where> |
|||
</sql> |
|||
|
|||
<!-- 分页查询 --> |
|||
<select id="selectPageRel" resultType="com.gxwebsoft.oa.entity.OaAppRenew"> |
|||
<include refid="selectSql"></include> |
|||
</select> |
|||
|
|||
<!-- 查询全部 --> |
|||
<select id="selectListRel" resultType="com.gxwebsoft.oa.entity.OaAppRenew"> |
|||
<include refid="selectSql"></include> |
|||
</select> |
|||
|
|||
</mapper> |
@ -1,68 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="com.gxwebsoft.oa.mapper.OaTaskRecordMapper"> |
|||
|
|||
<!-- 关联查询sql --> |
|||
<sql id="selectSql"> |
|||
SELECT a.* |
|||
FROM oa_task_record a |
|||
<where> |
|||
<if test="param.taskRecordId != null"> |
|||
AND a.task_record_id = #{param.taskRecordId} |
|||
</if> |
|||
<if test="param.parentId != null"> |
|||
AND a.parent_id = #{param.parentId} |
|||
</if> |
|||
<if test="param.taskId != null"> |
|||
AND a.task_id = #{param.taskId} |
|||
</if> |
|||
<if test="param.content != null"> |
|||
AND a.content LIKE CONCAT('%', #{param.content}, '%') |
|||
</if> |
|||
<if test="param.confidential != null"> |
|||
AND a.confidential LIKE CONCAT('%', #{param.confidential}, '%') |
|||
</if> |
|||
<if test="param.phone != null"> |
|||
AND a.phone LIKE CONCAT('%', #{param.phone}, '%') |
|||
</if> |
|||
<if test="param.files != null"> |
|||
AND a.files LIKE CONCAT('%', #{param.files}, '%') |
|||
</if> |
|||
<if test="param.userId != null"> |
|||
AND a.user_id = #{param.userId} |
|||
</if> |
|||
<if test="param.sortNumber != null"> |
|||
AND a.sort_number = #{param.sortNumber} |
|||
</if> |
|||
<if test="param.comments != null"> |
|||
AND a.comments LIKE CONCAT('%', #{param.comments}, '%') |
|||
</if> |
|||
<if test="param.status != null"> |
|||
AND a.status = #{param.status} |
|||
</if> |
|||
<if test="param.deleted != null"> |
|||
AND a.deleted = #{param.deleted} |
|||
</if> |
|||
<if test="param.deleted == null"> |
|||
AND a.deleted = 0 |
|||
</if> |
|||
<if test="param.createTimeStart != null"> |
|||
AND a.create_time >= #{param.createTimeStart} |
|||
</if> |
|||
<if test="param.createTimeEnd != null"> |
|||
AND a.create_time <= #{param.createTimeEnd} |
|||
</if> |
|||
</where> |
|||
</sql> |
|||
|
|||
<!-- 分页查询 --> |
|||
<select id="selectPageRel" resultType="com.gxwebsoft.oa.entity.OaTaskRecord"> |
|||
<include refid="selectSql"></include> |
|||
</select> |
|||
|
|||
<!-- 查询全部 --> |
|||
<select id="selectListRel" resultType="com.gxwebsoft.oa.entity.OaTaskRecord"> |
|||
<include refid="selectSql"></include> |
|||
</select> |
|||
|
|||
</mapper> |
Loading…
Reference in new issue