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依赖

<!-- OpenAPI3 (swagger3) 访问地址:http://localhost:8080/swagger-ui/index.html-->
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-boot-starter</artifactId>
	<version>3.0.0</version>
</dependency>
<!-- knife4j UI 访问地址:http://127.0.0.1:8080/doc.html-->
<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>knife4j-spring-boot-starter</artifactId>
	<version>3.0.3</version>
</dependency>

4.2添加OpenAPI配置

/**
 * @description:openAPI(Swagger)配置
 * @author:hutao
 * @mail:hutao_2017@aliyun.com
 * @date:2022年2月24日 上午10:12:54
 */
@EnableOpenApi
@EnableKnife4j
@SpringBootConfiguration
public class SwaggerConfiguration {
	/**
	 * @description:屏蔽不暴露的接口
	 * @author:hutao
	 * @mail:hutao_2017@aliyun.com
	 * @date:2022年3月16日 下午5:38:16
	 */
	@Bean
	public Docket createRestApi() {
		return new Docket(DocumentationType.OAS_30)
			.apiInfo(apiInfo())
			.select()
			.paths(PathSelectors.regex("/error").negate())
			.build();
		
	}
	/**
	 * @description:设置API文档信息
	 * @author:hutao
	 * @mail:hutao_2017@aliyun.com
	 * @date:2022年3月16日 下午5:37:56
	 */
	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)。

在这里说一下代码生成器的原理,其实就是利用模板引擎,咱们自己写好代码的模板,然后把表信息和列信息从数据库里面查出来,然后渲染到模板里面,进而生成具体的代码文件。
本文集成原理如下所示:
在这里插入图片描述

  1. 利用openAPI和freemarker编写模板
  2. 读取表结构,将结构信息填充到模板中
  3. 将模板生成文件

例如如下所示,左边为我们使用openAPI和freemarker编写模板,右边为我们生成的Java Controller。
在这里插入图片描述
对比就能发现,我们在模板里面使用了类似占位符的东西,然后将我们读取到的表注释信息读取出来,然后用表的描述信息去填充openAPI中方法的描述信息
在这里插入图片描述
备注:生成的文件类关系图
在这里插入图片描述

6.集成代码生成器

6.1添加依赖

<!-- mybatis-plus -->
<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>

/**
 * @description:${table.comment}控制器
 * @author ${author}
 * @date ${date}
 */
@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;
	
	/**
 	* @description:新增${table.comment}
    * @author ${author}
    * @date ${date}
    */
	@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();
	}
	
	/**
 	* @description:按照主键删除${table.comment}
    * @author ${author}
    * @date ${date}
    */
	@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();
	}
	
	/**
 	* @description:按照主键修改${table.comment}
    * @author ${author}
    * @date ${date}
    */
	@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();
	}
	
	/**
 	* @description:按照主键查询${table.comment}
    * @author ${author}
    * @date ${date}
    */
	@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);
	}
	
	
	/**
 	* @description:按照条件进行分页查询${table.comment}
    * @author ${author}
    * @date ${date}
    */
	@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);
	}
	
	/**
 	* @description:按照条件进行查询${table.comment}
    * @author ${author}
    * @date ${date}
    */
	@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};

 /**
 * @description:${table.comment!} 服务接口定义
 * @author ${author}
 * @date ${date}
 */
<#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;

 /**
 * @description:${table.comment!} 服务接口实现类
 * @author ${author}
 * @date ${date}
 */
@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>

  /**
 * @description:${table.comment!} dao接口,该接口内置了普通的增删查改
 * @author ${author}
 * @date ${date}
 */
<#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;

 /**
 * @description:${table.comment!}数据表:${table.name}表的持久类
 * @author ${author}
 * @date ${date}
 */
@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配置

/**
 * @description:mybatis-plus配置
 * @author:hutao
 * @mail:hutao_2017@aliyun.com
 * @date:2022年2月19日 下午3:54:55
 */
@SpringBootConfiguration
@MapperScan(basePackages = "com.**.mapper")
@EnableTransactionManagement
public class MybatisPlusConfig {
	/**
	 * @description:配置mybatis使用分页时,采用乐观锁和达梦数据库
	 * @author:hutao
	 * @mail:hutao_2017@aliyun.com
	 * @date:2022年2月19日 下午3:54:55
	 */
    @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配置    
mybatis-plus:
  mapper-locations: classpath:/com/**/*.xml  
  #mapper-locations: classpath:/mapper/*.xml  
  configuration:
    #开启驼峰功能
    map-underscore-to-camel-case: true
    #NONE 取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)
    auto-mapping-behavior: FULL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    logic-delete-field: deleted # 全局逻辑删除的实体字段名
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 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()/*.serviceImpl("/templates/serviceImpl.java")*/;
    	
		FastAutoGenerator.create(url,userName,passWord)
		//全局配置
		.globalConfig((scanner, builder) -> builder
				.author(templateParam.getAuthor())
				.enableSwagger()
				//开启覆盖已生成的文件。注释掉则关闭覆盖。
				//.fileOverride()
				//禁止打开生成的文件目录。
				.disableOpenDir()
				//设置时间类型 Date(默认使用Java8的时间类型,部分数据库可能无法识别)
				.dateType(DateType.ONLY_DATE)
				//生成文件的输出路径
				.outputDir(projectPath))
		//包设置
		.packageConfig((scanner, builder) -> builder
				//设置代码包路径
				.parent(packagePath+"."+templateParam.getParentPackage()))
		//策略配置
		.strategyConfig((scanner, builder) -> builder
				//需要使用表生成的代码(传入all时,所有表生成)
				.addInclude(getTables(templateParam.getTableName()))
				.entityBuilder()
				//逻辑删除配置,当字段名称为deleted,会设置逻辑删除。会在实体类的该字段属性前加注解[@TableLogic](这里已经在/resources/templates/entity.java.ftl中配置了删除)
				//.logicDeleteColumnName("DELETE_FLAG")
				//添加字段描述
				.enableLombok()
				.addTableFills(
						//设定创建时间 ,更新时间自动填充
						new Column("create_time", FieldFill.INSERT),
						new Column("update_time", FieldFill.INSERT_UPDATE)
						)
				.enableTableFieldAnnotation()
				//Mapper.xml策略配置
				.mapperBuilder()
				//启用 BaseResultMap 生成。会在mapper.xml文件生成[通用查询映射结果]配置。
				.enableBaseResultMap()
				//启用 BaseColumnList。会在mapper.xml文件生成[通用查询结果列 ]配置
				.enableBaseColumnList()
				.build())
		//模板引擎配置,Freemarker
		.templateEngine(new FreemarkerTemplateEngine())
		.execute();
		new InjectionConfig
		.Builder()
		.beforeOutputFile((tableInfo, objectMap) -> {})
		.build();
        return RestData.success("代码已生成,您可以编译源代码,重启项目即可看到生成的默认接口!");
	}
	
    /**
     * @description:处理all的时候
     * @author:hutao
     * @mail:hutao_2017@aliyun.com
     * @date:2022年2月18日 上午11:32:54
     */
    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
在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐