Spring Cloud + Vue前后端分离-第6章 通用代码生成器开发

Spring Cloud + Vue前后端分离-第6章 通用代码生成器开发

6-1 代码生成器原理介绍

1.增加generator模块,用于代码生成

2.集成freemarker

通用代码生成器开发

FreeMarker 是一款模版引擎,通过模板生成文件,包括html页面,excel 等。本章将利用freemarker 制作前后端代码生成器,提高开发效率。

代码生成器的目标;2分钟内完成单表的增删改查功能,包含前后端代码

集成freemarker 模板引擎

freemarker简介

大牌的模版引擎有freemarker和thymeleaf,freemarker是资历老,1999年就有了,有广泛的群众基础。thymeleaf是Spring官方推荐的,所以受众也广

freemarker代码段
<#if success><div>成功</div>
</#if><#list userList as user><tr><td>${user.id}</td><td>${user.name}</td></tr>
</#list>thymeleaf代码段
<div th:if="${success}">成功</div><tr th:each="user:${userList}"><td th:text="${user.id}"></td><td th:text="${user.name}"></td>
</tr>

两者的代码风格不同:freemarker 的逻辑主要写在自定义标签中;thymeleaf 的逻辑主要写在标签属性中,web 开发时可读性好一点。随着版本不断更新,两者性能正常使用没什么区别,不用纠结。

freemarker集成与使用

步骤1:在父pom.xml中添加freemarker jar 依赖

步骤2:增加generator子模块,代码生成器专用,并添加freemarker  jar 依赖

步骤3:添加模板文件test.ftl 和启动文件TestUtil.java

.ftl是 freemarker 约定的模板文件后缀,也可以改成任意其它后缀

windows下用双反斜杠

我使用的是mac,用的正斜杠

ftlPath:模板文件所在的路径;toPath:要生成的文件路径

Test.java 文件可以不用删,freemarker生成文件会自动覆盖

6-2 controller层和service层代码生成

小节表持久层代码生成

1.小节表section表结构设计

2.生成section表持久层代码

本章将以小节表的增删改查功能为例,来制作代码生成器

mybatis generator 支持同时生成多个table,但我们最好每次只生成一张表,其它表都注释掉

点击mybatis-generator运行 

service层代码生成

1.制作FreemarkerUtil,简化生成器的使用

2.新增ServerGenerator,用于生成后端代码:controller service dto

制作freemarker 工具类,将通用代码提取成公共方法。

ftlPath 是统一放模板的地方,这个属性值一般不会变,所以可以直接放到工具类里。

我们会用代码生成器生成controller service dto vue 的代码,这些代码的文件路径都不一样,toPath 是变化的,所以把toPath 变成入参。

复制test.ftl

然后运行ServerGenerator,可以看到,是对应进行变化的

1.生成器开发:增加生成service层的代码

Paste without Formatting(早期版本叫Paste Simple):不带格式化的粘贴,复制的文本长什么样子,粘贴出来就长什么样,idea不会自动格式化

选择第二个Paste Plain Text 

package com.course.server.service;import com.course.server.domain.${Domain};
import com.course.server.domain.${Domain}Example;
import com.course.server.dto.${Domain}Dto;
import com.course.server.dto.PageDto;
import com.course.server.mapper.${Domain}Mapper;
import com.course.server.util.CopyUtil;
import com.course.server.util.UuidUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.util.StringUtil;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;@Service
public class ${Domain}Service {@Resourceprivate ${Domain}Mapper ${domain}Mapper;/*** 列表查询*/public void list(PageDto pageDto) {PageHelper.startPage(pageDto.getPage(), pageDto.getSize());${Domain}Example ${domain}Example = new ${Domain}Example();List<${Domain}> ${domain}List = ${domain}Mapper.selectByExample(${domain}Example);PageInfo<${Domain}> pageInfo = new PageInfo<>(${domain}List);pageDto.setTotal(pageInfo.getTotal());List<${Domain}Dto> ${domain}DtoList = CopyUtil.copyList(${domain}List, ${Domain}Dto.class);pageDto.setList(${domain}DtoList);}/*** 保存,id有值时更新,无值时新增*/public void save(${Domain}Dto ${domain}Dto) {${Domain} ${domain} = CopyUtil.copy(${domain}Dto, ${Domain}.class);if (StringUtil.isEmpty(${domain}Dto.getId())){this.insert(${domain});}else {this.update(${domain});}}/*** 新增*/private void insert(${Domain} ${domain}) {//目前使用BeanUtil.copyProperties,需要多行代码,后续会对其做封装优化。${domain}.setId(UuidUtil.getShortUuid());${domain}Mapper.insert(${domain});}/*** 更新*/private void update(${Domain} ${domain}) {${domain}Mapper.updateByPrimaryKey(${domain});}/*** 删除*/public void delete(String id) {${domain}Mapper.deleteByPrimaryKey(id);}
}

运行一下ServerGenerator

没有报错,发现没有SectionDto

复制domain中的Section到dto中,改名为SectionDto 

controller层代码生成

1.生成器开发:增加生成controller层的代码

和service.ftl一样的道理,复制ChapterController到controller.ftl

package com.course.business.controller.admin;import com.course.server.dto.${Domain}Dto;
import com.course.server.dto.PageDto;
import com.course.server.dto.ResponseDto;
import com.course.server.service.${Domain}Service;
import com.course.server.util.ValidatorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;@RestController
@RequestMapping("/admin/${domain}")
public class ${Domain}Controller {private static final Logger LOG = LoggerFactory.getLogger(${Domain}Controller.class);public static final String BUSINESS_NAME = "大章";@Resourceprivate ${Domain}Service ${domain}Service;/*** 列表查询*/@PostMapping("/list")public ResponseDto list(@RequestBody PageDto pageDto){//LOG.info("pageDto:{}",pageDto);ResponseDto responseDto = new ResponseDto();${domain}Service.list(pageDto);responseDto.setContent(pageDto);return responseDto;}/*** 保存,id有值时更新,无值时新增*/@PostMapping("/save")public ResponseDto save(@RequestBody ${Domain}Dto ${domain}Dto){//LOG.info("${domain}Dto:{}",${domain}Dto);//保存校验ResponseDto responseDto = new ResponseDto();${domain}Service.save(${domain}Dto);responseDto.setContent(${domain}Dto);return responseDto;}/*** 删除*/@DeleteMapping("/delete/{id}")public ResponseDto delete(@PathVariable String id){//LOG.info("id:{}",id);ResponseDto responseDto = new ResponseDto();${domain}Service.delete(id);return responseDto;}
}


注意:每次执行生成器,都会重新生成service 和controller 代码,只是因为service 生成后没变化,所以没在changlist 中显示。

6-3 dto层代码生成

dto层代码生成与生成器优化

1.生成器开发:解决controller 模板中的中文业务名称和模块名称问题

解决上一个问题

dto层代码生成

1.生成器开发:增加dto 生成

dto 层的生成,需要知道表的所有字段,每个字段的类型需要映射成java类型,并且要把字段的变成小驼峰和大驼峰,比如course_id变成courseId,CourseId等。

Field 类,用于存储每个字段的信息。流程:根据表名获取所有的字段信息,再将字段信息填充到Field类中,得到Field列表,之后将Field列表变量传入模板

Field.java

package com.course.generator.util;public class Field {private String name; // 字段名:course_idprivate String nameHump; // 字段名小驼峰:courseIdprivate String nameBigHump; // 字段名大驼峰:CourseIdprivate String nameCn; // 中文名:课程private String type; // 字段类型:char(8)private String javaType; // java类型:Stringprivate String comment; // 注释:课程|IDpublic String getName() {return name;}public void setName(String name) {this.name = name;}public String getNameHump() {return nameHump;}public void setNameHump(String nameHump) {this.nameHump = nameHump;}public String getNameBigHump() {return nameBigHump;}public void setNameBigHump(String nameBigHump) {this.nameBigHump = nameBigHump;}public String getNameCn() {return nameCn;}public void setNameCn(String nameCn) {this.nameCn = nameCn;}public String getType() {return type;}public void setType(String type) {this.type = type;}public String getJavaType() {return javaType;}public void setJavaType(String javaType) {this.javaType = javaType;}public String getComment() {return comment;}public void setComment(String comment) {this.comment = comment;}
}

DbUtil.java

package com.course.generator.util;import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class DbUtil {public static Connection getConnection() {Connection conn = null;try {Class.forName("com.mysql.cj.jdbc.Driver");String url = "jdbc:mysql://localhost:3306/courseimooc";String user = "courseimooc";String pass = "root";conn = DriverManager.getConnection(url, user, pass);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}return conn;}/*** 获得表注释* @param tableName* @return* @throws Exception*/public static String getTableComment(String tableName) throws Exception {Connection conn = getConnection();Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery("select table_comment from information_schema.tables Where table_name = '" + tableName + "'");String tableNameCH = "";if (rs != null) {while(rs.next()) {tableNameCH = rs.getString("table_comment");break;}}rs.close();stmt.close();conn.close();System.out.println("表名:" + tableNameCH);return tableNameCH;}/*** 获得所有列信息* @param tableName* @return* @throws Exception*/public static List<Field> getColumnByTableName(String tableName) throws Exception {List<Field> fieldList = new ArrayList<>();Connection conn = getConnection();Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery("show full columns from " + tableName + "");if (rs != null) {while(rs.next()) {String columnName = rs.getString("Field");String type = rs.getString("Type");String comment = rs.getString("Comment");String nullAble = rs.getString("Null"); //YES NOField field = new Field();field.setName(columnName);field.setNameHump(lineToHump(columnName));field.setNameBigHump(lineToBigHump(columnName));field.setType(type);field.setJavaType(DbUtil.sqlTypeToJavaType(rs.getString("Type")));field.setComment(comment);if (comment.contains("|")) {field.setNameCn(comment.substring(0, comment.indexOf("|")));} else {field.setNameCn(comment);}fieldList.add(field);}}rs.close();stmt.close();conn.close();System.out.println("列信息:" + fieldList);return fieldList;}/*** 下划线转小驼峰*/public static String lineToHump(String str){Pattern linePattern = Pattern.compile("_(\\w)");str = str.toLowerCase();Matcher matcher = linePattern.matcher(str);StringBuffer sb = new StringBuffer();while(matcher.find()){matcher.appendReplacement(sb, matcher.group(1).toUpperCase());}matcher.appendTail(sb);return sb.toString();}/*** 下划线转大驼峰*/public static String lineToBigHump(String str){String s = lineToHump(str);return s.substring(0, 1).toUpperCase() + s.substring(1);}/*** 数据库类型转为Java类型*/public static String sqlTypeToJavaType(String sqlType) {if (sqlType.toUpperCase().contains("varchar".toUpperCase())|| sqlType.toUpperCase().contains("char".toUpperCase())|| sqlType.toUpperCase().contains("text".toUpperCase())) {return "String";} else if (sqlType.toUpperCase().contains("datetime".toUpperCase())) {return "Date";} else if (sqlType.toUpperCase().contains("int".toUpperCase())) {return "Integer";} else if (sqlType.toUpperCase().contains("long".toUpperCase())) {return "Long";} else if (sqlType.toUpperCase().contains("decimal".toUpperCase())) {return "BigDecimal";} else {return "String";}}
}

ServerGenerator.java

package com.course.generator.server;import com.course.generator.util.DbUtil;
import com.course.generator.util.Field;
import com.course.generator.util.FreemarkerUtil;import java.util.*;public class ServerGenerator {static String MODULE = "business";static String toDtoPath = "server/src/main/java/com/course/server/dto/";static String toServicePath = "server/src/main/java/com/course/server/service/";static String toControllerPath = MODULE + "/src/main/java/com/course/"+MODULE+"/controller/admin/";public static void main(String[] args) throws Exception {String Domain = "Section";String domain = "section";String tableNameCn = "小节";String module = MODULE;List<Field> fieldList = DbUtil.getColumnByTableName(domain);Set<String> typeSet = getJavaTypes(fieldList);Map<String, Object> map = new HashMap<>();map.put("Domain",Domain);map.put("domain",domain);map.put("tableNameCn",tableNameCn);map.put("module",module);map.put("fieldList",fieldList);map.put("typeSet",typeSet);//生成dtoFreemarkerUtil.initConfig("dto.ftl");FreemarkerUtil.generator(toDtoPath + Domain + "dto.java",map);//生成serviceFreemarkerUtil.initConfig("service.ftl");FreemarkerUtil.generator(toServicePath + Domain + "Service.java",map);//生成controllerFreemarkerUtil.initConfig("controller.ftl");FreemarkerUtil.generator(toControllerPath + Domain + "Controller.java",map);}/*** 获取所有的Java类型,使用Set去重,这个就是用来import使用的。*/private static Set<String> getJavaTypes(List<Field> fieldList) {Set<String> set = new HashSet<>();for (int i = 0; i < fieldList.size(); i++) {Field field = fieldList.get(i);set.add(field.getJavaType());}return set;}
}

typeSet作用:整理出所有用到的Java 类型,生成import 语句。一种类型只需import 一次,所以用set 去重

JsonFormat 注解:将后端日期类型格式化,再返回给前端

dto.ftl

package com.course.server.dto;<#list typeSet as type><#if type=='Date'>import java.util.Date;import com.fasterxml.jackson.annotation.JsonFormat;</#if><#if type=='BigDecimal'>import java.math.BigDecimal;</#if>
</#list>public class ${Domain}Dto {<#list fieldList as field>/*** ${field.comment}*/<#if field.javaType=='Date'>@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")</#if>private ${field.javaType} ${field.nameHump};</#list>
<#list fieldList as field>public ${field.javaType} get${field.nameBigHump}() {return ${field.nameHump};}public void set${field.nameBigHump}(${field.javaType} ${field.nameHump}) {this.${field.nameHump} = ${field.nameHump};}</#list>@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
<#list fieldList as field>sb.append(", ${field.nameHump}=").append(${field.nameHump});
</#list>
sb.append("]");
return sb.toString();
}}

运行ServerGenerator,生成SectionDto

通用代码生成器和mybatis generator 整合

1.生成器开发:代码生成器和mybatis generator 整合

generatorConfig.xml

问题:新表生成代码时,generatorConfig.xml得改动,ServerGenerator 代码也要改动。

                            ServerGenerator.java

package com.course.generator.server;import com.course.generator.util.DbUtil;
import com.course.generator.util.Field;
import com.course.generator.util.FreemarkerUtil;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.File;
import java.util.*;public class ServerGenerator {static String MODULE = "business";static String toDtoPath = "server/src/main/java/com/course/server/dto/";static String toServicePath = "server/src/main/java/com/course/server/service/";static String toControllerPath = MODULE + "/src/main/java/com/course/"+MODULE+"/controller/admin/";static String generatorConfigPath = "server/src/main/resources/generator/generatorConfig.xml";public static void main(String[] args) throws Exception {String module = MODULE;// 只生成配置文件中的第一个table节点File file = new File(generatorConfigPath);SAXReader reader = new SAXReader();//读取xml文件到Document中Document doc = reader.read(file);//获取xml文件的根节点Element rootElement = doc.getRootElement();//读取context节点Element contextElement = rootElement.element("context");//定义一个Element用于遍历Element tableElement;//取第一个“table”的节点tableElement = contextElement.elementIterator("table").next();String Domain = tableElement.attributeValue("domainObjectName");String tableName = tableElement.attributeValue("tableName");String tableNameCn = DbUtil.getTableComment(tableName);String domain = Domain.substring(0, 1).toLowerCase() + Domain.substring(1);System.out.println("表:" + tableElement.attributeValue("tableName"));System.out.println("Domain:" + tableElement.attributeValue("domainObjectName"));//获取所有的类型,名称啊,类型啊,注释啊。List<Field> fieldList = DbUtil.getColumnByTableName(tableName);//进行去重,为了import,就是类型Set<String> typeSet = getJavaTypes(fieldList);Map<String, Object> map = new HashMap<>();map.put("Domain",Domain);map.put("domain",domain);map.put("tableNameCn",tableNameCn);map.put("module",module);map.put("fieldList",fieldList);map.put("typeSet",typeSet);//生成dtoFreemarkerUtil.initConfig("dto.ftl");FreemarkerUtil.generator(toDtoPath + Domain + "dto.java",map);//生成serviceFreemarkerUtil.initConfig("service.ftl");FreemarkerUtil.generator(toServicePath + Domain + "Service.java",map);//生成controllerFreemarkerUtil.initConfig("controller.ftl");FreemarkerUtil.generator(toControllerPath + Domain + "Controller.java",map);}/*** 获取所有的Java类型,使用Set去重,这个就是用来import使用的。*/private static Set<String> getJavaTypes(List<Field> fieldList) {Set<String> set = new HashSet<>();for (int i = 0; i < fieldList.size(); i++) {Field field = fieldList.get(i);set.add(field.getJavaType());}return set;}
}

优化:ServerGenerator 的配置不再是写死的,而是去读generatorConfig.xml 的配置。

父pom.xml

pom.xml(generator) 


6-4 前端vue界面代码生成

Vue cli 多环境处理

1.增加vue多环境配置,后端地址改为读环境变量

问题:生产环境和开发环境的地址是不一样的,所以不能把固定的ip 地址写在代码里。

NODE_ENV(node environment):用来表示构建项目的当前环境

自定义多环境变量必须以VUE_APP开头

main.js

代码中使用环境变量:process.env.XXX

 package.json

chapter.vue

新建VueGenerator 用于生成vue页面代码

1.生成器开发:增加生成vue 界面代码。

2.小节管理增删改查功能测试成功

新建vue.ftl ,代码从chapter.vue 复制过来。

将模板中特定的代码替换成变量

chapter->${domain}

Chapter->${Domain}

business->${module}

大章->${tableNameCn}

从dto.ftl 复制过来一些代码

先把保存校验删去

 接着,我们来制作生成器

复制ServerGenerator.java 改名字VueGenerator.java

package com.course.generator.vue;import com.course.generator.util.DbUtil;
import com.course.generator.util.Field;
import com.course.generator.util.FreemarkerUtil;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.File;
import java.util.*;public class VueGenerator {static String MODULE = "business";static String toVuePath = "admin/src/views/admin/";//static String toVuePath = "admin\\src\\views\\admin\\";windowsstatic String generatorConfigPath = "server/src/main/resources/generator/generatorConfig.xml";public static void main(String[] args) throws Exception {String module = MODULE;// 只生成配置文件中的第一个table节点File file = new File(generatorConfigPath);SAXReader reader = new SAXReader();//读取xml文件到Document中Document doc = reader.read(file);//获取xml文件的根节点Element rootElement = doc.getRootElement();//读取context节点Element contextElement = rootElement.element("context");//定义一个Element用于遍历Element tableElement;//取第一个“table”的节点tableElement = contextElement.elementIterator("table").next();String Domain = tableElement.attributeValue("domainObjectName");String tableName = tableElement.attributeValue("tableName");String tableNameCn = DbUtil.getTableComment(tableName);String domain = Domain.substring(0, 1).toLowerCase() + Domain.substring(1);System.out.println("表:" + tableElement.attributeValue("tableName"));System.out.println("Domain:" + tableElement.attributeValue("domainObjectName"));//获取所有的类型,名称啊,类型啊,注释啊。List<Field> fieldList = DbUtil.getColumnByTableName(tableName);//进行去重,为了import,就是类型Set<String> typeSet = getJavaTypes(fieldList);Map<String, Object> map = new HashMap<>();map.put("Domain",Domain);map.put("domain",domain);map.put("tableNameCn",tableNameCn);map.put("module",module);map.put("fieldList",fieldList);map.put("typeSet",typeSet);//生成vueFreemarkerUtil.initConfig("vue.ftl");FreemarkerUtil.generator(toVuePath + domain + ".vue",map);}/*** 获取所有的Java类型,使用Set去重,这个就是用来import使用的。*/private static Set<String> getJavaTypes(List<Field> fieldList) {Set<String> set = new HashSet<>();for (int i = 0; i < fieldList.size(); i++) {Field field = fieldList.get(i);set.add(field.getJavaType());}return set;}
}

Copy Relative Path:复制相对路径;Copy Path:复制全路径;

 运行VueGenerator.java,看效果

从上面我们能够看到,vue.ftl是有缩进的格式,你可以直接复制<template></template>放到chapter.vue,利用这个页面的缩进对vue.ftl文件中的<template></template>进行修改,然后再复制回来,再次生成就可以了

如果有不满意的格式,我们可以进行调整

我们只需要调整vue.ftl

 再次生成就可以了

admin.vue

router.js 

可以看出来,模板生成文件这里少了},所以我们需要修改一下模板 

再次生成就可以了

然后进行测试,测试每个字段的编辑功能是否都生效。

把Cancel换成中文

6-5 字段校验和通用字段的处理

前后端模板增加字段校验

1.生成器开发:前后端模板增加字段校验

约定:当length>0时,表示需要对length做校验,当length=0时,表示不需要校验

疑问:char 类型为什么不需要校验长度?

解答:char类型一般用于固定长度的字段,常见的有id字段和枚举字段,id 字段不需要校验,枚举字段界面一般会有下拉框,不是手输的,不需要校验。

DbUtil.java

controller.ftl

 vue.ftl

1! = 1 的设计,类似于mybatis 的动态sql 设计,在拼动态where 条件时,会在前面加 1==1

可以删除id 这行的校验

点进来

测试

拓展:

https://cdn.bootcdn.net/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js

有时候会打不开,问题:cdn.bootcss.com 偶尔会出现404,导致jquery.blockUI.min.js 没下载下来。原因可能是cdn 不稳定,也可能是本地网络不稳定。

解决方法:

1.将jquery.blockUI.min.js 复制到本地,不使用cdn

 

通用字段的处理

1.生成器开发:通用字段的处理,id,createdAt,sort

controller.ftl

service.ftl 

 

vue.ftl

生成后端,再生成前端

idea 可以设置自动优化导入代码,对import代码做整理:整理顺序,自动增加需要的,去掉没用的import

实际开发中,是先写好SectionService 代码,再根据代码去修改模板。再用模板生成代码,测试生成的代码和手写的代码一致。

controller.ftl 模板改动了,但SectionController 之前已经手动把id 的校验删除并提交了,所以本次生成的代码没有变动。

拓展:1.追加提交

如果只是commit ,就可以进行追加提交,会显示在同一次提交,提交信息是最新一次写的内容,如果已经push,就没办法追加提交了 

(我的代码中createdAt单词有错误,还需要mybatis-generator执行一下)

6-6 前端枚举代码生成

前端下拉框和表格枚举的设计

1.枚举字段,表格显示中文,表单显示下拉框

2.vue中增加过滤器

 filter.js

main.js

section.vue

1.增加枚举常量

对于下拉框的列表值,一般会提取成全局的常量,方便多个页面使用。

enums.js

index.html

section.vue

说明是正确的,改回来

 

新增EnumGenerator 用于生成前端枚举

1.增加枚举生成器EnumGenerator

2.optionKV 过滤器从list 改为object

会员状态一般有正常、注销、冻结等。这些状态不是在页面上选择的,而是会员注册的时候,默认是正常

SectionChargeEnum.java

直接写“C” 还有一个缺点,就是从代码上看不出来“C”是什么意思,后期很难维护。

枚举名称是给开发人员用的,code 是给程序用的,desc 是给用户用的。

从前后端数据传输,到数据库存储,都是用的C或F

SectionService.java

问题:前端的枚举缺少枚举类型的信息,后续我们会碰到直接在前端用枚举值,我们的写法是直接SECTION_CHARGE.CHARGE.key,而不是直接写“C”

enums.js

filter.js

/*** 数组过滤器 例如:{{SECTION_CHARGE | optionKV(section.charge)}}* @param object 例如:{CHARGE:{key:"C",value:"收费"},FREE:{key:"F",value:"免费"}}* @param key  例如:C* @returns {string} 例如:收费*/
let optionKV = (object,key) => {if(!object || !key) {return "";}else {let result = "";for (let enums in object) {console.log(object[enums]["key"]);if (key === object[enums]["key"]){result = object[enums]["value"];}}return result;}
};
/*** 数组过滤器* @param list 例如:[{key:"C",value:"收费"},{key:"F",value:"免费"}]* @param key  例如:C* @returns {string} 例如:收费*/
let optionKVArray = (list,key) => {if(!list || !key) {return "";}else {let result = "";for (let i = 0; i < list.length; i++) {if (key === list[i]["key"]){result = list[i]["value"];}}return result;}
};
export default {optionKV
}

section.vue

为了测试,修改一下,方便看

 测试成功,改回来

开始配置枚举生成器EnumGenerator

generator引入依赖 server

pom.xml(generator)

EnumGenerator.java

package com.course.generator.enums;import com.course.server.enums.SectionChargeEnum;
import com.course.server.enums.YesNoEnum;import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class EnumGenerator {static String path = "admin/public/static/js/enums.js";public static void main(String[] args) {StringBuffer bufferObject = new StringBuffer();StringBuffer bufferArray = new StringBuffer();long begin = System.currentTimeMillis();try {toJson(SectionChargeEnum.class,bufferObject,bufferArray);toJson(YesNoEnum.class,bufferObject,bufferArray);StringBuffer buffer = bufferObject.append("\r\n").append(bufferArray);writeJs(buffer);}catch (Exception e){e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("执行耗时:"+(end - begin) + "毫秒");}private static void toJson(Class clazz,StringBuffer bufferObject,StringBuffer bufferArray)throws Exception{String key = toUnderline(clazz.getSimpleName());toJson(clazz,key,bufferObject,bufferArray);}private static void toJson(Class clazz, String key, StringBuffer bufferObject, StringBuffer bufferArray)throws Exception {Object[] objects = clazz.getEnumConstants();//原理:通过反射,得到枚举类的枚举类型、code、desc,然后通过字符串拼接的方式,// 得到最终完整的json 字符串,再通过写文件的方式直接写入enums.js。// 小提示:也可以用freemarker 模板的方式实现。Method name = clazz.getMethod("name");Method getDesc = clazz.getMethod("getDesc");Method getCode = clazz.getMethod("getCode");//生成对象bufferObject.append(key).append("={");for (int i = 0; i < objects.length; i++) {Object obj = objects[i];if (getCode == null){bufferObject.append(name.invoke(obj)).append(":{key:\"").append(name.invoke(obj)).append("\",value:\"").append(getDesc.invoke(obj)).append("\"}");}else{bufferObject.append(name.invoke(obj)).append(":{key:\"").append(getCode.invoke(obj)).append("\",value:\"").append(getDesc.invoke(obj)).append("\"}");}if (i < objects.length - 1){bufferObject.append(",");}}bufferObject.append("};\r\n");//生成数组bufferArray.append(key).append("_ARRAY=[");for (int i = 0; i < objects.length; i++) {Object obj = objects[i];if (getCode == null){bufferArray.append("{key:\"").append(name.invoke(obj)).append("\",value:\"").append(getDesc.invoke(obj)).append("\"}");}else{bufferArray.append("{key:\"").append(getCode.invoke(obj)).append("\",value:\"").append(getDesc.invoke(obj)).append("\"}");}if (i < objects.length - 1){bufferArray.append(",");}}bufferArray.append("];\r\n");}/*** 写文件* @param stringBuffer*/public static void writeJs(StringBuffer stringBuffer){FileOutputStream out = null;try {out = new FileOutputStream(path);OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");System.out.println(path);osw.write(stringBuffer.toString());osw.close();}catch (Exception e){e.printStackTrace();}finally {try {out.close();}catch (Exception e){e.printStackTrace();}}}/*** 功能:驼峰转大写下划线,并去掉_ENUM* 如:SectionChargeEnum 变成SECTION_CHARGE* @param str* @return*/public static String toUnderline(String str){String result = underline(str).toString();return result.substring(1,result.length()).toUpperCase().replace("_ENUM","");}/*** 驼峰转下划线,第一位是下划线* 如:SectionChargeEnum 变成_section_charge_enum* @param str* @return*/private static StringBuffer underline(String str){Pattern pattern = Pattern.compile("[A-Z]");Matcher matcher = pattern.matcher(str);StringBuffer sb = new StringBuffer(str);if(matcher.find()){sb = new StringBuffer();matcher.appendReplacement(sb,"_"+matcher.group(0).toLowerCase());matcher.appendTail(sb);}else {return sb;}return underline(sb.toString());}
}

原理:通过反射,得到枚举类的枚举类型、code、desc,然后通过字符串拼接的方式,得到最终完整的json 字符串,再通过写文件的方式直接写入enums.js。小提示:也可以用freemarker 模板的方式实现。

YesNoEnum.java

运行EnumGenerator

1.vue 生成器为组件名称增加模块名作为前缀

注意:如果手动改过代码,重新生成代码时,需要用代码比对的方式,将手动改过的代码还原回来

section.vue

vue.ftl

filter.js

不会有报错了 

6-7 生成器综合示例

课程管理功能开发

1.通过生成器,完成基本的课程管理功能

注意:数据存储的是以秒为单位的整数,比如36000,页面显示会时分秒,比如10:00:00.并且这个字段是自动生成的,不是手工填写的,后续会介绍。

如果你直接执行这段代码不成功,可以先把最后的created_at和updated_at字段删除,再设计表进行添加 

提示:日期字段都是用datatime(3),精确到毫秒。界面显示可以只到秒,但是落库要精确。

如果你在设计表时,添加的是时间戳,那么你就可以在插入语句,省去 created_at和updated_at字段

CourseLevelEnum.java

CourseStatusEnum.java

CourseChargeEnum.java

generatorConfig.xml

先停掉所有的应用,然后首先生成持久层代码mybatis-generator

然后生成服务端代码ServerGenerator

再生成前端代码VueGenerator

现在生成完成,找到前端admin.vue

增加路由router.js

接着,就可以启动测试

加上?c

freemarker有很多内置函数,方便对数据做各种操作、格式化等。

同样,前端也是有这样的问题

一样的处理方法

生成器的功能会随着使用场景的增加而不断扩展。

然后,我们再次生成一次VueGenerator

生成ServerGenerator

进行测试

针对枚举类型做一下处理

EnumGenerator.java生成一下

enums.js 

course.vue增加枚举

本章小结

我们这一章写了三个生成器,后端,前端,枚举,再配合上持久层生成器,可以快速的完成单表增删改查功能。

通过刚才课程管理的演示,如果没有枚举类似字段,基本可以在2分钟之内完成单表增删改查。大家也可以再做升级,把枚举字段的代码一起生成。

技术扩展:自制自己项目的代码生成器,导出复杂excel;生成静态页面等。freemarker的使用核心就是制作模板。

用jar包的方式很难导出复杂的excel,使用模板可以轻松导出复杂excel,包括各种合并单元格、字体、背景设置等。

先设计好最终要导出的excel的样子,再通过这个excel制作模板。

保存为xml格式

可以在这上面添加数据模板

6-8 生成器升级作业

1.生成器开发:前端生成器升级,增加枚举字段的生成

生成器升级作业:将枚举类型的字段加入到代码生成器里面

我们的生成器的数据来源是数据库,所以在源头上加标识

all.sql

约定:枚举类型的字段,增加“枚举”中文,并且将对应的枚举类写在[]中

mybatis-generator再次生成,ServerGenerator

Field.java

DbUtil.java

这里也可以考虑将toUnderLine 方法提取到工具类中

vue.ftl

再次生成一次VueGenerator

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/231663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu 18.04配置NFS服务器以及配置时遇到NFS问题

1.安装相关软件 sudo apt-get install nfs-kernel-server sudo apt-get install nfs-common 2.配置共享目录 2.1修改exports文件 sudo vi /etc/exports在最后添加如下并保存退出 /home/xiaowu/nfs 192.168.31*(rw,sync,no_root_squash,no_subtree_check) /home/xiaowu/nfs…

计算机组成原理(输入输出系统-----程序查询方式)

目录 程序查询方式 一.程序查询方式的流程 1.查询流程 2.程序流程 二.程序查询方式的接口电路 程序查询方式 一.程序查询方式的流程 1.查询流程 单个设备&#xff1a; 如果在传输过程当中只有一个内存和I/O之间数据传输&#xff0c;在执行程序的过程当中CPU会执行出来一…

Windows11编译x265源码生成Visual Studio工程详细步骤

概述 x265是一款开源符合HEVC标准的编码器&#xff0c;也属于VLC项目之一。 由于x265是开源的&#xff0c;因此它得到了广泛的应用和开发。许多开源项目和商业产品都使用x265进行视频压缩处理。同时&#xff0c;x265也支持多种编程语言和平台&#xff0c;使得开发者可以方便地…

Docker与云计算平台集成:AWS、Azure、GCP完全指南

Docker和云计算平台的结合&#xff0c;如AWS&#xff08;Amazon Web Services&#xff09;、Azure&#xff08;Microsoft Azure&#xff09;和GCP&#xff08;Google Cloud Platform&#xff09;&#xff0c;为现代应用的构建和部署提供了巨大的便利性。本文将深入研究如何与这…

02.微服务组件 Eureka注册中心

1.Eureka注册中心 服务提供者与消费者&#xff1a; 服务提供者:一次业务中&#xff0c;被其它微服务调用的服务。(提供接口给其它微服务)服务消费者:一次业务中&#xff0c;调用其它微服务的服务。&#xff08;调用其它微服务提供的接口)一个服务是消费者还是提供者&#xff…

Redis设计与实现之Lua 脚本

目录 一、 Lua 脚本 1、初始化 Lua 环境 2、脚本的安全性 3、脚本的执行 4、 EVAL 命令的实现 定义 Lua 函数 执行 Lua 函数 5、 EVALSHA 命令的实现 二、 小结 一、 Lua 脚本 Lua 脚本功能是 Reids 2.6 版本的最大亮点&#xff0c;通过内嵌对 Lua 环境的支持&#xf…

Mysql之约束上篇

Mysql之约束上篇 约束的概述为什么需要约束什么是约束约束的分类 非空约束作用关键字特点添加非空约束删除非空约束 唯一性约束关键字特点添加唯一约束关于复合唯一约束删除唯一约束查看索引 主键约束(非空唯一性约束)作用关键字特点添加主键约束关于复合主键删除主 约束的概述…

探索拉普拉斯算子:计算机视觉中用于边缘检测和图像分析的关键工具

一、介绍 拉普拉斯算子是 n 维欧几里得空间中的二阶微分算子&#xff0c;表示为 ∇。它是函数梯度的发散度。在图像处理的上下文中&#xff0c;该运算符应用于图像的强度函数&#xff0c;可以将其视为每个像素具有强度值的二维信号。拉普拉斯算子是计算机视觉领域的关键工具&am…

了解 SBOM (软件物料清单)

近年来&#xff0c;开源软件在开发中的采用激增&#xff0c;目前已占已构建软件的高达 90%。它在全球公司中的受欢迎程度源于成本节约和产品上市时间的加快。然而&#xff0c;在集成开源软件组件时&#xff0c;有一个关键的方面需要考虑。 Synopsys 报告84% 的商业和专有代码库…

Qt-QTransform介绍与使用

QTransform是一个用于二维坐标系转换的类。我们知道Qt的坐标系是左上角为原点&#xff0c;x轴向右&#xff0c;y轴向下&#xff0c;屏幕上每个像素代表一个单位&#xff0c;那么&#xff0c;如果我们想要在屏幕上建立自己的坐标系用于绘制&#xff0c;就需要借助QTransform。 …

Guitar Pro8.1最新2024中文免激活版下载(附教程)

Guitar Pro 8是一款功能强大的指法阅读器和编辑器&#xff0c;它允许您编辑吉他、贝斯和尤克里里的乐谱和指法谱&#xff0c;并为鼓或钢琴创建背景音轨。轻松创建、播放和共享您的标签&#xff01;快速的进行乐谱播放并进行练习&#xff0c;也可以进行编辑操作&#xff0c;允许…

机器学习---推荐系统案例(一)

一、推荐系统-数据处理流程 推荐系统数据处理首先是将Hive中的用户app历史下载表与app浏览信息表按照设备id进行关联&#xff0c;然后将关联数据使用python文件进行处理&#xff0c;将数据预处理为label和feature两列的临时数据&#xff0c;后期经过处理转换成逻辑回归 模型的…

【经典LeetCode算法题目专栏分类】【第5期】贪心算法:分发饼干、跳跃游戏、模拟行走机器人

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 分发饼干 class Solutio…

【Qt之Quick模块】1. 概述及Quick应用程序创建流程

概述 Qt的Quick模块是用于创建现代化、动态和响应式用户界面的工具集。它是基于QML&#xff08;Qt Meta-Object Language&#xff09;和JavaScript的。 QML是一种声明性的语言&#xff0c;用于描述用户界面的结构和行为。它使用层叠样式表&#xff08;CSS&#xff09;的语法来…

Apache Flume(5):多个agent模型

可以将多个Flume agent 程序连接在一起&#xff0c;其中一个agent的sink将数据发送到另一个agent的source。Avro文件格式是使用Flume通过网络发送数据的标准方法。 从多个Web服务器收集日志&#xff0c;发送到一个或多个集中处理的agent&#xff0c;之后再发往日志存储中心&…

电脑操作系统深度剖析:Windows、macOS和Linux的独特特性及应用场景

导言 电脑操作系统是计算机硬件和应用软件之间的桥梁&#xff0c;不同的操作系统在用户体验、性能和安全性方面有着独特的特色。电脑操作系统是计算机系统中的核心组件&#xff0c;不同的操作系统在设计理念、用户体验和应用领域上存在显著差异。本文将深入探讨几种常见的电脑操…

安全芯片是什么?为什么可以应用在加密卡上?

安全芯片是指芯片内带有微处理器CPU、随机数发生器、硬件密码算法、存储单元&#xff08;包括随机存储器RAM、程序存储器ROM&#xff08;FLASH&#xff09;、用户数据存储器EEPROM&#xff09;以及芯片操作系统COS的智能芯片&#xff0c;相当于一台微型计算机&#xff0c;不仅具…

【经典LeetCode算法题目专栏分类】【第6期】二分查找系列:x的平方根、有效完全平方数、搜索二位矩阵、寻找旋转排序数组最小值

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; X的平方根 class Soluti…

【Image】图像处理

计算机视觉 CV Perception 如自动驾驶领域。 只要是从所谓的图像当中去抽取信息的过程&#xff0c;我们都叫做Perception。 视觉检测可以涵盖二维检测&#xff0c;如车辆、人和信号灯的检测。另外&#xff0c;还可以控制三维信息&#xff0c;直接在三维空间中操作数据。 SL…

鸿蒙OS:打破界限的操作系统新星

导言 鸿蒙OS&#xff08;HarmonyOS&#xff09;是华为公司为应对技术封锁而推出的分布式操作系统&#xff0c;其背后蕴含着华为构建全球数字生活愿景的雄心。本文将深入剖析鸿蒙OS的起源、核心特性&#xff0c;并展望其未来在数字生态中的角色。 1. 背景与起源 华为的…