MyBatis-Plus结合Swagger实现接口代码及文档自动生成工具(基础篇-基于配置文件)
需求背景:
近日公司接到一个新项目,时间比较紧急,需求比较简单无非是简单的增删改查分页查询等等,业务复杂度不高。唯一的要求就是快速正常交付,并且需要提供文档给到客户端进行开发联调。
常规的需求分析步骤是:
1:根据需求设计数据库表结构。
2:技术选型搭建开发工程。
3:根据表利用第三方插件自动生成bean及mapper等文件。
4:由于时间紧还需要和客户端进行联调,因此需要文档先行,设计接口文档。
5:代码开发,编写各个数据表的增删改成方法。
我的分析:
虽然此次需求较简单,业务逻辑不复杂,无非就是一些增删改查的功能,但是设计功能模块较多,初步估计需要设计50多张数据表。
1:关于数据表的设计,由于业务逻辑不复杂,因此在数据表的设计也要秉承简单的原则,因此宁可多设计表,也不牵扯太多关联。
2:技术选型肯定选择springboot+maven快速集成发布的模式。
3:对于第三点现在比较流行的就是mybatis,由于很大地方有分页的需求,因此还需要集成分页插件。
4:关于第四点需要文档联调,我第一反应就是Swagger,如果你对它还一无所知,建议你去了解一下,简单来说就是可以在你写好代码的时候自动生成接口文档,并且提供在线测试的功能。
5:对于第五点也是最需要思考的一点,最最常规的操作就是对着需求一个个接口来敲,可是如果是这样就没有本文存在的必要了。注意我前面对于需求的分析,重点圈起来(虽然此次需求较简单,业务逻辑不复杂,无非就是一些增删改查的功能,但是设计功能模块较多,初步估计需要设计50多张数据表。)。
我在思考有没有可能根据表结构像自动生成bean和mapper一样可以自动生成增删改查分页等功能接口。答案是肯定的,因为mybatis的插件能够做到自动生成bean和mapper代码,我们就肯定可以自动生成接口代码。
既然答案是肯定的那么下一步就需要思考如何去实现了。于是第一想法就是去度娘上搜索是否有现成的第三方jar包可以实现这个功能。可是搜索良久收获全无。因此我下定决心自己来写一个。你要问我怎么样去写,我只能告诉你,我毫无头绪,可是我会去一步步分析,问题都是一个个解决的,没有谁能一蹴而就,那是大神的水准。
自动化代码分析:
步骤一:既然要生成swagger,就需要观察正常的swagger接口是个啥?
@ApiOperation(value = "查询学员基本信息接口", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "pageNum", value = "页码", required = true, dataType = "Integer"),
@ApiImplicitParam(paramType = "path", name = "pageSize", value = "每页条数", required = true, dataType = "Integer"),
@ApiImplicitParam(paramType = "query", name = "fbh", value = "编号", required = true, dataType = "String"),
@ApiImplicitParam(paramType = "query", name = "fsfz", value = "身份证", required = true, dataType = "String") })
@GetMapping(value = "list/{pageNum}/{pageSize}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult list(@PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize, Gjda item) {
GjdaExample example = new GjdaExample();
// 补充查询参数开始
if (item != null) {
GjdaExample.Criteria criteria = example.createCriteria();
if (StringUtils.isNotEmpty(item.getFbh())) {
criteria.andFbhLike("%" + item.getFbh() + "%");
}
if (StringUtils.isNotEmpty(item.getFsfz())) {
criteria.andFsfzLike("%" + item.getFsfz() + "%");
}
}
// 补充查询参数结束
Page> page = PageHelper.startPage(pageNum, pageSize);
List list = gjdaMapper.selectByExample(example);
PageInfo info = new PageInfo(page.getPageNum(), page.getPageSize(), page.getTotal(), page.getPages(), list);
return JsonResult.getSuccess(info);
}
上面是一个正常的手写的swagger风格的接口代码,从上面可知,如果要生成这样一个代码我们需要知道bean对象自段的名称、类型、以及说明。这几点你会联想到什么?反射没错就是java反射,通过反射我们可以拿到bean对象的所有属性。那么我们就看看通过mybatis自动生成的bean到底长啥样,能不能满足我们的需求。
package com.zte.model;
import java.io.Serializable;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiModel;
public class TSysParameter implements Serializable {
/**
* This field was generated by MyBatis Generator. This field corresponds to the database column t_sys_parameter.parameter_id
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
private Long parameterId;
/**
* This field was generated by MyBatis Generator. This field corresponds to the database column t_sys_parameter.parameter_name
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
private String parameterName;
/**
* This field was generated by MyBatis Generator. This field corresponds to the database column t_sys_parameter.parameter_value
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
private String parameterValue;
/**
* This field was generated by MyBatis Generator. This field corresponds to the database column t_sys_parameter.remark
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
private String remark;
/**
* This field was generated by MyBatis Generator. This field corresponds to the database table t_sys_parameter
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
private static final long serialVersionUID = 1L;
/**
* This method was generated by MyBatis Generator. This method returns the value of the database column t_sys_parameter.parameter_id
* @return the value of t_sys_parameter.parameter_id
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public Long getParameterId() {
return parameterId;
}
/**
* This method was generated by MyBatis Generator. This method sets the value of the database column t_sys_parameter.parameter_id
* @param parameterId the value for t_sys_parameter.parameter_id
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public void setParameterId(Long parameterId) {
this.parameterId = parameterId;
}
/**
* This method was generated by MyBatis Generator. This method returns the value of the database column t_sys_parameter.parameter_name
* @return the value of t_sys_parameter.parameter_name
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public String getParameterName() {
return parameterName;
}
/**
* This method was generated by MyBatis Generator. This method sets the value of the database column t_sys_parameter.parameter_name
* @param parameterName the value for t_sys_parameter.parameter_name
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public void setParameterName(String parameterName) {
this.parameterName = parameterName == null ? null : parameterName.trim();
}
/**
* This method was generated by MyBatis Generator. This method returns the value of the database column t_sys_parameter.parameter_value
* @return the value of t_sys_parameter.parameter_value
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public String getParameterValue() {
return parameterValue;
}
/**
* This method was generated by MyBatis Generator. This method sets the value of the database column t_sys_parameter.parameter_value
* @param parameterValue the value for t_sys_parameter.parameter_value
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public void setParameterValue(String parameterValue) {
this.parameterValue = parameterValue == null ? null : parameterValue.trim();
}
/**
* This method was generated by MyBatis Generator. This method returns the value of the database column t_sys_parameter.remark
* @return the value of t_sys_parameter.remark
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public String getRemark() {
return remark;
}
/**
* This method was generated by MyBatis Generator. This method sets the value of the database column t_sys_parameter.remark
* @param remark the value for t_sys_parameter.remark
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
public void setRemark(String remark) {
this.remark = remark == null ? null : remark.trim();
}
/**
* This method was generated by MyBatis Generator. This method corresponds to the database table t_sys_parameter
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
TSysParameter other = (TSysParameter) that;
return (this.getParameterId() == null ? other.getParameterId() == null
: this.getParameterId().equals(other.getParameterId()))
&& (this.getParameterName() == null ? other.getParameterName() == null
: this.getParameterName().equals(other.getParameterName()))
&& (this.getParameterValue() == null ? other.getParameterValue() == null
: this.getParameterValue().equals(other.getParameterValue()))
&& (this.getRemark() == null ? other.getRemark() == null : this.getRemark().equals(other.getRemark()));
}
/**
* This method was generated by MyBatis Generator. This method corresponds to the database table t_sys_parameter
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getParameterId() == null) ? 0 : getParameterId().hashCode());
result = prime * result + ((getParameterName() == null) ? 0 : getParameterName().hashCode());
result = prime * result + ((getParameterValue() == null) ? 0 : getParameterValue().hashCode());
result = prime * result + ((getRemark() == null) ? 0 : getRemark().hashCode());
return result;
}
/**
* This method was generated by MyBatis Generator. This method corresponds to the database table t_sys_parameter
* @mbg.generated Mon Sep 23 17:26:31 CST 2019
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", parameterId=").append(parameterId);
sb.append(", parameterName=").append(parameterName);
sb.append(", parameterValue=").append(parameterValue);
sb.append(", remark=").append(remark);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
生成的对象是上图这样的,除了注释其它都能满足。那么摆在我们面前的问题就成了如何给生成的bean对象加上备注。通过集成swagger我们发现它提供了几个注解的功能
@ApiModel("操作历史表") @ApiModelProperty(value ="操作历史ID")其中第一个注解是类说明,第二个注解是字段说明。所以现在问题就转变为了如何为bean对象添加相应的对象。这里又有两种方案可以去解决。
方案一:重写mybatis生成bean的源代码,使它在生成bean的时候就生成相应注解。
方案二:自己动手重写bean.
由于时间紧迫,故不想花太多精力去研究源码因此我选择方案二。
方案二我是这样考虑的,首先我读取事先生成的bean对象,然后根据其关键字添加相应的注解。再把生成的字符串写入新的文件中。现在的问题进一步转化为如何知道相应类和字段的注解。这里又有几种方案。比如读取数据库配置信息,比如自己写配置文件。都能实现,各有特点。而我由于已经在数据库设计阶段就形成了,数据表说明的excel表,因此我就利用读取excel表的方式获取对应的注解信息。
表信息如下:
一:POI方式读取excel数据
package com.zte.common.util;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class ReadCVSUtil{
private static XSSFWorkbook hssfWorkbook;
public static Map> readCVS() throws FileNotFoundException, IOException {
hssfWorkbook = new XSSFWorkbook(new FileInputStream(PropertiesUtil.getValue("FileParams.properties", "file.path")));
// 2.获取要解析的表格(第一个表格)
XSSFSheet sheet = hssfWorkbook.getSheetAt(0);
Map> map = new HashMap>();
// 获得最后一行的行号
int lastRowNum = sheet.getLastRowNum();
String stringCellValue0=null;
String stringCellValue1=null;
List wordList=null;
// List beanNameList=new ArrayList();
// List beanParamsList=new ArrayList();
// List wordList=new ArrayList();
// List rowsList=new ArrayList();
for (int i = 1; i <= lastRowNum; i++) {// 遍历每一行
// 3.获得要解析的行
XSSFRow row = sheet.getRow(i);
double stringCellValue2 = row.getCell(2).getNumericCellValue();
// 4.获得每个单元格中的内容(String)
String stringCellValue12 = row.getCell(12).getStringCellValue();
if(stringCellValue2==1 && i == 1) {
if( row.getCell(0)!=null) {
stringCellValue0 = row.getCell(0).getStringCellValue();
}
if( row.getCell(1)!=null) {
stringCellValue1 = row.getCell(1).getStringCellValue();
}
wordList = new ArrayList();
}
if (stringCellValue2==1 && i != 1) {
map.put(stringCellValue0+"|"+stringCellValue1, wordList);
wordList = new ArrayList();
if( row.getCell(0)!=null) {
stringCellValue0 = row.getCell(0).getStringCellValue();
}
if( row.getCell(1)!=null) {
stringCellValue1 = row.getCell(1).getStringCellValue();
}
}
wordList.add(stringCellValue12);
if(i==lastRowNum) {
map.put(stringCellValue0+"|"+stringCellValue1, wordList);
}
}
return map;
}
}
通过该方法将表信息直接映射成了一个key为表名+表说明的map,value为字段说明的list对象。
二:文件读写给bean对象生成注解信息
package com.zte.common.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class AutoCreateBeanUtil {
/**
* 指定目录下的bean文件生成注解
* @throws IOException
*/
public static void autoCreateAnntation() throws IOException {
File file = new File(PropertiesUtil.getValue("FileParams.properties", "bean.path"));
File[] fileList = file.listFiles();
//通过数据库字典excel读取注解属性
Map> map = ReadCVSUtil.readCVS();
for (int i = 0; i < fileList.length; i++) {
if (fileList[i].isFile()) {
String fileName = fileList[i].getName();
String[] name = fileName.split("\\.");
if (name[0].endsWith("Example")) {
continue;
}
for (String key : map.keySet()) {
if (key.split("\\|")[0].replaceAll("_", "").toLowerCase().equals(name[0].toLowerCase())) {
readFile(fileList[i], map.get(key), key.split("\\|")[1]);
}
}
}
}
List sourceFiles = new ArrayList();
String targetpath=PropertiesUtil.getValue("FileParams.properties", "target.path");
//自动编译bean文件,为后续反射获取bean属性做准备
compiler(file,targetpath,sourceFiles);
compilerJavaFile(sourceFiles,targetpath);
}
/**
* bean文件添加swagge注解
* @param file 目标java文杰
* @param apiName 类注解
* @param list 字段注释
* @throws IOException
*/
public static void readFile(File file, List list, String apiName) throws IOException {
List temp = new ArrayList<>();
FileInputStream inputStream = new FileInputStream(file);
int index = 0;
// 设置inputStreamReader的构造方法并创建对象设置编码方式为gbk
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String str = null;
while ((str = bufferedReader.readLine()) != null) {
if (str.trim().startsWith("public class")) {
temp.add("import io.swagger.annotations.ApiModelProperty;");
temp.add("import io.swagger.annotations.ApiModel;");
temp.add(" ");
temp.add("@ApiModel(" +"\""+ apiName +"\""+")");
}
if (str.trim().startsWith("private") && !str.trim().startsWith("private static")) {
temp.add(" @ApiModelProperty(value =" +"\""+ list.get(index) +"\""+ ")");
index++;
}
temp.add(str);
}
// close
inputStream.close();
bufferedReader.close();
// 下面按行读。我实现的其实就是变相的分行打印,如果有更好的方法请大家指教
OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
BufferedWriter bw = new BufferedWriter(os);
PrintWriter out = new PrintWriter(bw);
for (String aTemp : temp) {
out.println(aTemp);
}
bw.close();
os.close();
out.close();
}
/**
* 递归获取java文件
*
* @param file
* 需要编译的文件夹
* @param targetPath
* 编译后class类文件存放目录
*/
public static void compiler(File file, String targetPath, List sourceFiles) {
File targetDir = new File(targetPath);
if (!targetDir.exists()) {
targetDir.mkdirs();
}
if (file != null && file.exists()) {
File[] listFiles = file.listFiles();
if (null == listFiles || listFiles.length == 0) {
return;
}
for (File file2 : listFiles) {
if (file2.isDirectory()) {
compiler(file2, targetPath, sourceFiles);
} else {
if (file2.getName().endsWith(".java")) {
// 将源文件目录中的Java文件加入集合中
sourceFiles.add(file2);
}
}
}
} else {
System.out.println("传入格式未知文件");
}
}
/**
* 编译java文件
*
* @param sourcePath
* @param targerPath
* @return
*/
public static boolean compilerJavaFile(List sourceFile, String targerPath) {
JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = comp.getStandardFileManager(null, null, null);
Iterable extends JavaFileObject> compilationUnits = fileMgr.getJavaFileObjectsFromFiles(sourceFile);
List options = new ArrayList();
options.add("-d");
options.add(targerPath);
return comp.getTask(null, fileMgr, null, options, null, compilationUnits).call();
}
}
通过阅读上述代码,不光为指定目录下的所有bean对象添加了相应注释外,还为生成的bean对象字段编译成.class文件。这样做又是为了什么呢?
细心的读者可能会发现,在我进行代码分析的时候我说过生成最终的接口代码时,由于要添加swagger接口注释,因此我们需要通过反射获取对象的属性。而反射都是操作.class文件。这里有两种方案。
方案一:生成bean文件后手动编译代码生成class文件,再执行后续的自动生成rest风格的增删改查接口代码。
方案二:自动编译class文件,马上自动生成接口代码。
本文既然是快速构建,那么毫无疑问是要采用第二种方案的。既然选择了远方,便只顾往难的那条路去走,这样你才会发现时时刻刻都在提高。
因此上述代码我自动将生成的代码编译到了指定的目录下,为后续的步骤做准备。
三:利用java反射自动生成接口代码(包含文档注释)
@ApiOperation(value = "查询列表接口", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "pageNum", value = "页码", required = true, dataType = "Integer"),
@ApiImplicitParam(paramType = "path", name = "pageSize", value = "每页条数", required = true, dataType = "Integer"),
@ApiImplicitParam(paramType = "query", name = "functionId", value = "功能id", required = false, dataType = "Long"),
@ApiImplicitParam(paramType = "query", name = "functionRightsMapId", value = " ", required = false, dataType = "Long"),
@ApiImplicitParam(paramType = "query", name = "rightsGroupId", value = "权限组id", required = false, dataType = "Long"), })
@GetMapping(value = "list/{pageNum}/{pageSize}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult list(@PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize,
TFunctionRightsMap item) {
TFunctionRightsMapExample example = null;
// 补充查询参数开始
if (item != null) {
example = new TFunctionRightsMapExample();
TFunctionRightsMapExample.Criteria criteria = example.createCriteria();
// 功能id
if (item.getFunctionId() != null) {
criteria.andFunctionIdEqualTo(item.getFunctionId());
}
//
if (item.getFunctionRightsMapId() != null) {
criteria.andFunctionRightsMapIdEqualTo(item.getFunctionRightsMapId());
}
// 权限组id
if (item.getRightsGroupId() != null) {
criteria.andRightsGroupIdEqualTo(item.getRightsGroupId());
}
}
// 补充查询参数结束
Page> page = PageHelper.startPage(pageNum, pageSize);
List list = tFunctionRightsMapMapper.selectByExample(example);
PageInfo info = new PageInfo(page.getPageNum(), page.getPageSize(), page.getTotal(), page.getPages(), list);
return JsonResult.getSuccess(info);
}
@ApiOperation(value = "查询单个接口", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "id", value = "id", required = true, dataType = "Long") })
@GetMapping(value = "item/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult item(@PathVariable("id") long id) {
return JsonResult.getSuccess(tFunctionRightsMapMapper.selectByPrimaryKey(id));
}
@ApiOperation(value = "修改接口", notes = "")
@PostMapping(value = "update/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult update(@PathVariable("id") long id, @RequestBody TFunctionRightsMap item) {
item.setFunctionRightsMapId(id);
return JsonResult.getSuccess(tFunctionRightsMapMapper.updateByPrimaryKeySelective(item));
}
@ApiOperation(value = "删除接口", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "id", value = "id", required = true, dataType = "Long"), })
@GetMapping(value = "delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult delete(@PathVariable("id") long id) {
return JsonResult.getSuccess(tFunctionRightsMapMapper.deleteByPrimaryKey(id));
}
@ApiOperation(value = "新增接口", notes = "")
@PostMapping(value = "insert", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult insert(@RequestBody TFunctionRightsMap item) {
return JsonResult.getSuccess(tFunctionRightsMapMapper.insertSelective(item));
}
从上面代码可以发现,我们的增删改查、分页接口其实都大同小异,只是操作的类不一样,对象不一样。那么我们可否用一个模板方式进行动态替换呢?答案是肯定的,通过以前前端的开发,我敏锐的发现freemarker简直就是为这个需求定制的。
编写模板
package com.zte.rest;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zte.mapper.${mapper};
import com.zte.model.${model};
import com.zte.model.${example};
import com.zte.param.PageInfo;
import com.zte.common.util.JsonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@RestController
@RequestMapping("/${mapping}")
@Api(tags = "${tags}")
public class ${class} {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
${mapper} ${mapperclass1};
@ApiOperation(value = "查询列表接口", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "pageNum", value = "页码", required = true, dataType = "Integer"),
@ApiImplicitParam(paramType = "path", name = "pageSize", value = "每页条数", required = true, dataType = "Integer"),
${ApiImplicitParam}})
@GetMapping(value = "list/{pageNum}/{pageSize}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult list(@PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize,
${model} item) {
${example} example = new ${example}();
// 补充查询参数开始
// 补充查询参数结束
Page> page = PageHelper.startPage(pageNum, pageSize);
List< ${model}> list = ${mapperclass1}.selectByExample(example);
PageInfo info = new PageInfo(page.getPageNum(), page.getPageSize(), page.getTotal(), page.getPages(), list);
return JsonResult.getSuccess(info);
}
@ApiOperation(value = "查询单个接口", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "id", value = "id", required = true, dataType = "Long") })
@GetMapping(value = "item/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult item(@PathVariable("id") long id) {
return JsonResult.getSuccess(${mapperclass1}.selectByPrimaryKey(id));
}
@ApiOperation(value = "修改接口", notes = "")
@PostMapping(value = "update", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult update @RequestBody ${model} item) {
return JsonResult.getSuccess(${mapperclass1}.updateByPrimaryKeySelective(item));
}
@ApiOperation(value = "删除接口", notes = "")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "id", value = "id", required = true, dataType = "Long"), })
@GetMapping(value = "delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult delete(@PathVariable("id") long id) {
return JsonResult.getSuccess(${mapperclass1}.deleteByPrimaryKey(id));
}
@ApiOperation(value = "新增接口", notes = "")
@PostMapping(value = "insert", produces = MediaType.APPLICATION_JSON_VALUE)
public JsonResult insert(@RequestBody ${model} item) {
return JsonResult.getSuccess(${mapperclass1}.insertSelective(item));
}
}
因此现在我们只需要替换掉模板中的变量,就能新生成一个接口类。
package com.zte.common.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class AutoCreateBeanUtil {
/**
* 指定目录下的bean文件生成注解
* @throws IOException
*/
public static void autoCreateAnntation() throws IOException {
File file = new File(PropertiesUtil.getValue("FileParams.properties", "bean.path"));
File[] fileList = file.listFiles();
//通过数据库字典excel读取注解属性
Map> map = ReadCVSUtil.readCVS();
for (int i = 0; i < fileList.length; i++) {
if (fileList[i].isFile()) {
String fileName = fileList[i].getName();
String[] name = fileName.split("\\.");
if (name[0].endsWith("Example")) {
continue;
}
for (String key : map.keySet()) {
if (key.split("\\|")[0].replaceAll("_", "").toLowerCase().equals(name[0].toLowerCase())) {
readFile(fileList[i], map.get(key), key.split("\\|")[1]);
}
}
}
}
List sourceFiles = new ArrayList();
String targetpath=PropertiesUtil.getValue("FileParams.properties", "target.path");
//自动编译bean文件,为后续反射获取bean属性做准备
compiler(file,targetpath,sourceFiles);
compilerJavaFile(sourceFiles,targetpath);
}
/**
* bean文件添加swagge注解
* @param file 目标java文杰
* @param apiName 类注解
* @param list 字段注释
* @throws IOException
*/
public static void readFile(File file, List list, String apiName) throws IOException {
List temp = new ArrayList<>();
FileInputStream inputStream = new FileInputStream(file);
int index = 0;
// 设置inputStreamReader的构造方法并创建对象设置编码方式为gbk
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String str = null;
while ((str = bufferedReader.readLine()) != null) {
if (str.trim().startsWith("public class")) {
temp.add("import io.swagger.annotations.ApiModelProperty;");
temp.add("import io.swagger.annotations.ApiModel;");
temp.add(" ");
temp.add("@ApiModel(" +"\""+ apiName +"\""+")");
}
if (str.trim().startsWith("private") && !str.trim().startsWith("private static")) {
temp.add(" @ApiModelProperty(value =" +"\""+ list.get(index) +"\""+ ")");
index++;
}
temp.add(str);
}
// close
inputStream.close();
bufferedReader.close();
// 下面按行读。我实现的其实就是变相的分行打印,如果有更好的方法请大家指教
OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
BufferedWriter bw = new BufferedWriter(os);
PrintWriter out = new PrintWriter(bw);
for (String aTemp : temp) {
out.println(aTemp);
}
bw.close();
os.close();
out.close();
}
/**
* 递归获取java文件
*
* @param file
* 需要编译的文件夹
* @param targetPath
* 编译后class类文件存放目录
*/
public static void compiler(File file, String targetPath, List sourceFiles) {
File targetDir = new File(targetPath);
if (!targetDir.exists()) {
targetDir.mkdirs();
}
if (file != null && file.exists()) {
File[] listFiles = file.listFiles();
if (null == listFiles || listFiles.length == 0) {
return;
}
for (File file2 : listFiles) {
if (file2.isDirectory()) {
compiler(file2, targetPath, sourceFiles);
} else {
if (file2.getName().endsWith(".java")) {
// 将源文件目录中的Java文件加入集合中
sourceFiles.add(file2);
}
}
}
} else {
System.out.println("传入格式未知文件");
}
}
/**
* 编译java文件
*
* @param sourcePath
* @param targerPath
* @return
*/
public static boolean compilerJavaFile(List sourceFile, String targerPath) {
JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = comp.getStandardFileManager(null, null, null);
Iterable extends JavaFileObject> compilationUnits = fileMgr.getJavaFileObjectsFromFiles(sourceFile);
List options = new ArrayList();
options.add("-d");
options.add(targerPath);
return comp.getTask(null, fileMgr, null, options, null, compilationUnits).call();
}
}
至此一个完整的自动代码生成工具构建完成,只需要几个简单配置,分分钟搭建开发工程。
插件定义源码
插件实现原源码
插件测试源码