1.文章简介
本文主要介绍openAPI(Swagger3)和Mybatis-Plus代码生成器技术,然后将两者进行集成,并且自我定制,达到使用生成代码的同时,能够按照我们的要求定制我们所需要的代码,以及注释,并且和openAPI进行集成,形成openAPI(Swagger3)在线接口文档。阅读者最好有swagger基础和mybatis-plus基础
2.项目效果
启动项目后,访问地址http://127.0.0.1:8080/doc.html,即可看到如下页面,
重启项目(建议您开发环境配置热部署,这样您就不用手动重启项目了),重启项目后, 回到我们的浏览器页面,然后刷新浏览器页面,即可看到我们生成了常用的接口。 并且对应的接口描述以及接口属性字段都为我们添加了解释说明,以及请求示例,响应示例。
3.什么是OpenAPI
OpenAPI 规范(OAS),是定义一个标准的、与具体编程语言无关的RESTful API的规范。OpenAPI 规范使得人类和计算机都能在“不接触任何程序源代码和文档、不监控网络通信”的情况下理解一个服务的作用。如果您在定义您的 API 时做的很好,那么使用 API 的人就能非常轻松地理解您提供的 API 并与之交互了。
如果您遵循 OpenAPI 规范来定义您的 API,那么您就可以用文档生成工具来展示您的 API,用代码生成工具来自动生成各种编程语言的服务器端和客户端的代码,用自动测试工具进行测试等等。例如上述演示中,我们成了用户的增删查改的RESTful API。
OpenAPI 3.0是该规范的第一个正式版本,因为它是由SmartBear Software捐赠给OpenAPI Initiative,并在2015年从Swagger规范重命名为OpenAPI规范。
换句话说,Swagger3版本更名叫做OpenAPI。OpenAPI并不局限于Java,其他开发语言或者框架依然可以使用。例如之前的博客python基于flask实现swagger在线文档以及接口测试
Swagger可用于对api文档进行设计、生成和使用。主要包括以下工具: Swagger Editor – 浏览器编辑器,可用于编辑OpenAPI说明. Swagger UI – 以API文档的方式提供OpenAPI说明书。 Swagger Codegen - 根据OpenAPI文档生成服务端存根和客户端库。
4.集成openAPI(Swagger3)
4.1添加openAPI依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
4.2添加OpenAPI配置
@EnableOpenApi
@EnableKnife4j
@SpringBootConfiguration
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.paths(PathSelectors.regex("/error").negate())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("XXXXXX项目字段API文档")
.version("3.0")
.description("该接口主要列举了数据的条件查询接口,以及表字段的详细介绍")
.build();
}
}
对比之前的swagger1和swagger2,openAPI的配置显得就比较少了。 如下是使用的是knife4j UI 访问地址:http://127.0.0.1:8080/doc.html 如下默认的OpenAPI3 (swagger3) UI 访问地址:http://localhost:8080/swagger-ui/index.html 具体使用,看个人使用倾向。
5.什么是代码生成器
平时学习工作,我们都会用到代码生成的一些功能,比如代码自动补全,生成构造方法,生成get、set方法。 尤其是参加工作以后,很多项目都会提供用数据库表结构生成对应的Java Bean对象(PO/Doman/Entity)。
在这里说一下代码生成器的原理,其实就是利用模板引擎,咱们自己写好代码的模板,然后把表信息和列信息从数据库里面查出来,然后渲染到模板里面,进而生成具体的代码文件。 本文集成原理如下所示:
- 利用openAPI和freemarker编写模板
- 读取表结构,将结构信息填充到模板中
- 将模板生成文件
例如如下所示,左边为我们使用openAPI和freemarker编写模板,右边为我们生成的Java Controller。 对比就能发现,我们在模板里面使用了类似占位符的东西,然后将我们读取到的表注释信息读取出来,然后用表的描述信息去填充openAPI中方法的描述信息。 备注:生成的文件类关系图
6.集成代码生成器
6.1添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
6.2mybatis-plus模板路径
即然知道了,原理,那我们就先去找mybatis-plus的模板吧。 ftl结尾的表示用:freemarker模板 vm结尾的表示用:velocity模板 本文我们采用的是freemarker模板,因此把ftl结尾的模板考出来,我们需要用它赖制作我们自己的模板
6.3制作我们的freemarker模板
将模板考出来,放到我们的静态资源目录下。
6.3.1controller.java.ftl模板
package ${package.Controller};
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.cloud.codetool.config.codetool.service.QueryService;
import com.cloud.codetool.config.codetool.vo.ConditionQuery;
import com.cloud.codetool.config.codetool.vo.PageQuery;
import com.cloud.codetool.config.codetool.vo.RestData;
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.web.bind.annotation.RestController;
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
@RestController
@Api(tags = "${table.comment}")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
@RequestMapping("/${entity ? uncap_first}")
public class ${table.controllerName} {
</#if>
@Autowired
private ${table.serviceName} ${table.serviceName ? substring(1) ? uncap_first};
@Autowired
private QueryService queryService;
@ApiOperation(value = "新增${table.comment}")
@PostMapping("/info/save")
public RestData<String> save${entity}Info(@RequestBody ${entity} entity) {
${table.serviceName ? substring(1) ? uncap_first}.save(entity);
return RestData.success();
}
@ApiOperation(value = "按照主键删除${table.comment}")
@PostMapping("/info/remove")
public RestData<String> remove${entity}InfoByKey(@RequestBody List<String> ids) {
for (String id : ids) {
${table.serviceName ? substring(1) ? uncap_first}.removeById(id);
}
return RestData.success();
}
@ApiOperation(value = "按照主键修改${table.comment}")
@PostMapping("/info/update")
public RestData<String> update${entity}InfoByKey(@RequestBody ${entity} entity) {
${table.serviceName ? substring(1) ? uncap_first}.updateById(entity);
return RestData.success();
}
@ApiOperation(value = "按照主键查询${table.comment}")
@GetMapping("/info/{id}")
public RestData<${entity}> get${entity}InfoByKey(@PathVariable String id) {
${entity} entity = ${table.serviceName ? substring(1) ? uncap_first}.getById(id);
return RestData.success(entity);
}
@ApiOperation(value = "按照条件进行分页查询${table.comment}")
@PostMapping("/info/page")
public RestData<IPage<${entity}>> page${entity}Info(@RequestBody PageQuery pageQuery) {
IPage<${entity}> page = queryService.queryByPage(${table.serviceName ? substring(1) ? uncap_first}, pageQuery);
return RestData.success(page);
}
@ApiOperation(value = "按照条件进行查询${table.comment}")
@PostMapping("/info/list")
public RestData<List<${entity}>> list${entity}InfoByCondition(@RequestBody ConditionQuery conditionQuery) {
List<${entity}> list = queryService.queryByCondition(${table.serviceName ? substring(1) ? uncap_first}, conditionQuery);
return RestData.success(list);
}
}
</#if>
6.3.2service.java.ftl模板
package ${package.Service};
import ${package.Entity}.${entity};
import ${superServiceClassPackage};
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {
}
</#if>
6.3.3serviceImpl.java.ftl模板
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>(), ${table.serviceName} {
}
<#else>
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {
@Autowired
${entity}Mapper ${entity ? uncap_first}Mapper;
}
</#if>
6.3.4mapper.java.ftl模板
package ${package.Mapper};
import ${package.Entity}.${entity};
import ${superMapperClassPackage};
<#if mapperAnnotation>
import org.apache.ibatis.annotations.Mapper;
</#if>
<#if mapperAnnotation>
@Mapper
</#if>
<#if kotlin>
interface ${table.mapperName} : ${superMapperClass}<${entity}>
<#else>
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {
}
</#if>
6.3.5entity.java.ftl模板
package ${package.Entity};
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
@Data
@TableName("${table.name}")
@ApiModel(value = "${entity}对象", description = "${table.comment!}")
public class ${entity} implements Serializable {
private static final long serialVersionUID = 1L;
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if field.comment!?length gt 0>
@ApiModelProperty("${field.comment}")
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
<#elseif idType??>
@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.annotationColumnName}")
<#else>
@TableId("${field.annotationColumnName}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.annotationColumnName}")
</#if>
<#-- 乐观锁注解 -->
<#if field.versionField>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if field.propertyName == 'deleted'>
@TableLogic
@JsonIgnore
</#if>
<#-- 数据库字段映射 -->
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
}
6.3.6mapper.xml.ftl
<?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="${package.Mapper}.${table.mapperName}">
<#if enableCache>
<!-- 开启二级缓存 -->
<cache type="${cacheClassName}"/>
</#if>
<#if baseResultMap>
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<#list table.fields as field>
<#if field.keyFlag><#--生成主键排在第一位-->
<id column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
<#list table.commonFields as field><#--生成公共字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#list>
<#list table.fields as field>
<#if !field.keyFlag><#--生成普通字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
</resultMap>
</#if>
<#if baseColumnList>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
<#list table.commonFields as field>
${field.columnName},
</#list>
${table.fieldNames}
</sql>
</#if>
</mapper>
6.4Mybatis-plus配置
@SpringBootConfiguration
@MapperScan(basePackages = "com.**.mapper")
@EnableTransactionManagement
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
6.5application.yml配置
添加如下mybatis配置
code.tool.package: com.cloud.codetool.module
mybatis-plus:
mapper-locations: classpath:/com/**/*.xml
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: FULL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
6.6暴露代码生成器接口
@Data
@ApiModel(value = "自动生成代码参数")
public class TemplateParam {
@ApiModelProperty(value = "作者:将作用于类名,方法名等注释",required = true)
private String author;
@ApiModelProperty(value = "父包:生成的controller、servcie代码将放在此包下",required = true)
private String parentPackage;
@ApiModelProperty(value = "表名:数据库表名,将用于映射对应的增删查改代码",required = true)
private String tableName;
}
@RestController
@Api(tags = "代码生成工具")
public class CodeTempController {
@Value(value = "${spring.datasource.druid.url}")
private String url;
@Value(value = "${spring.datasource.druid.username}")
private String userName;
@Value(value = "${spring.datasource.druid.password}")
private String passWord;
@Value(value = "${code.tool.package}")
private String packagePath;
@ApiOperation(value = "按照系统数据库表生成Controler、Service、Mapper、Entity")
@GetMapping("/code/generate")
public RestData<?> codeTemplate(@RequestBody TemplateParam templateParam ){
String projectPath = new File("").getAbsolutePath()+"//src//main//java//";
new TemplateConfig.Builder();
FastAutoGenerator.create(url,userName,passWord)
.globalConfig((scanner, builder) -> builder
.author(templateParam.getAuthor())
.enableSwagger()
.disableOpenDir()
.dateType(DateType.ONLY_DATE)
.outputDir(projectPath))
.packageConfig((scanner, builder) -> builder
.parent(packagePath+"."+templateParam.getParentPackage()))
.strategyConfig((scanner, builder) -> builder
.addInclude(getTables(templateParam.getTableName()))
.entityBuilder()
.enableLombok()
.addTableFills(
new Column("create_time", FieldFill.INSERT),
new Column("update_time", FieldFill.INSERT_UPDATE)
)
.enableTableFieldAnnotation()
.mapperBuilder()
.enableBaseResultMap()
.enableBaseColumnList()
.build())
.templateEngine(new FreemarkerTemplateEngine())
.execute();
new InjectionConfig
.Builder()
.beforeOutputFile((tableInfo, objectMap) -> {})
.build();
return RestData.success("代码已生成,您可以编译源代码,重启项目即可看到生成的默认接口!");
}
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
}
6.7数据库表接口示例
表的描述信息
表的字段注释信息
7完整的代码
https://download.csdn.net/download/m0_37892044/86328882
|