需求:
- 最近一段时间的工作中,发现代码自动生成模板比较单一,无法满足现有的代码规范。
- 好些代码模板中没有,需要手动编写代码
- 且生成的模板中没有办法设置校验规则,如:是否必填,字段长度判断等。
- 生成的代码,部分无法直接生成apifox,不需要手动编写接口文档。
- 生成apifox接口文档不规范,是否必填等内容还需要手动编写。
基于以上需要,整理了一套适合我们自己的代码模板,生成的模板能严格遵守我们的代码规范,且可以直接生成apifox。
安装插件EasyCode
代码自动生成插件
安装Apifox Helper插件
接口文档自动生成插件,我们接口都放在apifox中,如果是别的接口调试软件,请更换别的插件来生成代码。
Alibaba Java Coding Guidelines
阿里代码规约扫描软件
设置EasyCode模板
模板介绍
因为项目路径问题,不同的项目返回前段的公共包路径不一样,且有一些返回格式也不同,因此将不同的项目规范成为不同的代码生成模板。
以下我会以话务模板为例展示,其他模板,大家根据各自使用情况自行修改。
首先模板数量:
dto.java.vm
入参接受类,此类中会增加数据校验,且开发人员可以任意修改入参。
##引入宏定义
$!{define.vm}
##设置表后缀(宏定义)
#setTableSuffix("Dto")
##使用宏定义设置回调(保存位置与文件后缀)
#save("/controller/dto", "Dto.java")
##使用宏定义设置包后缀
#setPackageSuffix("controller.dto")
##使用全局变量实现默认包导入
$!{autoImport.vm}
import lombok.Data;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;
##使用宏定义实现类注释信息
#tableComment("实体类")
@Data
public class $!{tableName}{
#foreach($column in $tableInfo.fullColumn)
#if(${column.comment})
/**
* ${column.comment}
*/
#end
#if(${column.name} != "id"&&${column.obj.isNotNull()})
@NotNull(message = "${column.comment}不能为空")
#end
#if($!{tool.getClsNameByFullName($column.type)} == "String")
@Length(max = $!{column.obj.dataType.length}, message = "${column.comment}不能超过$!{column.obj.dataType.length}个字符")
#end
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end
}
controller.java.vm
##定义初始变量
#set($tableName = $tool.append($tableInfo.name, "Controller"))
##设置回调
$!callback.setFileName($tool.append($tableName, ".java"))
$!callback.setSavePath($tool.append($tableInfo.savePath, "/controller"))
##拿到主键
#if(!$tableInfo.pkColumn.isEmpty())
#set($pk = $tableInfo.pkColumn.get(0))
#end
#if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}controller;
import $!{tableInfo.savePackageName}.entity.$!{tableInfo.name};
import $!{tableInfo.savePackageName}.controller.dto.$!{tableInfo.name}Dto;
import $!{tableInfo.savePackageName}.service.$!{tableInfo.name}Service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.library.processing.controller.dto.CommonResult;
import org.springframework.web.bind.annotation.*;
import com.library.processing.controller.dto.IdDto;
import com.library.processing.controller.dto.SelectInputCommon;
import javax.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.annotation.Validated;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
/**
* $!{tableInfo.comment}($!{tableInfo.name})表控制层
*
* @author $!author
* @since $!time.currTime()
*/
@RestController
@RequestMapping("$!tool.firstLowerCase($tableInfo.name)")
public class $!{tableName} {
/**
* 服务对象
*/
@Resource
private $!{tableInfo.name}Service $!tool.firstLowerCase($tableInfo.name)Service;
/**
* 分页查询
*
* @param input 筛选条件
* @return 查询结果
*/
@PostMapping("list")
public CommonResult<Page<$!{tableInfo.name}>> queryByPage(@Validated @RequestBody SelectInputCommon<$!{tableInfo.name}Dto> input) {
Page<$!{tableInfo.name}> page=new Page<>();
BeanUtils.copyProperties(input.getPage(), page);
$!{tableInfo.name} $!{tool.firstLowerCase($tableInfo.name)} = new $!{tableInfo.name}();
BeanUtils.copyProperties(input.getToNullData(), $!{tool.firstLowerCase($tableInfo.name)});
return CommonResult.ok(this.$!{tool.firstLowerCase($tableInfo.name)}Service.page(page, new QueryWrapper<>($!{tool.firstLowerCase($tableInfo.name)})));
}
/**
* 通过主键查询单条数据
*
* @param idDto 主键
* @return 单条数据
*/
@PostMapping("getById")
public CommonResult<$!{tableInfo.name}> queryById(@Validated @RequestBody IdDto<$!pk.shortType> idDto) {
return CommonResult.ok(this.$!{tool.firstLowerCase($tableInfo.name)}Service.getById(idDto.getId()));
}
/**
* 新增数据
*
* @param $!{tool.firstLowerCase($tableInfo.name)}Dto 实体
* @return 新增结果
*/
@PostMapping("add")
public CommonResult<Boolean> add(@Validated @RequestBody $!{tableInfo.name}Dto $!{tool.firstLowerCase($tableInfo.name)}Dto) {
$!{tableInfo.name} $!{tool.firstLowerCase($tableInfo.name)} = new $!{tableInfo.name}();
BeanUtils.copyProperties($!{tool.firstLowerCase($tableInfo.name)}Dto, $!{tool.firstLowerCase($tableInfo.name)});
return CommonResult.ok(this.$!{tool.firstLowerCase($tableInfo.name)}Service.save($!{tool.firstLowerCase($tableInfo.name)}));
}
/**
* 编辑数据
*
* @param $!{tool.firstLowerCase($tableInfo.name)}Dto 实体
* @return 编辑结果
*/
@PostMapping("edit")
public CommonResult<Boolean> edit(@Validated @RequestBody $!{tableInfo.name}Dto $!{tool.firstLowerCase($tableInfo.name)}Dto) {
$!{tableInfo.name} $!{tool.firstLowerCase($tableInfo.name)} = new $!{tableInfo.name}();
BeanUtils.copyProperties($!{tool.firstLowerCase($tableInfo.name)}Dto, $!{tool.firstLowerCase($tableInfo.name)});
return CommonResult.ok(this.$!{tool.firstLowerCase($tableInfo.name)}Service.saveOrUpdate($!{tool.firstLowerCase($tableInfo.name)}));
}
/**
* 删除数据
*
* @param idDto 主键
* @return 删除是否成功
*/
@PostMapping("deleteById")
public CommonResult<Boolean> deleteById(@Validated @RequestBody IdDto<$!pk.shortType> idDto) {
return CommonResult.ok(this.$!{tool.firstLowerCase($tableInfo.name)}Service.removeById(idDto.getId()));
}
}
entity.java.vm
##引入宏定义
$!{define.vm}
##使用宏定义设置回调(保存位置与文件后缀)
#save("/entity", ".java")
##使用宏定义设置包后缀
#setPackageSuffix("entity")
##使用全局变量实现默认包导入
$!{autoImport.vm}
import java.io.Serializable;
import lombok.Data;
##使用宏定义实现类注释信息
#tableComment("实体类")
@Data
public class $!{tableInfo.name} implements Serializable {
private static final long serialVersionUID = $!tool.serial();
#foreach($column in $tableInfo.fullColumn)
#if(${column.comment})/**
* ${column.comment}
*/#end
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end
}
service.java.vm
##导入宏定义
$!{define.vm}
##设置表后缀(宏定义)
#setTableSuffix("Service")
##保存文件(宏定义)
#save("/service", "Service.java")
##包路径(宏定义)
#setPackageSuffix("service")
import com.baomidou.mybatisplus.extension.service.IService;
import $!{tableInfo.savePackageName}.entity.$!tableInfo.name;
##表注释(宏定义)
#tableComment("表服务接口")
public interface $!{tableName} extends IService<$!tableInfo.name> {
}
serviceImpl.java.vm
##导入宏定义
$!{define.vm}
##设置表后缀(宏定义)
#setTableSuffix("ServiceImpl")
##保存文件(宏定义)
#save("/service/impl", "ServiceImpl.java")
##包路径(宏定义)
#setPackageSuffix("service.impl")
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import $!{tableInfo.savePackageName}.dao.$!{tableInfo.name}Dao;
import $!{tableInfo.savePackageName}.entity.$!{tableInfo.name};
import $!{tableInfo.savePackageName}.service.$!{tableInfo.name}Service;
import org.springframework.stereotype.Service;
##表注释(宏定义)
#tableComment("表服务实现类")
@Service("$!tool.firstLowerCase($tableInfo.name)Service")
public class $!{tableName} extends ServiceImpl<$!{tableInfo.name}Dao, $!{tableInfo.name}> implements $!{tableInfo.name}Service {
}
dao.java.vm
##引入宏定义
$!{define.vm}
##设置表后缀(宏定义)
#setTableSuffix("Dto")
##使用宏定义设置回调(保存位置与文件后缀)
#save("/controller/dto", "Dto.java")
##使用宏定义设置包后缀
#setPackageSuffix("controller.dto")
##使用全局变量实现默认包导入
$!{autoImport.vm}
import lombok.Data;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;
##使用宏定义实现类注释信息
#tableComment("实体类")
@Data
public class $!{tableName}{
#foreach($column in $tableInfo.fullColumn)
#if(${column.comment})
/**
* ${column.comment}
*/
#end
#if(${column.name} != "id"&&${column.obj.isNotNull()})
@NotNull(message = "${column.comment}不能为空")
#end
#if($!{tool.getClsNameByFullName($column.type)} == "String")
@Length(max = $!{column.obj.dataType.length}, message = "${column.comment}不能超过$!{column.obj.dataType.length}个字符")
#end
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end
}
mapper.xml.vm
##引入mybatis支持
$!{mybatisSupport.vm}
##设置保存名称与保存位置
$!callback.setFileName($tool.append($!{tableInfo.name}, "Dao.xml"))
$!callback.setSavePath($tool.append($modulePath, "/src/main/resources/mapper"))
##拿到主键
#if(!$tableInfo.pkColumn.isEmpty())
#set($pk = $tableInfo.pkColumn.get(0))
#end
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="$!{tableInfo.savePackageName}.dao.$!{tableInfo.name}Dao">
</mapper>
代码生成过程
1.选择EasyCode工具
2. 选择要生成的代码地址(包名),选择要生成的文件,下面示例是我选择全部生成的文件,可以根据需要自行选择要生成的文件。
3.点击确定,生成成功。
生成代码如下
dto
package com.library.processing.callplatform.controller.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* 技能组(SkillCopy)实体类
*
* @author 李亮
* @since 2023-08-31 16:03:54
*/
@Data
public class SkillCopyDto {
/**
* 主键
*/
@NotNull(message = "主键不能为空")
private Integer id;
/**
* 技能组名称
*/
@NotNull(message = "技能组名称不能为空")
@Length(max = 50, message = "技能组名称不能超过50个字符")
private String skillName;
/**
* 类型(ring_all 所有空闲坐席立即全部振铃(群振)。 longest_idle 最长空闲时间坐席振铃 round 顺振,每次从上次振铃坐席下一个开始振铃 top_down 顺振,每次都从第一个坐席开始振铃 least_talk_time 最少通话时间坐席振铃 fewest_calls 最少接通量坐席振铃 random 随机振铃)
*/
@Length(max = 50, message = "类型(ring_all 所有空闲坐席立即全部振铃(群振)。 longest_idle 最长空闲时间坐席振铃 round 顺振,每次从上次振铃坐席下一个开始振铃 top_down 顺振,每次都从第一个坐席开始振铃 least_talk_time 最少通话时间坐席振铃 fewest_calls 最少接通量坐席振铃 random 随机振铃)不能超过50个字符")
private String strategy;
/**
* 最大排队时间(秒)
*/
private Integer maxWaitTime;
/**
* 排队等待音,wav音频文件的linux绝对路径
*/
@Length(max = 100, message = "排队等待音,wav音频文件的linux绝对路径不能超过100个字符")
private String nohSound;
/**
* 创建时间
*/
@NotNull(message = "创建时间不能为空")
private Date createTime;
/**
* 创建人名称
*/
@Length(max = 50, message = "创建人名称不能超过50个字符")
private String createUserName;
/**
* 创建人工号
*/
@Length(max = 50, message = "创建人工号不能超过50个字符")
private String createJobNumber;
/**
* 更新时间
*/
private Date updateTime;
/**
* 更新人名称
*/
@Length(max = 50, message = "更新人名称不能超过50个字符")
private String updateUserName;
/**
* 更新人工号
*/
@Length(max = 50, message = "更新人工号不能超过50个字符")
private String updateJobNumber;
/**
* 技能组名称拼音
*/
@NotNull(message = "技能组名称拼音不能为空")
@Length(max = 255, message = "技能组名称拼音不能超过255个字符")
private String skillNamePy;
}
controller
package com.library.processing.callplatform.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.library.processing.callplatform.controller.dto.SkillCopyDto;
import com.library.processing.callplatform.entity.SkillCopy;
import com.library.processing.callplatform.service.SkillCopyService;
import com.library.processing.controller.dto.CommonResult;
import com.library.processing.controller.dto.IdDto;
import com.library.processing.controller.dto.SelectInputCommon;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 技能组(SkillCopy)表控制层
*
* @author 李亮
* @since 2023-08-31 16:07:40
*/
@RestController
@RequestMapping("skillCopy")
public class SkillCopyController {
/**
* 服务对象
*/
@Resource
private SkillCopyService skillCopyService;
/**
* 分页查询
*
* @param input 筛选条件
* @return 查询结果
*/
@PostMapping("list")
public CommonResult<Page<SkillCopy>> queryByPage(@Validated @RequestBody SelectInputCommon<SkillCopyDto> input) {
Page<SkillCopy> page = new Page<>();
BeanUtils.copyProperties(input.getPage(), page);
SkillCopy skillCopy = new SkillCopy();
BeanUtils.copyProperties(input.getToNullData(), skillCopy);
return CommonResult.ok(this.skillCopyService.page(page, new QueryWrapper<>(skillCopy)));
}
/**
* 通过主键查询单条数据
*
* @param idDto 主键
* @return 单条数据
*/
@PostMapping("getById")
public CommonResult<SkillCopy> queryById(@Validated @RequestBody IdDto<Integer> idDto) {
return CommonResult.ok(this.skillCopyService.getById(idDto.getId()));
}
/**
* 新增数据
*
* @param skillCopyDto 实体
* @return 新增结果
*/
@PostMapping("add")
public CommonResult<Boolean> add(@Validated @RequestBody SkillCopyDto skillCopyDto) {
SkillCopy skillCopy = new SkillCopy();
BeanUtils.copyProperties(skillCopyDto, skillCopy);
return CommonResult.ok(this.skillCopyService.save(skillCopy));
}
/**
* 编辑数据
*
* @param skillCopyDto 实体
* @return 编辑结果
*/
@PostMapping("edit")
public CommonResult<Boolean> edit(@Validated @RequestBody SkillCopyDto skillCopyDto) {
SkillCopy skillCopy = new SkillCopy();
BeanUtils.copyProperties(skillCopyDto, skillCopy);
return CommonResult.ok(this.skillCopyService.saveOrUpdate(skillCopy));
}
/**
* 删除数据
*
* @param idDto 主键
* @return 删除是否成功
*/
@PostMapping("deleteById")
public CommonResult<Boolean> deleteById(@Validated @RequestBody IdDto<Integer> idDto) {
return CommonResult.ok(this.skillCopyService.removeById(idDto.getId()));
}
}
这里主要展示上述两个文件,其他文件不在过多赘述。其他文件和通用的内容没有什么区别。
Controller公共包补充:
CommonResult
package com.library.processing.controller.dto;
import java.io.Serializable;
import java.util.List;
public class CommonResult<T> extends BaseResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 数据
*/
private T data;
private void setData(T data) {
this.data = data;
}
public CommonResult<T> setResult(T data) {
this.setData(data);
return this;
}
private CommonResult() {
}
private CommonResult(int code, String desc) {
this.setCode(code);
this.setDesc(desc);
}
private CommonResult(int code) {
this.setCode(code);
}
private CommonResult(int code, String desc, T data) {
this.setCode(code);
this.setDesc(desc);
this.setData(data);
}
public static CommonResult ok() {
return ok("");
}
public static CommonResult ok(String desc) {
return baseCreate(BaseResult.CODE_SUCCESS, desc);
}
public static <T> CommonResult<T> ok(T data) {
return baseCreate(BaseResult.CODE_SUCCESS, "", data);
}
public static <T> CommonResult<T> ok(String desc, T data) {
return baseCreate(BaseResult.CODE_SUCCESS, desc, data);
}
public static CommonResult fail(int code, String desc) {
return baseCreate(code, desc);
}
private static <T> CommonResult<T> baseCreate(int code, String desc, T data) {
CommonResult<T> result = new CommonResult<T>();
result.setCode(code);
result.setDesc(desc);
result.setData(data);
return result;
}
private static <T> CommonResult<T> baseCreate(int code, String desc, T data, String eventType) {
CommonResult<T> result = new CommonResult<T>();
result.setCode(code);
result.setDesc(desc);
result.setData(data);
result.setEventType(eventType);
return result;
}
private static CommonResult baseCreate(int code, String desc) {
CommonResult result = new CommonResult();
result.setCode(code);
result.setDesc(desc);
return result;
}
public static CommonResult getNoAuthorityUserInstance() {
return fail(CODE_NO_AUTHORITY, "没有该用户修改权限");
}
public static CommonResult paramErrorResult(List<ParamError> errors) {
CommonResult result = fail(CODE_PARAM_VALIDATED_ERROR, "输入参数错误");
result.setParamErr(errors);
return result;
}
public static CommonResult paramErrorResult(ParamError error) {
CommonResult result = fail(CODE_PARAM_VALIDATED_ERROR, "输入参数错误");
result.addParamErr(error);
return result;
}
public T getData() {
return data;
}
public static CommonResult fail(ResultCode resultCode) {
return baseCreate(resultCode.getCode(), resultCode.getDesc());
}
public static <T> CommonResult<T> ok(ResultCode resultCode, T data) {
return baseCreate(resultCode.getCode(), resultCode.getDesc(), data);
}
public static <T> CommonResult<T> ok(ResultCode resultCode, T data, String eventType) {
return baseCreate(resultCode.getCode(), resultCode.getDesc(), data, eventType);
}
}
IdDto
package com.library.processing.controller.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* * id
* *
* * @author ll
* * @since 2023-05-08 16:18:03
*/
@Data
public class IdDto<T> {
@NotNull
T id;
public IdDto() {
super();
}
}
SelectInputCommon
package com.library.processing.controller.dto;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 公共输入参数
*
* @author ll
* @since 2023.5.8
*/
@Slf4j
@Data
public class SelectInputCommon<T> {
/**
* 查询筛选条件
*/
private T input;
/**
* 页码
*/
private int current = 1;
/**
* 每页size
*/
private int size = 10;
public Page<T> getPage() {
Page<T> page = new Page<T>();
page.setCurrent(current);
page.setSize(size);
return page;
}
/**
* 主要解决查询时前端传参为空值 ("")
* BeanUtils.copyProperties会把空值带入目标对象中
* 使用目标对象作为查询对象到mybatisPlus进行查询会导致没有匹配数据;
* 使用该方法将 空值 转换为 null,避免copy时带入到查询对象
*/
public T getToNullData() {
if (this.input == null) {
return null;
}
Class<?> clazz = this.input.getClass();
// 获取实体类的所有属性,返回Field数组
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 可访问私有变量
field.setAccessible(true);
// 获取属性类型
String type = field.getGenericType().toString();
// 如果type是类类型,则前面包含"class ",后面跟类名
if ("class java.lang.String".equals(type)) {
// 将属性的首字母大写
String methodName = field.getName().replaceFirst(field.getName().substring(0, 1),
field.getName().substring(0, 1).toUpperCase());
try {
Method methodGet = clazz.getMethod("get" + methodName);
// 调用getter方法获取属性值
String str = (String) methodGet.invoke(this.input);
//属性为null结束循环
if (str == null) {
continue;
}
if ("".equals(str)) {
// 为空 设置为null
field.set(this.input, null);
}
} catch (Exception e) {
log.error("emptyToNull 方法属性转换异常: {}", e.getMessage());
}
}
}
return this.input;
}
}
接口文档生成
1.点击生成api接口文档
2.接口文档生成成功
3.接口文档内容如下所示
分页查询接口(接口文档中可以查看详细的入参和出参的含义)
添加修改接口(可以查看参数是否必填等参数)