1. 程式人生 > >程式碼自動生成,避免重複性勞動,程式猿解放時間陪家人

程式碼自動生成,避免重複性勞動,程式猿解放時間陪家人

前序

你有沒有覺得自己常常寫重複的程式碼,面試造輪船,上班擰螺絲?
你有沒有常覺得,自己明明可以更快地寫好功能模組,卻要不斷地 Ctrl + C 、Ctrl + V ?

一、java程式碼自動生成系統開發

1.1 開發環境
  • java JDK 8或更新
  • Tomcat 6及以上,tomcat8以上更佳,或者jetty7以上
  • intellij idea 或者 eclipse
  • maven
  • mysql 5.7及以上(非必須)
  • idea 需要安裝lombok外掛,一塊縮減程式碼的getter setter log等的外掛
1.2 已實現目標
  • 通過配置實體模板自動生成MVC層,及mysql的指令碼
  • 通過配置現有系統的開發模板(或者開發流程),進行全自動生成各層的程式碼
1.3 系統推介

系統模組結構圖,如下圖所示,圖中展示了,模板層基礎層程式碼自動生成層,以及各層的必備工具utils層。
系統模組結構圖

生成的系統模組邏輯程式碼如下圖所示,這些程式碼完全是根據模板,通過IO流,配置規則,自動讀取包名進行切割完成IO輸出,註釋和配置分層都會自動生成,程式碼可以隨意更改,完全符合你需要更改成自己的模組生成的自由度。
這裡寫圖片描述

二、系統亮點展示

2.1 亮點一、自動讀取實體,根據註釋生成js table所需的程式碼

如下圖,是js table節選程式碼塊,如果是做管理後臺的頁面,bootstrap 裡頭有js table控制元件 這是標配了。
這裡寫圖片描述

這裡寫圖片描述

而這一部分,程式碼的相似性,可以不誇張地說,所有的模組,所有目錄下的頁面都會包含,如果,每次都要去開發,都要去複製貼上修改,這是多麼痛苦的領悟!

舉例說明: 下面有這麼一個實體,實現的是機構資訊這麼個實體。

package com.xxx.cum.vo;

import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
* 機構資訊
* @author XXX
* @version Id; OrgInfo, 2018/8/9 16:17 XXX Exp $$
*/
@Data
public class OrgInfo implements Serializable {

private static final long serialVersionUID = ${gETSerialVersionUID };

        /**
        * ID
        */
        private Long id;


        /**
        * 機構編號
        */
        private String orgCode;

        /**
        * 機構名稱
        */
        private String orgName;

        /**
        * 機構全稱
        */
        private String orgFullName;

        /**
        * 收款開戶行銀行編碼
        */
        private String bankCode;

        /**
        * 收款開戶行名稱
        */
        private String bankName;

        /**
        * 收款銀行卡賬號'
        */
        private String bankCardNo;

        /**
        * 銀聯分潤比例
        */
        private BigDecimal unionpayBenefitRate;

        /**
        * 品牌服務費率
        */
        private BigDecimal brandRate;

        /**
        * 轉接清算費率
        */
        private BigDecimal  transferRate;


        /**
        * 付款摘要
        */
        private String payoutSummary;

        /**
        * 是否集團結算 0否 1是
        */
        private String groupSettle;

        /**
        * 是否再付 0否 1是
        */
        private String payAgain;

        /**
        * 公私標識 0對公,1對私
        */
        private String perEntFlag;

        /**
        * 清結算模式 1自主清算 2代理清算 3自主清算+代理清算
        */
        private String settleType;


        /**
        * 協議有效期
        */
        private Date expiredAt;


        /**
        * 機構是否停用狀態碼(預設0,0未停用、1停用、其他待定)
        */
        private String  state;

        /**
        * 建立時間
        */
        private Date createdAt;

        /**
        * 建立人
        */
        private String createdBy;

        /**
        * 更新時間
        */
        private Date updatedAt;

        /**
        * 更新人
        */
        private String updatedBy;



}

實現步驟:
1)、先讀取檔案

/**
     * 通過類實體獲取DataTable
     * @return
     * @throws IOException
     */
    public static String doSomeThing() throws IOException {
        StringBuilder outputSb = new StringBuilder();

        //讀檔案
        File directory = new File("F:\\study\\project\\noUsage\\codeGenerator\\dedd\\com.william\\src\\main\\java\\com\\william\\auto\\temp_help\\template_oss_web");
        for (File tempFile : directory.listFiles()) {
            if (tempFile.getName().indexOf(".xml") != -1) {
                FileReader fileReader = new FileReader(tempFile);
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                String s = null;
                StringBuilder sb = new StringBuilder();
                while(( s = bufferedReader.readLine() ) != null ) {
                    //System.out.println(s);
                    sb.append(s);
                }

                List<String> annoList = getRegexAnnotationExp("\\/\\*([^\\*^\\/]*|[\\*^\\/*]*|[^\\**\\/]*)*\\*\\/",sb);
                List<String> fieldList = getRegexFieldExp("private\\s+([a-zA-Z]*)*\\s+([a-zA-Z]*)*",sb);

                for (int i=0;i<fieldList.size();i++) {
                    if (fieldList.size() > i) {
                        if (fieldList.get(i).equalsIgnoreCase("final")) {
                            continue;
                        }

                        //{'title': '選擇', 'column': 'id'},
                        String item = "{'title':'"+annoList.get(i)+",'column':'"+fieldList.get(i)+"'},";
                        outputSb.append(item+"\n");
                        //System.out.println(item);
                    }
                }

                //輸出結果
                System.out.println(outputSb.toString());
            }
        }
        return outputSb.toString();
    }

2)、讀取實體,以註釋作為js table的title,屬性作為值

/**
     * 讀取註釋文字
     * @param regexExp
     * @param sb
     */
    private static List<String> getRegexAnnotationExp(String regexExp, StringBuilder sb) {
        List<String> resultList = new ArrayList<String>();

        Pattern pattern = Pattern.compile(regexExp);
        String searchPlainText = sb.toString();
        Matcher matcher = pattern.matcher(searchPlainText);
        while (matcher.find()) {
            String groupExp = matcher.group(0).replaceAll("/\\*+\\s+\\*","").replaceAll("\\s+\\*/","");
            resultList.add(groupExp);
            //System.out.println("匹配到:"+groupExp+"  位置:(" + matcher.start()+","+matcher.end()+")");
        }
        return resultList;
    }

    /**
     * 讀取屬性field
     * @param regexExp
     * @param sb
     */
    private static List<String> getRegexFieldExp(String regexExp,StringBuilder sb) {
        List<String> resultList = new ArrayList<String>();

        Pattern pattern = Pattern.compile(regexExp);
        String searchPlainText = sb.toString();
        Matcher matcher = pattern.matcher(searchPlainText);
        while (matcher.find()) {
            String groupExp = matcher.group(0).replaceAll("private\\s+([a-zA-Z]*)*\\s+","");
            resultList.add(groupExp);
            //System.out.println("匹配到:"+groupExp+"  位置:(" + matcher.start()+","+matcher.end()+")");
        }
        return resultList;
    }

生成的結果如下:

{'title':' 機構編號,'column':'orgCode'},
{'title':' 機構名稱,'column':'orgName'},
{'title':' 機構全稱,'column':'orgFullName'},
{'title':' 收款開戶行銀行編碼,'column':'bankCode'},
{'title':' 收款開戶行名稱,'column':'bankName'},
{'title':' 收款銀行卡賬號','column':'bankCardNo'},
{'title':' 銀聯分潤比例,'column':'unionpayBenefitRate'},
{'title':' 品牌服務費率,'column':'brandRate'},
{'title':' 轉接清算費率,'column':'transferRate'},
{'title':' 付款摘要,'column':'payoutSummary'},
{'title':' 是否集團結算 01是,'column':'groupSettle'},
{'title':' 是否再付 0否 1是,'column':'payAgain'},
{'title':' 公私標識 0對公,1對私,'column':'perEntFlag'},
{'title':' 清結算模式 1自主清算 2代理清算 3自主清算+代理清算,'column':'settleType'},
{'title':' 協議有效期,'column':'expiredAt'},
{'title':' 機構是否停用狀態碼(預設0,0未停用、1停用、其他待定),'column':'state'},
{'title':' 建立時間,'column':'createdAt'},
{'title':' 建立人,'column':'createdBy'},
{'title':' 更新時間,'column':'updatedAt'},
{'title':' 更新人,'column':'updatedBy'},
2.2 自動替換程式碼模板的內容(程式碼為節選塊)
File templateDirectory = new File(mybatisPath);
            if (templateDirectory.isDirectory()) {
                File[] templateFiles = templateDirectory.listFiles();
                for (File templateFileItem : templateFiles) {
                    String suffixGenerate = ".xml";
                    String template = "";
                    FileReader fr = new FileReader(templateFileItem);

                    BufferedReader br = new BufferedReader(fr);
                    String s,packagePath = "";

                    while ((s = br.readLine()) != null) {
                        if(s.trim().startsWith("package ")){
                            suffixGenerate = ".java";
                            packagePath = s;
                            packagePath = packagePath.replaceAll("package","").trim().toLowerCase().replaceAll(";","");
                            logger.info("package  path: " + packagePath + "\n");
                        }
                        template += s + "\n";
                    }
                    fr.close();

                    // 替換內容
                    template = StringUtils.toReplaceTemplateKey(template, new String[]{
                                    "Template",
                                    "template",
                                    "author",
                                    "TemplateCN",
                                    "DateTime",
                                    "TemplateUpdateReturn",
                                    "TemplateDeleteReturn",
                                    "TemplateAddReturn",
                                    "TemplateReturnUpdateType",
                                    "TemplateReturnDeleteType",
                                    "TemplateReturnAddType"
                            },
                            new String[]{
                                    className,
                                    StringUtils.toLower(className),
                                    author,
                                    classCNName,
                                    new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date()),
                                    TemplateUpdateReturn,
                                    TemplateDeleteReturn,
                                    TemplateAddReturn,
                                    TemplateReturnUpdateType,
                                    TemplateReturnDeleteType,
                                    TemplateReturnAddType
                            });

                    String realProjectFilePath = "";
                    for (String tempPath:packagePath.split("\\.")) {
                        logger.info("tempPath:" + tempPath + "\tpackagePath:"+packagePath);
                        if (StringUtils.isNull(realProjectFilePath)) {
                            realProjectFilePath = realProjectFilePath + tempPath ;
                        } else {
                            realProjectFilePath = realProjectFilePath +File.separator+ tempPath ;
                        }
                        logger.info("切割後生成的目錄為:==> packagePath:"+realProjectFilePath);
                    }

                    String path = PROJECT_PATH + File.separator + PropertiesHelper.getValueByKey("rootPackage").replaceAll("\\.","\\\\") + File.separator + yyyyMMddHHmmss + File.separator+ realProjectFilePath + File.separator;
                    File file = new File(path);
                    if(!file.exists()){
                        file.mkdirs();
                    }

                    //File fileBiz = new File(path + "\\" + fileNameTemplate + suffixTemplate);

                    String fileItemName = templateFileItem.getName().replaceAll("_","").replaceAll("CustInfoConfig",className).replaceAll("Template",className).split("\\.")[0];


                    FileWriter fw = new FileWriter(path + File.separator + fileItemName + suffixGenerate);
                    BufferedWriter bw = new BufferedWriter(fw);
                    bw.write(template);
                    bw.close();
                    logger.info("生成測試類檔案:[" + fileItemName + suffixGenerate + "]成功" );
                    logger.info("檔案根目錄點選開啟:[" + "file:" + File.separator + File.separator+ File.separator + path + "]成功" );
                }

如你所有的模板中,有個類註解

/**
 * ${TemplateCN}查詢請求
 * @author ${author}
 * @version Id: ${Template}QueryRequest.java , ${DateTime}  ${author} Exp $
 */
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ${Template}QueryRequest extends CumRequest {

模板中的${TemplateCN} ,${author} ,${Template},${DateTime}
可以通過全域性檔案的替換功能,實現替換
替換後的效果:

/**
 * 商戶查詢請求
 * @author zs
 * @version Id: OssCustInstRateQueryRequest.java , 2018-09-03 17:08  zs Exp $
 */
2.3 自動生成各層的MVC程式碼,及資料庫
/**
     * 開始執行自動生成程式碼
     * @param className
     */
    private static void autoGenerator(String className) {
        try{
            //生成實體物件
            Class.forName(entityPackage + "." + className);

            //生成實體檔案
            writeEntity(className);

            //生成dao
            writeDao(className);

            //生成service
            writeService(className);

            //生成service的實現類
            writeServiceImpl(className);

            //生成controller類
            writeController(className);

            //生成xml檔案
            writeXml(className);

            //生成ServiceTest的實現類
            writeFileBeforeReplace(className,"TemplateManageServiceTest",className + "ManageServiceTest",".xml",".java");

            //生成dubbo-service-provider.xml配置檔案
            writeDubboProviderXml(className);

            System.out.println("\n======生成的建表語句如下:======");
            System.out.println(SqlGenerator.generateSql(entityPackage + "." + className,"id"));
        }catch (Exception e){
            logger.error(e.getStackTrace(),e);
        }
    }

如dao層、sql指令碼生成的程式碼節選:

/**
     * 生成dao程式碼
     * @param className
     */
    private static void writeDao(String className) {
        String daoPath = StringUtils.getJavaPath() + templatePackage;
        try {
            String targetDao = PROJECT_PATH + File.separator + PropertiesHelper.getValueByKey("rootPackage").replaceAll("\\.","/") + File.separator + "dao//";
            File file = new File(targetDao);
            if(!file.exists()){
                file.mkdirs();
            }
            File fileAction = new File(targetDao + className + "Dao.java");
            if (!fileAction.exists()) {
                String template = "";
                FileReader fr = new FileReader(daoPath + "TemplateDao.java");

                BufferedReader br = new BufferedReader(fr);
                String s;
                boolean started = false;
                while ((s = br.readLine()) != null) {
                    if(s.startsWith("package")){
                        //重新賦值
                        s = "package " + PropertiesHelper.getValueByKey("rootPackage") + ".dao;";
                    }
                    if(!started){
                        if(s.startsWith("import")){
                            String entityClass = PropertiesHelper.getValueByKey("rootPackage") + ".entity." + className + ";";
                            template += "import " + entityClass + "\n";
                            started = true;
                        }
                    }
                    template += s + "\n";
                }
                fr.close();

                // 替換內容
                template = template.replaceAll("Template", className).replaceAll("template", className);

                FileWriter fw = new FileWriter(targetDao + className + "Dao.java");
                BufferedWriter bw = new BufferedWriter(fw);
                bw.write(template);
                bw.close();
            }
            logger.info("生成dao類:[" + className + "Dao]成功" );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



sql指令碼根據配置的實體生成:


public class SqlGenerator {

    static Logger logger = Logger.getLogger(SqlGenerator.class);

    public static void main(String[] args) {
        String className = "User";
        System.out.println(generateSql(className,"id"));
    }

    /**
     * 根據實體類生成建表語句
     * @author
     * @param className 全類名
     */
    public static String generateSql(String className,String indexKey){
        try {
            Class<?> clz = Class.forName(className);
            className = clz.getSimpleName();
            Field[] fields = clz.getDeclaredFields();
            StringBuffer column = new StringBuffer();
            String varchar = " varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,";
            for (Field f : fields) {
                boolean isStatic = Modifier.isStatic(f.getModifiers());
                if(!isStatic) {
                    if (indexKey.equals(f.getName())) {
                        column.append("\n\t`" + f.getName() + "` int(11) NOT NULL AUTO_INCREMENT,");
                    } else {
                        Class type = f.getType();
                        if (type == String.class) {
                            column.append("\n\t`" + f.getName() + "`" + varchar);
                        }
                    }
                }
            }
            StringBuffer sql = new StringBuffer();
            sql.append("DROP TABLE IF EXISTS `"+ RuleUtils.getTableNameByBean(className) + "`; ")
                    .append("\nCREATE TABLE `"+ RuleUtils.getTableNameByBean(className) + "`(")
                    .append(column)
                    .append(" \n\t PRIMARY KEY (`" + indexKey + "`) USING BTREE")
                    .append("\n) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci;");
            return sql.toString();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.debug("該類未找到!");
            return null;
        }
    }
}

程式碼下載

程式碼目錄


對本程式碼感興趣的同學,可以下載下來使用,下載地址
對本程式碼感興趣的同學,可以下載下來使用,下載地址
對本程式碼感興趣的同學,可以下載下來使用,下載地址