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

news2024/11/25 13:32:36

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

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

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

相关文章

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

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

万兆网络之线路测速

网络测速有很多种方式&#xff0c;建议使用开源的iperf搭建测试 官方&#xff1a;iperf3&#xff08;技术网站一般不被和谐&#xff0c;有部分可能被污染&#xff09; Windows下载后解压即可运行 小技巧&#xff1a;如果你用的笔记本只有一个C盘&#xff0c;最好将免安装的软…

Zotero插件安装、问题、bug大全(随时更新)

Zotero插件安装、问题、bug大全&#xff08;随时更新&#xff09; 1. 插件安装2. 茉莉花&#xff08;Jasminum&#xff09;插件使用tips及可能遇到的问题2.1 更新2.2 未找到PDFtk Server的可执行文件 问题解决方法 3. Zotero Sci-hub插件相关问题3.1 Zotero Sci-hub插件有时抓取…

iOS问题记录 - iOS 17通过NSUserDefaults设置UserAgent无效

文章目录 前言开发环境问题描述问题分析解决方案最后 前言 最近维护一个老项目时遇到的问题。说起这老项目我就有点头疼&#xff0c;一个快十年前的项目&#xff0c;这么说你可能不觉得有什么&#xff0c;但是你想想Swift也才发布不到十年&#xff08;2014年6月发布&#xff0…

DS排序--快速排序

Description 给出一个数据序列&#xff0c;使用快速排序算法进行从小到大的排序 排序方式&#xff1a;以区间第一个数字为枢轴记录 输出方式&#xff1a;每一步区间排序&#xff0c;都输出整个数组 –程序要求– 若使用C只能include一个头文件iostream&#xff1b;若使用C…

深度学习笔记_7经典网络模型LSTM解决FashionMNIST分类问题

1、 调用模型库&#xff0c;定义参数&#xff0c;做数据预处理 import numpy as np import torch from torchvision.datasets import FashionMNIST import torchvision.transforms as transforms from torch.utils.data import DataLoader import torch.nn.functional as F im…

2000年AMC8数学竞赛中英文真题典型考题、考点分析和答案解析

今天是2023年12月19日&#xff0c;距离2024年的AMC8正式考试倒计时一个月。 从战争中学习战争最有效。前几天&#xff0c;六分成长分析了2023年、2022年、2020、2019、2018、2017的AMC8真题的典型考题、考点和详细答案解析。 今天我们不再从2016年分析&#xff0c;来看看更早…

pytorch文本分类(三)模型框架(DNNtextCNN)

pytorch文本分类&#xff08;三&#xff09;模型框架&#xff08;DNN&textCNN&#xff09; 原任务链接 目录 pytorch文本分类&#xff08;三&#xff09;模型框架&#xff08;DNN&textCNN&#xff09;1. 背景知识深度学习 2. DNN2.1 从感知器到神经网络2.2 DNN的基本…

避坑指南:uni-forms表单在uni-app中的实践经验

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;uni-app篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来JavaScript篇专栏内容:uni-app中forms表单的避坑指南篇 该篇章已被前端圈子收录,点此处进入即可查看更多优质内…

Pytorch nn.Linear()的基本用法与原理详解及全连接层简介

主要引用参考&#xff1a; https://blog.csdn.net/zhaohongfei_358/article/details/122797190 https://blog.csdn.net/weixin_43135178/article/details/118735850 nn.Linear的基本定义 nn.Linear定义一个神经网络的线性层&#xff0c;方法签名如下&#xff1a; torch.nn.Li…

AT32F403如何扩大SRAM

配置方法 使用雅特力的ICP 进行配置(可在官网下载) (1)当连接上芯片后,点击设备操作->选择字节 (2)选择224KB SRAM (3)然后点击应用到设备,(可以点击从设备加载,来看当前的配置) (4)打开keil5魔术棒图标 ,将Target中的IRAM1第二个选项从0x10000改为0x3800。…

虚拟电厂 能源物联新方向

今年有多热&#xff1f;据上海市气象局官微消息&#xff0c;5月29日13时09分&#xff0c;徐家汇站气温达36.1℃&#xff0c;打破了百年来的当地5月份气温*高纪录。不仅如此&#xff0c;北京、四川、江西、湖南、广东、广西等地也频频发布高温预警。 伴随着居民用电急剧攀升&am…

4.1 媒资管理模块 - Nacos与Gateway搭建

文章目录 媒资管理模块 - 媒资项目搭建一、需求分析1.1 介绍1.2 数据模型1.3 分析网关 二、 搭建Nacos2.1 服务发现中心2.2.1 Maven2.2.2 配置Nacos 2.2 配置中心2.2.1 介绍2.2.2 Maven 坐标2.2.3 配置 content-api 工程2.2.4 配置 content-service 工程2.2.5 配置 system-api …

基础算法(5):滑动窗口

1.何为滑动窗口&#xff1f; 滑动窗口其实也是一种算法&#xff0c;主要有两类&#xff1a;一类是固定窗口&#xff0c;一类是可变窗口。固定的窗口只需要一个变量记录&#xff0c;而可变窗口需要两个变量。 2.固定窗口 就像上面这个图一样。两个相邻的长度为4的红色窗口&…

HTML---CSS美化网页元素

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.div 标签&#xff1a; <div>是HTML中的一个常用标签&#xff0c;用于定义HTML文档中的一个区块&#xff08;或一个容器&#xff09;。它可以包含其他HTML元素&#xff0c;如文本、图像…

探秘 AJAX:让网页变得更智能的异步技术(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

如何编写好的测试用例?

对于软件测试工程师来说&#xff0c;设计测试用例和提交缺陷报告是最基本的职业技能。是非常重要的部分。一个好的测试用例能够指示测试人员如何对软件进行测试。在这篇文章中&#xff0c;我们将介绍测试用例设计常用的几种方法&#xff0c;以及如何编写高效的测试用例。 一、…

iPhone 17Pro/Max或升级4800万像素长焦镜头,配备自研Wi-Fi 7芯片。

iPhone 16未至&#xff0c;关于iPhone 17系列的相关消息就已经放出&#xff0c;到底是谁走漏了风声。 海通国际证券技术分析师Jeff Pu近日发布报告称&#xff0c;苹果将为2025年推出的iPhone 17ProMax配备4800万像素的长焦镜头。经调查&#xff0c;该分析师认为提升iPhone拍摄方…

如何在华为云上购买ECS及以镜像的方式部署华为云欧拉操作系统 (HCE OS)

写在前面 工作中遇到&#xff0c;简单整理博文内容为 华为云开发者认证 实验笔记https://edu.huaweicloud.com/certificationindex/developer/9bf91efb086a448ab4331a2f53a4d3a1理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在…

Nginx快速入门:Nginx应用场景、安装与部署(一)

1. Nginx简介 Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;也是一个非常流行的开源 Web 服务器软件。它是由俄罗斯程序员 Igor Sysoev 开发的&#xff0c;最初是为了解决在高并发场景下的C10k 问题&#xff08;即一个服务器进程只能处理 10,000 个并发连接&#x…