MyBatis-Plus結合Swagger實現接口代碼及文檔自動生成工具(基礎篇-基於配置文件)

              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<List<Gjda>> page = PageHelper.startPage(pageNum, pageSize);
        List<Gjda> 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<String, List<String>> readCVS() throws FileNotFoundException, IOException {

		hssfWorkbook = new XSSFWorkbook(new FileInputStream(PropertiesUtil.getValue("FileParams.properties", "file.path")));
		// 2.獲取要解析的表格(第一個表格)
		XSSFSheet sheet = hssfWorkbook.getSheetAt(0);
		Map<String, List<String>> map = new HashMap<String, List<String>>();
		
		// 獲得最後一行的行號
		int lastRowNum = sheet.getLastRowNum();
		String stringCellValue0=null;
		String stringCellValue1=null;
		List<String> wordList=null;
		// List<String> beanNameList=new ArrayList<String>();
		// List<String> beanParamsList=new ArrayList<String>();
		// List<String> wordList=new ArrayList<String>();
		// List<Integer> rowsList=new ArrayList<Integer>();
		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<String>();
			}
			
			if (stringCellValue2==1 && i != 1) {
				map.put(stringCellValue0+"|"+stringCellValue1, wordList);
				wordList = new ArrayList<String>();
				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<String, List<String>> 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<File> sourceFiles = new ArrayList<File>();
		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<String> list, String apiName) throws IOException {
		List<String> 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<File> 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<File> sourceFile, String targerPath) {
        JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileMgr = comp.getStandardFileManager(null, null, null);
		Iterable<? extends JavaFileObject> compilationUnits = fileMgr.getJavaFileObjectsFromFiles(sourceFile);
		List<String> options = new ArrayList<String>();
        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<List<TFunctionRightsMap>> page = PageHelper.startPage(pageNum, pageSize);
        List<TFunctionRightsMap> 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<List< ${model}>> 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<String, List<String>> 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<File> sourceFiles = new ArrayList<File>();
		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<String> list, String apiName) throws IOException {
		List<String> 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<File> 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<File> sourceFile, String targerPath) {
        JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileMgr = comp.getStandardFileManager(null, null, null);
		Iterable<? extends JavaFileObject> compilationUnits = fileMgr.getJavaFileObjectsFromFiles(sourceFile);
		List<String> options = new ArrayList<String>();
        options.add("-d");
        options.add(targerPath);
		return comp.getTask(null, fileMgr, null, options, null, compilationUnits).call();

	}

}

至此一個完整的自動代碼生成工具構建完成,只需要幾個簡單配置,分分鐘搭建開發工程。

 

插件定義源碼

插件實現原源碼

插件測試源碼

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章