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 {
@Resource
private ${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 = "大章";
@Resource
private ${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_id
private String nameHump; // 字段名小驼峰:courseId
private String nameBigHump; // 字段名大驼峰:CourseId
private String nameCn; // 中文名:课程
private String type; // 字段类型:char(8)
private String javaType; // java类型:String
private String comment; // 注释:课程|ID
public 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 NO
Field 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);
//生成dto
FreemarkerUtil.initConfig("dto.ftl");
FreemarkerUtil.generator(toDtoPath + Domain + "dto.java",map);
//生成service
FreemarkerUtil.initConfig("service.ftl");
FreemarkerUtil.generator(toServicePath + Domain + "Service.java",map);
//生成controller
FreemarkerUtil.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);
//生成dto
FreemarkerUtil.initConfig("dto.ftl");
FreemarkerUtil.generator(toDtoPath + Domain + "dto.java",map);
//生成service
FreemarkerUtil.initConfig("service.ftl");
FreemarkerUtil.generator(toServicePath + Domain + "Service.java",map);
//生成controller
FreemarkerUtil.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\\";windows
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);
//生成vue
FreemarkerUtil.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