将 tool 合并到 infra 模块

This commit is contained in:
YunaiV
2022-03-11 00:39:34 +08:00
parent 716bbb9813
commit 5a2169b688
139 changed files with 1192 additions and 1476 deletions

View File

@@ -13,8 +13,9 @@
<name>${project.artifactId}</name>
<description>
infra 模块,我们放基础设施的运维与管理,支撑上层的通用与核心业务。
例如说:定时任务的管理、服务器的信息等等
infra 模块,主要提供两块能力:
1. 我们放基础设施的运维与管理,支撑上层的通用与核心业务。 例如说:定时任务的管理、服务器的信息等等
2. 研发工具,提升研发效率与质量。 例如说:代码生成器、接口文档等等
</description>
<dependencies>
@@ -88,6 +89,11 @@
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId> <!-- 实现代码生成 -->
</dependency>
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->

View File

@@ -0,0 +1,168 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ZipUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenPreviewRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.SchemaTableRespVO;
import cn.iocoder.yudao.module.infra.convert.codegen.CodegenConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaTableDO;
import cn.iocoder.yudao.module.infra.service.codegen.CodegenService;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "管理后台 - 代码生成器")
@RestController
@RequestMapping("/infra/codegen")
@Validated
public class CodegenController {
@Resource
private CodegenService codegenService;
@GetMapping("/db/table/list")
@ApiOperation(value = "获得数据库自带的表定义列表", notes = "会过滤掉已经导入 Codegen 的表")
@ApiImplicitParams({
@ApiImplicitParam(name = "tableName", value = "表名,模糊匹配", required = true, example = "yudao", dataTypeClass = String.class),
@ApiImplicitParam(name = "tableComment", value = "描述,模糊匹配", required = true, example = "芋道", dataTypeClass = String.class)
})
@PreAuthorize("@ss.hasPermission('infra:codegen:query')")
public CommonResult<List<SchemaTableRespVO>> getSchemaTableList(
@RequestParam(value = "tableName", required = false) String tableName,
@RequestParam(value = "tableComment", required = false) String tableComment) {
// 获得数据库自带的表定义列表
List<SchemaTableDO> schemaTables = codegenService.getSchemaTableList(tableName, tableComment);
// 移除在 Codegen 中,已经存在的
Set<String> existsTables = CollectionUtils.convertSet(codegenService.getCodeGenTableList(), CodegenTableDO::getTableName);
schemaTables.removeIf(table -> existsTables.contains(table.getTableName()));
return success(CodegenConvert.INSTANCE.convertList04(schemaTables));
}
@GetMapping("/table/page")
@ApiOperation("获得表定义分页")
@PreAuthorize("@ss.hasPermission('infra:codegen:query')")
public CommonResult<PageResult<CodegenTableRespVO>> getCodeGenTablePage(@Valid CodegenTablePageReqVO pageReqVO) {
PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(pageReqVO);
return success(CodegenConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/detail")
@ApiOperation("获得表和字段的明细")
@ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:codegen:query')")
public CommonResult<CodegenDetailRespVO> getCodegenDetail(@RequestParam("tableId") Long tableId) {
CodegenTableDO table = codegenService.getCodegenTablePage(tableId);
List<CodegenColumnDO> columns = codegenService.getCodegenColumnListByTableId(tableId);
// 拼装返回
return success(CodegenConvert.INSTANCE.convert(table, columns));
}
@ApiOperation("基于数据库的表结构,创建代码生成器的表和字段定义")
@ApiImplicitParam(name = "tableNames", value = "表名数组", required = true, example = "sys_user", dataTypeClass = List.class)
@PostMapping("/create-list-from-db")
@PreAuthorize("@ss.hasPermission('infra:codegen:create')")
public CommonResult<List<Long>> createCodegenListFromDB(@RequestParam("tableNames") List<String> tableNames) {
return success(codegenService.createCodegenListFromDB(getLoginUserId(), tableNames));
}
@ApiOperation("基于 SQL 建表语句,创建代码生成器的表和字段定义")
@ApiImplicitParam(name = "sql", value = "SQL 建表语句", required = true, example = "sql", dataTypeClass = String.class)
@PostMapping("/create-list-from-sql")
@PreAuthorize("@ss.hasPermission('infra:codegen:create')")
public CommonResult<Long> createCodegenListFromSQL(@RequestParam("sql") String sql) {
return success(codegenService.createCodegenListFromSQL(getLoginUserId(), sql));
}
@ApiOperation("更新数据库的表和字段定义")
@PutMapping("/update")
@PreAuthorize("@ss.hasPermission('infra:codegen:update')")
public CommonResult<Boolean> updateCodegen(@Valid @RequestBody CodegenUpdateReqVO updateReqVO) {
codegenService.updateCodegen(updateReqVO);
return success(true);
}
@ApiOperation("基于数据库的表结构,同步数据库的表和字段定义")
@PutMapping("/sync-from-db")
@ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:codegen:update')")
public CommonResult<Boolean> syncCodegenFromDB(@RequestParam("tableId") Long tableId) {
codegenService.syncCodegenFromDB(tableId);
return success(true);
}
@ApiOperation("基于 SQL 建表语句,同步数据库的表和字段定义")
@PutMapping("/sync-from-sql")
@ApiImplicitParams({
@ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class),
@ApiImplicitParam(name = "sql", value = "SQL 建表语句", required = true, example = "sql", dataTypeClass = String.class)
})
@PreAuthorize("@ss.hasPermission('infra:codegen:update')")
public CommonResult<Boolean> syncCodegenFromSQL(@RequestParam("tableId") Long tableId,
@RequestParam("sql") String sql) {
codegenService.syncCodegenFromSQL(tableId, sql);
return success(true);
}
@ApiOperation("删除数据库的表和字段定义")
@DeleteMapping("/delete")
@ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:codegen:delete')")
public CommonResult<Boolean> deleteCodegen(@RequestParam("tableId") Long tableId) {
codegenService.deleteCodegen(tableId);
return success(true);
}
@ApiOperation("预览生成代码")
@GetMapping("/preview")
@ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:codegen:preview')")
public CommonResult<List<CodegenPreviewRespVO>> previewCodegen(@RequestParam("tableId") Long tableId) {
Map<String, String> codes = codegenService.generationCodes(tableId);
return success(CodegenConvert.INSTANCE.convert(codes));
}
@ApiOperation("下载生成代码")
@GetMapping("/download")
@ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:codegen:download')")
public void downloadCodegen(@RequestParam("tableId") Long tableId,
HttpServletResponse response) throws IOException {
// 生成代码
Map<String, String> codes = codegenService.generationCodes(tableId);
// 构建 zip 包
String[] paths = codes.keySet().toArray(new String[0]);
ByteArrayInputStream[] ins = codes.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipUtil.zip(outputStream, paths, ins);
// 输出
ServletUtils.writeAttachment(response, "codegen.zip", outputStream.toByteArray());
}
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("管理后台 - 代码生成表和字段的明细 Response VO")
@Data
public class CodegenDetailRespVO {
@ApiModelProperty("表定义")
private CodegenTableRespVO table;
@ApiModelProperty("字段定义")
private List<CodegenColumnRespVO> columns;
}

View File

@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel(value = "管理后台 - 代码生成预览 Response VO", description ="注意,每个文件都是一个该对象")
@Data
public class CodegenPreviewRespVO {
@ApiModelProperty(value = "文件路径", required = true, example = "java/cn/iocoder/yudao/adminserver/modules/system/controller/test/SysTestDemoController.java")
private String filePath;
@ApiModelProperty(value = "代码", required = true, example = "Hello World")
private String code;
}

View File

@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnBaseVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableBaseVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 代码生成表和字段的修改 Request VO")
@Data
public class CodegenUpdateReqVO {
@Valid // 校验内嵌的字段
@NotNull(message = "表定义不能为空")
private Table table;
@Valid // 校验内嵌的字段
@NotNull(message = "字段定义不能为空")
private List<Column> columns;
@ApiModel("更新表定义")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public static class Table extends CodegenTableBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
}
@ApiModel("更新表定义")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public static class Column extends CodegenColumnBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
}
}

View File

@@ -0,0 +1,85 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 代码生成字段定义 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class CodegenColumnBaseVO {
@ApiModelProperty(value = "表编号", required = true, example = "1")
@NotNull(message = "表编号不能为空")
private Long tableId;
@ApiModelProperty(value = "字段名", required = true, example = "user_age")
@NotNull(message = "字段名不能为空")
private String columnName;
@ApiModelProperty(value = "字段类型", required = true, example = "int(11)")
@NotNull(message = "字段类型不能为空")
private String columnType;
@ApiModelProperty(value = "字段描述", required = true, example = "年龄")
@NotNull(message = "字段描述不能为空")
private String columnComment;
@ApiModelProperty(value = "是否允许为空", required = true, example = "true")
@NotNull(message = "是否允许为空不能为空")
private Boolean nullable;
@ApiModelProperty(value = "是否主键", required = true, example = "false")
@NotNull(message = "是否主键不能为空")
private Boolean primaryKey;
@ApiModelProperty(value = "是否自增", required = true, example = "true")
@NotNull(message = "是否自增不能为空")
private String autoIncrement;
@ApiModelProperty(value = "排序", required = true, example = "10")
@NotNull(message = "排序不能为空")
private Integer ordinalPosition;
@ApiModelProperty(value = "Java 属性类型", required = true, example = "userAge")
@NotNull(message = "Java 属性类型不能为空")
private String javaType;
@ApiModelProperty(value = "Java 属性名", required = true, example = "Integer")
@NotNull(message = "Java 属性名不能为空")
private String javaField;
@ApiModelProperty(value = "字典类型", example = "sys_gender")
private String dictType;
@ApiModelProperty(value = "数据示例", example = "1024")
private String example;
@ApiModelProperty(value = "是否为 Create 创建操作的字段", required = true, example = "true")
@NotNull(message = "是否为 Create 创建操作的字段不能为空")
private Boolean createOperation;
@ApiModelProperty(value = "是否为 Update 更新操作的字段", required = true, example = "false")
@NotNull(message = "是否为 Update 更新操作的字段不能为空")
private Boolean updateOperation;
@ApiModelProperty(value = "是否为 List 查询操作的字段", required = true, example = "true")
@NotNull(message = "是否为 List 查询操作的字段不能为空")
private Boolean listOperation;
@ApiModelProperty(value = "List 查询操作的条件类型", required = true, example = "LIKE", notes = "参见 CodegenColumnListConditionEnum 枚举")
@NotNull(message = "List 查询操作的条件类型不能为空")
private String listOperationCondition;
@ApiModelProperty(value = "是否为 List 查询操作的返回字段", required = true, example = "true")
@NotNull(message = "是否为 List 查询操作的返回字段不能为空")
private Boolean listOperationResult;
@ApiModelProperty(value = "显示类型", required = true, example = "input")
@NotNull(message = "显示类型不能为空")
private String htmlType;
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
@ApiModel("管理后台 - 代码生成字段定义 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CodegenColumnRespVO extends CodegenColumnBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 代码生成 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class CodegenTableBaseVO {
@ApiModelProperty(value = "导入类型", required = true, example = "1", notes = "参见 CodegenImportTypeEnum 枚举")
@NotNull(message = "导入类型不能为空")
private Integer importType;
@ApiModelProperty(value = "生成场景", required = true, example = "1", notes = "参见 CodegenSceneEnum 枚举")
@NotNull(message = "导入类型不能为空")
private Integer scene;
@ApiModelProperty(value = "表名称", required = true, example = "yudao")
@NotNull(message = "表名称不能为空")
private String tableName;
@ApiModelProperty(value = "表描述", required = true, example = "芋道")
@NotNull(message = "表描述不能为空")
private String tableComment;
@ApiModelProperty(value = "备注", example = "我是备注")
private String remark;
@ApiModelProperty(value = "模块名", required = true, example = "system")
@NotNull(message = "模块名不能为空")
private String moduleName;
@ApiModelProperty(value = "业务名", required = true, example = "codegen")
@NotNull(message = "业务名不能为空")
private String businessName;
@ApiModelProperty(value = "类名称", required = true, example = "CodegenTable")
@NotNull(message = "类名称不能为空")
private String className;
@ApiModelProperty(value = "类描述", required = true, example = "代码生成器的表定义")
@NotNull(message = "类描述不能为空")
private String classComment;
@ApiModelProperty(value = "作者", required = true, example = "芋道源码")
@NotNull(message = "作者不能为空")
private String author;
@ApiModelProperty(value = "模板类型", required = true, example = "1", notes = "参见 CodegenTemplateTypeEnum 枚举")
@NotNull(message = "模板类型不能为空")
private Integer templateType;
@ApiModelProperty(value = "父菜单编号", example = "1024")
private Long parentMenuId;
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 表定义分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CodegenTablePageReqVO extends PageParam {
@ApiModelProperty(value = "表名称", example = "yudao", notes = "模糊匹配")
private String tableName;
@ApiModelProperty(value = "表描述", example = "芋道", notes = "模糊匹配")
private String tableComment;
@ApiModelProperty(value = "开始创建时间", example = "2020-10-24 00:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date beginCreateTime;
@ApiModelProperty(value = "结束创建时间", example = "2020-10-24 23:59:59")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date endCreateTime;
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
@ApiModel("管理后台 - 代码生成表定义 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CodegenTableRespVO extends CodegenTableBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
@ApiModelProperty(value = "更新时间", required = true)
private Date updateTime;
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel("管理后台 - 数据字典的表定义 Response VO")
@Data
public class SchemaTableRespVO {
@ApiModelProperty(value = "数据库", required = true, example = "yudao")
private String tableSchema;
@ApiModelProperty(value = "表名称", required = true, example = "yuanma")
private String tableName;
@ApiModelProperty(value = "表描述", required = true, example = "芋道源码")
private String tableComment;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@@ -0,0 +1,157 @@
package cn.iocoder.yudao.module.infra.controller.admin.doc;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@Api(tags = "管理后台 - 数据库文档")
@RestController
@RequestMapping("/infra/db-doc")
public class DbDocController {
@Resource
private DynamicDataSourceProperties dynamicDataSourceProperties;
private static final String FILE_OUTPUT_DIR = System.getProperty("java.io.tmpdir") + File.separator
+ "db-doc";
private static final String DOC_FILE_NAME = "数据库文档";
private static final String DOC_VERSION = "1.0.0";
private static final String DOC_DESCRIPTION = "文档描述";
@GetMapping("/export-html")
@ApiOperation("导出 html 格式的数据文档")
@ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true",
dataTypeClass = Boolean.class)
public void exportHtml(@RequestParam(defaultValue = "true") Boolean deleteFile,
HttpServletResponse response) throws IOException {
doExportFile(EngineFileType.HTML, deleteFile, response);
}
@GetMapping("/export-word")
@ApiOperation("导出 word 格式的数据文档")
@ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true",
dataTypeClass = Boolean.class)
public void exportWord(@RequestParam(defaultValue = "true") Boolean deleteFile,
HttpServletResponse response) throws IOException {
doExportFile(EngineFileType.WORD, deleteFile, response);
}
@GetMapping("/export-markdown")
@ApiOperation("导出 markdown 格式的数据文档")
@ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true",
dataTypeClass = Boolean.class)
public void exportMarkdown(@RequestParam(defaultValue = "true") Boolean deleteFile,
HttpServletResponse response) throws IOException {
doExportFile(EngineFileType.MD, deleteFile, response);
}
private void doExportFile(EngineFileType fileOutputType, Boolean deleteFile,
HttpServletResponse response) throws IOException {
String docFileName = DOC_FILE_NAME + "_" + IdUtil.fastSimpleUUID();
String filePath = doExportFile(fileOutputType, docFileName);
String downloadFileName = DOC_FILE_NAME + fileOutputType.getFileSuffix(); //下载后的文件名
try {
// 读取,返回
ServletUtils.writeAttachment(response, downloadFileName, FileUtil.readBytes(filePath));
} finally {
handleDeleteFile(deleteFile, filePath);
}
}
/**
* 输出文件,返回文件路径
*
* @param fileOutputType 文件类型
* @param fileName 文件名, 无需 ".docx" 等文件后缀
* @return 生成的文件所在路径
*/
private String doExportFile(EngineFileType fileOutputType, String fileName) {
try (HikariDataSource dataSource = buildDataSource()) {
// 创建 screw 的配置
Configuration config = Configuration.builder()
.version(DOC_VERSION) // 版本
.description(DOC_DESCRIPTION) // 描述
.dataSource(dataSource) // 数据源
.engineConfig(buildEngineConfig(fileOutputType, fileName)) // 引擎配置
.produceConfig(buildProcessConfig()) // 处理配置
.build();
// 执行 screw生成数据库文档
new DocumentationExecute(config).execute();
return FILE_OUTPUT_DIR + File.separator + fileName + fileOutputType.getFileSuffix();
}
}
private void handleDeleteFile(Boolean deleteFile, String filePath) {
if (!deleteFile) {
return;
}
FileUtil.del(filePath);
}
/**
* 创建数据源
*/
// TODO 芋艿screw 暂时不支持 druid尴尬
private HikariDataSource buildDataSource() {
// 获得 DataSource 数据源,目前只支持首个
String primary = dynamicDataSourceProperties.getPrimary();
DataSourceProperty dataSourceProperty = dynamicDataSourceProperties.getDatasource().get(primary);
// 创建 HikariConfig 配置类
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(dataSourceProperty.getUrl());
hikariConfig.setUsername(dataSourceProperty.getUsername());
hikariConfig.setPassword(dataSourceProperty.getPassword());
hikariConfig.addDataSourceProperty("useInformationSchema", "true"); // 设置可以获取 tables remarks 信息
// 创建数据源
return new HikariDataSource(hikariConfig);
}
/**
* 创建 screw 的引擎配置
*/
private static EngineConfig buildEngineConfig(EngineFileType fileOutputType, String docFileName) {
return EngineConfig.builder()
.fileOutputDir(FILE_OUTPUT_DIR) // 生成文件路径
.openOutputDir(false) // 打开目录
.fileType(fileOutputType) // 文件类型
.produceType(EngineTemplateType.velocity) // 文件类型
.fileName(docFileName) // 自定义文件名称
.build();
}
/**
* 创建 screw 的处理配置,一般可忽略
* 指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置
*/
private static ProcessConfig buildProcessConfig() {
return ProcessConfig.builder()
.ignoreTablePrefix(Arrays.asList("QRTZ_", "ACT_")) // 忽略表前缀
.build();
}
}

View File

@@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.infra.controller.admin.test;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.*;
import cn.iocoder.yudao.module.infra.convert.test.TestDemoConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.*;
import cn.iocoder.yudao.module.infra.service.test.TestDemoService;
@Api(tags = "管理后台 - 字典类型")
@RestController
@RequestMapping("/infra/test-demo")
@Validated
public class TestDemoController {
@Resource
private TestDemoService testDemoService;
@PostMapping("/create")
@ApiOperation("创建字典类型")
@PreAuthorize("@ss.hasPermission('infra:test-demo:create')") public CommonResult<Long> createTestDemo(@Valid @RequestBody TestDemoCreateReqVO createReqVO) {
return success(testDemoService.createTestDemo(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新字典类型")
@PreAuthorize("@ss.hasPermission('infra:test-demo:update')") public CommonResult<Boolean> updateTestDemo(@Valid @RequestBody TestDemoUpdateReqVO updateReqVO) {
testDemoService.updateTestDemo(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除字典类型")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:test-demo:delete')")
public CommonResult<Boolean> deleteTestDemo(@RequestParam("id") Long id) {
testDemoService.deleteTestDemo(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得字典类型")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:test-demo:query')")
public CommonResult<TestDemoRespVO> getTestDemo(@RequestParam("id") Long id) {
TestDemoDO testDemo = testDemoService.getTestDemo(id);
return success(TestDemoConvert.INSTANCE.convert(testDemo));
}
@GetMapping("/list")
@ApiOperation("获得字典类型列表")
@ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
@PreAuthorize("@ss.hasPermission('infra:test-demo:query')")
public CommonResult<List<TestDemoRespVO>> getTestDemoList(@RequestParam("ids") Collection<Long> ids) {
List<TestDemoDO> list = testDemoService.getTestDemoList(ids);
return success(TestDemoConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@ApiOperation("获得字典类型分页")
@PreAuthorize("@ss.hasPermission('infra:test-demo:query')") public CommonResult<PageResult<TestDemoRespVO>> getTestDemoPage(@Valid TestDemoPageReqVO pageVO) {
PageResult<TestDemoDO> pageResult = testDemoService.getTestDemoPage(pageVO);
return success(TestDemoConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@ApiOperation("导出字典类型 Excel")
@PreAuthorize("@ss.hasPermission('infra:test-demo:export')") @OperateLog(type = EXPORT)
public void exportTestDemoExcel(@Valid TestDemoExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<TestDemoDO> list = testDemoService.getTestDemoList(exportReqVO);
// 导出 Excel
List<TestDemoExcelVO> datas = TestDemoConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "字典类型.xls", "数据", TestDemoExcelVO.class, datas);
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
/**
* 字典类型 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class TestDemoBaseVO {
@ApiModelProperty(value = "名字", required = true)
@NotNull(message = "名字不能为空")
private String name;
@ApiModelProperty(value = "状态", required = true)
@NotNull(message = "状态不能为空")
private Integer status;
@ApiModelProperty(value = "类型", required = true)
@NotNull(message = "类型不能为空")
private Integer type;
@ApiModelProperty(value = "分类", required = true)
@NotNull(message = "分类不能为空")
private Integer category;
@ApiModelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,12 @@
package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
import lombok.*;
import io.swagger.annotations.*;
@ApiModel("管理后台 - 字典类型创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TestDemoCreateReqVO extends TestDemoBaseVO {
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
/**
* 字典类型 Excel VO
*
* @author 芋道源码
*/
@Data
public class TestDemoExcelVO {
@ExcelProperty("编号")
private Long id;
@ExcelProperty("名字")
private String name;
@ExcelProperty("状态")
private Integer status;
@ExcelProperty("类型")
private Integer type;
@ExcelProperty("分类")
private Integer category;
@ExcelProperty("备注")
private String remark;
@ExcelProperty("创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel(value = "管理后台 - 字典类型 Excel 导出 Request VO", description = "参数和 TestDemoPageReqVO 是一致的")
@Data
public class TestDemoExportReqVO {
@ApiModelProperty(value = "名字")
private String name;
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "类型")
private Integer type;
@ApiModelProperty(value = "分类")
private Integer category;
@ApiModelProperty(value = "备注")
private String remark;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
private Date beginCreateTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "结束创建时间")
private Date endCreateTime;
}

View File

@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 字典类型分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TestDemoPageReqVO extends PageParam {
@ApiModelProperty(value = "名字")
private String name;
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "类型")
private Integer type;
@ApiModelProperty(value = "分类")
private Integer category;
@ApiModelProperty(value = "备注")
private String remark;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
private Date beginCreateTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "结束创建时间")
private Date endCreateTime;
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
@ApiModel("管理后台 - 字典类型 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TestDemoRespVO extends TestDemoBaseVO {
@ApiModelProperty(value = "编号", required = true)
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
import lombok.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 字典类型更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TestDemoUpdateReqVO extends TestDemoBaseVO {
@ApiModelProperty(value = "编号", required = true)
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.infra.convert.codegen;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenPreviewRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.SchemaTableRespVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaTableDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Mapper
public interface CodegenConvert {
CodegenConvert INSTANCE = Mappers.getMapper(CodegenConvert.class);
// ========== InformationSchemaTableDO 和 InformationSchemaColumnDO 相关 ==========
CodegenTableDO convert(SchemaTableDO bean);
List<CodegenColumnDO> convertList(List<SchemaColumnDO> list);
CodegenTableRespVO convert(SchemaColumnDO bean);
// ========== CodegenTableDO 相关 ==========
// List<CodegenTableRespVO> convertList02(List<CodegenTableDO> list);
CodegenTableRespVO convert(CodegenTableDO bean);
PageResult<CodegenTableRespVO> convertPage(PageResult<CodegenTableDO> page);
// ========== CodegenTableDO 相关 ==========
List<CodegenColumnRespVO> convertList02(List<CodegenColumnDO> list);
CodegenTableDO convert(CodegenUpdateReqVO.Table bean);
List<CodegenColumnDO> convertList03(List<CodegenUpdateReqVO.Column> columns);
List<SchemaTableRespVO> convertList04(List<SchemaTableDO> list);
// ========== 其它 ==========
default CodegenDetailRespVO convert(CodegenTableDO table, List<CodegenColumnDO> columns) {
CodegenDetailRespVO respVO = new CodegenDetailRespVO();
respVO.setTable(convert(table));
respVO.setColumns(convertList02(columns));
return respVO;
}
default List<CodegenPreviewRespVO> convert(Map<String, String> codes) {
return codes.entrySet().stream().map(entry -> {
CodegenPreviewRespVO respVO = new CodegenPreviewRespVO();
respVO.setFilePath(entry.getKey());
respVO.setCode(entry.getValue());
return respVO;
}).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.infra.convert.test;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExcelVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoUpdateReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 字典类型 Convert
*
* @author 芋道源码
*/
@Mapper
public interface TestDemoConvert {
TestDemoConvert INSTANCE = Mappers.getMapper(TestDemoConvert.class);
TestDemoDO convert(TestDemoCreateReqVO bean);
TestDemoDO convert(TestDemoUpdateReqVO bean);
TestDemoRespVO convert(TestDemoDO bean);
List<TestDemoRespVO> convertList(List<TestDemoDO> list);
PageResult<TestDemoRespVO> convertPage(PageResult<TestDemoDO> page);
List<TestDemoExcelVO> convertList02(List<TestDemoDO> list);
}

View File

@@ -0,0 +1,124 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.codegen;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenColumnHtmlTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenColumnListConditionEnum;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 代码生成 column 字段定义
*
* @author 芋道源码
*/
@TableName(value = "infra_codegen_column", autoResultMap = true)
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class CodegenColumnDO extends BaseDO {
/**
* ID 编号
*/
@TableId
private Long id;
/**
* 表编号
*
* 关联 {@link CodegenTableDO#getId()}
*/
private Long tableId;
// ========== 表相关字段 ==========
/**
* 字段名
*/
private String columnName;
/**
* 字段类型
*/
private String columnType;
/**
* 字段描述
*/
private String columnComment;
/**
* 是否允许为空
*/
private Boolean nullable;
/**
* 是否主键
*/
private Boolean primaryKey;
/**
* 是否自增
*/
private Boolean autoIncrement;
/**
* 排序
*/
private Integer ordinalPosition;
// ========== Java 相关字段 ==========
/**
* Java 属性类型
*
* 例如说 String、Boolean 等等
*/
private String javaType;
/**
* Java 属性名
*/
// @NotBlank(message = "Java属性不能为空")
private String javaField;
/**
* 字典类型
*
* 关联 DictTypeDO 的 type 属性
*/
private String dictType;
/**
* 数据示例,主要用于生成 Swagger 注解的 example 字段
*/
private String example;
// ========== CRUD 相关字段 ==========
/**
* 是否为 Create 创建操作的字段
*/
private Boolean createOperation;
/**
* 是否为 Update 更新操作的字段
*/
private Boolean updateOperation;
/**
* 是否为 List 查询操作的字段
*/
private Boolean listOperation;
/**
* List 查询操作的条件类型
*
* 枚举 {@link CodegenColumnListConditionEnum}
*/
private String listOperationCondition;
/**
* 是否为 List 查询操作的返回字段
*/
private Boolean listOperationResult;
// ========== UI 相关字段 ==========
/**
* 显示类型
*
* 枚举 {@link CodegenColumnHtmlTypeEnum}
*/
private String htmlType;
}

View File

@@ -0,0 +1,102 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.codegen;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 代码生成 table 表定义
*
* @author 芋道源码
*/
@TableName(value = "infra_codegen_table", autoResultMap = true)
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class CodegenTableDO extends BaseDO {
/**
* ID 编号
*/
private Long id;
/**
* 导入类型
*
* 枚举 {@link CodegenTemplateTypeEnum}
*/
private Integer importType;
/**
* 生成场景
*
* 枚举 {@link CodegenSceneEnum}
*/
private Integer scene;
// ========== 表相关字段 ==========
/**
* 表名称
*/
private String tableName;
/**
* 表描述
*/
private String tableComment;
/**
* 备注
*/
private String remark;
// ========== 类相关字段 ==========
/**
* 模块名,即一级目录
*
* 例如说system、infra、tool 等等
*/
private String moduleName;
/**
* 业务名,即二级目录
*
* 例如说user、permission、dict 等等
*/
private String businessName;
/**
* 类名称(首字母大写)
*
* 例如说SysUser、SysMenu、SysDictData 等等
*/
private String className;
/**
* 类描述
*/
private String classComment;
/**
* 作者
*/
private String author;
// ========== 生成相关字段 ==========
/**
* 模板类型
*
* 枚举 {@link CodegenTemplateTypeEnum}
*/
private Integer templateType;
// ========== 菜单相关字段 ==========
/**
* 父菜单编号
*
* 关联 MenuDO 的 id 属性
*/
private Long parentMenuId;
}

View File

@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.codegen;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
/**
* MySQL 数据库中的 column 字段定义
*
* @author 芋道源码
*/
@TableName(value = "information_schema.columns", autoResultMap = true)
@Data
@Builder
public class SchemaColumnDO {
/**
* 表名称
*/
private String tableName;
/**
* 字段名
*/
private String columnName;
/**
* 字段类型
*/
private String columnType;
/**
* 字段描述
*/
private String columnComment;
/**
* 是否允许为空
*/
@TableField("case when is_nullable = 'yes' then '1' else '0' end")
private Boolean nullable;
/**
* 是否主键
*/
@TableField("case when column_key = 'PRI' then '1' else '0' end")
private Boolean primaryKey;
/**
* 是否自增
*/
@TableField("case when extra = 'auto_increment' then '1' else '0' end")
private Boolean autoIncrement;
/**
* 排序字段
*/
private Integer ordinalPosition;
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.codegen;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
/**
* MySQL 数据库中的 table 表定义
*
* @author 芋道源码
*/
@TableName(value = "information_schema.tables", autoResultMap = true)
@Data
@Builder
public class SchemaTableDO {
/**
* 数据库
*/
private String tableSchema;
/**
* 表名称
*/
private String tableName;
/**
* 表描述
*/
private String tableComment;
/**
* 创建时间
*/
private Date createTime;
}

View File

@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.test;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 字典类型 DO
*
* @author 芋道源码
*/
@TableName("infra_test_demo")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TestDemoDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 名字
*/
private String name;
/**
* 状态
*/
private Integer status;
/**
* 类型
*/
private Integer type;
/**
* 分类
*/
private Integer category;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.infra.dal.mysql.codegen;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CodegenColumnMapper extends BaseMapperX<CodegenColumnDO> {
default List<CodegenColumnDO> selectListByTableId(Long tableId) {
return selectList(new QueryWrapper<CodegenColumnDO>().eq("table_id", tableId)
.orderByAsc("ordinal_position"));
}
default void deleteListByTableId(Long tableId) {
delete(new QueryWrapper<CodegenColumnDO>().eq("table_id", tableId));
}
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.infra.dal.mysql.codegen;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CodegenTableMapper extends BaseMapperX<CodegenTableDO> {
default CodegenTableDO selectByTableName(String tableName) {
return selectOne(new QueryWrapper<CodegenTableDO>().eq("table_name", tableName));
}
default PageResult<CodegenTableDO> selectPage(CodegenTablePageReqVO pageReqVO) {
return selectPage(pageReqVO, new QueryWrapperX<CodegenTableDO>()
.likeIfPresent("table_name", pageReqVO.getTableName())
.likeIfPresent("table_comment", pageReqVO.getTableComment())
.betweenIfPresent("create_time", pageReqVO.getBeginCreateTime(), pageReqVO.getEndCreateTime()));
}
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.infra.dal.mysql.codegen;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaColumnDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SchemaColumnMapper extends BaseMapperX<SchemaColumnDO> {
default List<SchemaColumnDO> selectListByTableName(String tableSchema, String tableName) {
return selectList(new QueryWrapper<SchemaColumnDO>().eq("table_name", tableName)
.eq("table_schema", tableSchema)
.orderByAsc("ordinal_position"));
}
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.infra.dal.mysql.codegen;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaTableDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface SchemaTableMapper extends BaseMapperX<SchemaTableDO> {
default List<SchemaTableDO> selectList(Collection<String> tableSchemas, String tableName, String tableComment) {
return selectList(new QueryWrapperX<SchemaTableDO>().in("table_schema", tableSchemas)
.likeIfPresent("table_name", tableName)
.likeIfPresent("table_comment", tableComment));
}
default SchemaTableDO selectByTableSchemaAndTableName(String tableSchema, String tableName) {
return selectOne(new QueryWrapper<SchemaTableDO>().eq("table_schema",tableSchema)
.eq("table_name", tableName));
}
}

View File

@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.infra.dal.mysql.test;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExportReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoPageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 字典类型 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface TestDemoMapper extends BaseMapperX<TestDemoDO> {
default PageResult<TestDemoDO> selectPage(TestDemoPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TestDemoDO>()
.likeIfPresent(TestDemoDO::getName, reqVO.getName())
.eqIfPresent(TestDemoDO::getStatus, reqVO.getStatus())
.eqIfPresent(TestDemoDO::getType, reqVO.getType())
.eqIfPresent(TestDemoDO::getCategory, reqVO.getCategory())
.eqIfPresent(TestDemoDO::getRemark, reqVO.getRemark())
.betweenIfPresent(TestDemoDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.orderByDesc(TestDemoDO::getId));
}
default List<TestDemoDO> selectList(TestDemoExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<TestDemoDO>()
.likeIfPresent(TestDemoDO::getName, reqVO.getName())
.eqIfPresent(TestDemoDO::getStatus, reqVO.getStatus())
.eqIfPresent(TestDemoDO::getType, reqVO.getType())
.eqIfPresent(TestDemoDO::getCategory, reqVO.getCategory())
.eqIfPresent(TestDemoDO::getRemark, reqVO.getRemark())
.betweenIfPresent(TestDemoDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.orderByDesc(TestDemoDO::getId));
}
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.infra.enums.codegen;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 代码生成器的字段 HTML 展示枚举
*/
@AllArgsConstructor
@Getter
public enum CodegenColumnHtmlTypeEnum {
INPUT("input"), // 文本框
TEXTAREA("textarea"), // 文本域
SELECT("select"), // 下拉框
RADIO("radio"), // 单选框
CHECKBOX("checkbox"), // 复选框
DATETIME("datetime"), // 日期控件
UPLOAD_IMAGE("upload_image"), // 上传图片
UPLOAD_FILE("upload_file"), // 上传文件
EDITOR("editor"), // 富文本控件
;
/**
* 条件
*/
private final String type;
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.infra.enums.codegen;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 代码生成器的字段过滤条件枚举
*/
@AllArgsConstructor
@Getter
public enum CodegenColumnListConditionEnum {
EQ("="),
NE("!="),
GT(">"),
GTE(">="),
LT("<"),
LTE("<="),
LIKE("LIKE"),
BETWEEN("BETWEEN");
/**
* 条件
*/
private final String condition;
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.infra.enums.codegen;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 代码生成的导入类型
*
* @author 芋道源码
*/
@AllArgsConstructor
@Getter
public enum CodegenImportTypeEnum {
DB(1), // 从 information_schema 的 table 和 columns 表导入
SQL(2); // 基于建表 SQL 语句导入
/**
* 类型
*/
private final Integer type;
}

View File

@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.infra.enums.codegen;
import lombok.AllArgsConstructor;
import lombok.Getter;
import static cn.hutool.core.util.ArrayUtil.*;
/**
* 代码生成的场景枚举
*
* @author 芋道源码
*/
@AllArgsConstructor
@Getter
public enum CodegenSceneEnum {
ADMIN(1, "管理后台", "admin", ""),
APP(2, "用户 APP", "app", "App");
/**
* 场景
*/
private final Integer scene;
/**
* 场景名
*/
private final String name;
/**
* 基础包名
*/
private final String basePackage;
/**
* Controller 和 VO 类的前缀
*/
private final String prefixClass;
public static CodegenSceneEnum valueOf(Integer scene) {
return firstMatch(sceneEnum -> sceneEnum.getScene().equals(scene), values());
}
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.infra.enums.codegen;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 代码生成模板类型
*
* @author 芋道源码
*/
@AllArgsConstructor
@Getter
public enum CodegenTemplateTypeEnum {
CRUD(1), // 单表(增删改查)
TREE(2), // 树表(增删改查)
;
/**
* 类型
*/
private final Integer type;
}

View File

@@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.infra.enums;

View File

@@ -0,0 +1,9 @@
package cn.iocoder.yudao.module.infra.framework.codegen.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(CodegenProperties.class)
public class CodegenConfiguration {
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.infra.framework.codegen.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Collection;
@ConfigurationProperties(prefix = "yudao.codegen")
@Validated
@Data
public class CodegenProperties {
/**
* 生成的 Java 代码的基础包
*/
@NotNull(message = "Java 代码的基础包不能为空")
private String basePackage;
/**
* 数据库名数组
*/
@NotEmpty(message = "数据库不能为空")
private Collection<String> dbSchemas;
}

View File

@@ -0,0 +1,4 @@
/**
* 代码生成器
*/
package cn.iocoder.yudao.module.infra.framework.codegen;

View File

@@ -1,6 +1,7 @@
/**
* infra 包下,我们放基础设施的运维与管理,支撑上层的通用与核心业务。
* 例如说:定时任务的管理、服务器的信息等等
* infra 模块,主要提供两块能力:
* 1. 我们放基础设施的运维与管理,支撑上层的通用与核心业务。 例如说:定时任务的管理、服务器的信息等等
* 2. 研发工具,提升研发效率与质量。 例如说:代码生成器、接口文档等等
*
* 1. Controller URL以 /infra/ 开头,避免和其它 Module 冲突
* 2. DataObject 表名:以 infra_ 开头,方便在数据库中区分

View File

@@ -0,0 +1,124 @@
package cn.iocoder.yudao.module.infra.service.codegen;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaTableDO;
import java.util.List;
import java.util.Map;
/**
* 代码生成 Service 接口
*
* @author 芋道源码
*/
public interface CodegenService {
/**
* 基于 SQL 建表语句,创建代码生成器的表定义
*
* @param userId 用户编号
* @param sql SQL 建表语句
* @return 创建的表定义的编号
*/
Long createCodegenListFromSQL(Long userId, String sql);
/**
* 基于数据库的表结构,创建代码生成器的表定义
*
* @param userId 用户编号
* @param tableName 表名称
* @return 创建的表定义的编号
*/
Long createCodegen(Long userId, String tableName);
/**
* 基于 {@link #createCodegen(Long, String)} 的批量创建
*
* @param userId 用户编号
* @param tableNames 表名称数组
* @return 创建的表定义的编号数组
*/
List<Long> createCodegenListFromDB(Long userId, List<String> tableNames);
/**
* 更新数据库的表和字段定义
*
* @param updateReqVO 更新信息
*/
void updateCodegen(CodegenUpdateReqVO updateReqVO);
/**
* 基于数据库的表结构,同步数据库的表和字段定义
*
* @param tableId 表编号
*/
void syncCodegenFromDB(Long tableId);
/**
* 基于 SQL 建表语句,同步数据库的表和字段定义
*
* @param tableId 表编号
* @param sql SQL 建表语句
*/
void syncCodegenFromSQL(Long tableId, String sql);
/**
* 删除数据库的表和字段定义
*
* @param tableId 数据编号
*/
void deleteCodegen(Long tableId);
/**
* 获得表定义分页
*
* @param pageReqVO 分页条件
* @return 表定义分页
*/
PageResult<CodegenTableDO> getCodegenTablePage(CodegenTablePageReqVO pageReqVO);
/**
* 获得表定义
*
* @param id 表编号
* @return 表定义
*/
CodegenTableDO getCodegenTablePage(Long id);
/**
* 获得全部表定义
*
* @return 表定义数组
*/
List<CodegenTableDO> getCodeGenTableList();
/**
* 获得指定表的字段定义数组
*
* @param tableId 表编号
* @return 字段定义数组
*/
List<CodegenColumnDO> getCodegenColumnListByTableId(Long tableId);
/**
* 执行指定表的代码生成
*
* @param tableId 表编号
* @return 生成结果。key 为文件路径value 为对应的代码内容
*/
Map<String, String> generationCodes(Long tableId);
/**
* 获得数据库自带的表定义列表
*
* @param tableName 表名称
* @param tableComment 表描述
* @return 表定义列表
*/
List<SchemaTableDO> getSchemaTableList(String tableName, String tableComment);
}

View File

@@ -0,0 +1,292 @@
package cn.iocoder.yudao.module.infra.service.codegen;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
import cn.iocoder.yudao.module.infra.convert.codegen.CodegenConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaTableDO;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.SchemaColumnMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.SchemaTableMapper;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenImportTypeEnum;
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenSQLParser;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import org.apache.commons.collections4.KeyValue;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
/**
* 代码生成 Service 实现类
*
* @author 芋道源码
*/
@Service
public class CodegenServiceImpl implements CodegenService {
@Resource
private SchemaTableMapper schemaTableMapper;
@Resource
private SchemaColumnMapper schemaColumnMapper;
@Resource
private CodegenTableMapper codegenTableMapper;
@Resource
private CodegenColumnMapper codegenColumnMapper;
@Resource
private AdminUserApi userApi;
@Resource
private CodegenBuilder codegenBuilder;
@Resource
private CodegenEngine codegenEngine;
@Resource
private CodegenProperties codegenProperties;
private Long createCodegen0(Long userId, CodegenImportTypeEnum importType,
SchemaTableDO schemaTable, List<SchemaColumnDO> schemaColumns) {
// 校验导入的表和字段非空
if (schemaTable == null) {
throw exception(CODEGEN_IMPORT_TABLE_NULL);
}
if (CollUtil.isEmpty(schemaColumns)) {
throw exception(CODEGEN_IMPORT_COLUMNS_NULL);
}
// 校验是否已经存在
if (codegenTableMapper.selectByTableName(schemaTable.getTableName()) != null) {
throw exception(CODEGEN_TABLE_EXISTS);
}
// 构建 CodegenTableDO 对象,插入到 DB 中
CodegenTableDO table = codegenBuilder.buildTable(schemaTable);
table.setImportType(importType.getType());
table.setAuthor(userApi.getUser(userId).getNickname());
codegenTableMapper.insert(table);
// 构建 CodegenColumnDO 数组,插入到 DB 中
List<CodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
columns.forEach(column -> {
column.setTableId(table.getId());
codegenColumnMapper.insert(column); // TODO 批量插入
});
return table.getId();
}
@Override
public Long createCodegenListFromSQL(Long userId, String sql) {
// 从 SQL 中,获得数据库表结构
SchemaTableDO schemaTable;
List<SchemaColumnDO> schemaColumns;
try {
KeyValue<SchemaTableDO, List<SchemaColumnDO>> result = CodegenSQLParser.parse(sql);
schemaTable = result.getKey();
schemaColumns = result.getValue();
} catch (Exception ex) {
throw exception(CODEGEN_PARSE_SQL_ERROR);
}
// 导入
return this.createCodegen0(userId, CodegenImportTypeEnum.SQL, schemaTable, schemaColumns);
}
@Override
public Long createCodegen(Long userId, String tableName) {
// 获取当前schema
String tableSchema = codegenProperties.getDbSchemas().iterator().next();
// 从数据库中,获得数据库表结构
SchemaTableDO schemaTable = schemaTableMapper.selectByTableSchemaAndTableName(tableSchema, tableName);
List<SchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableSchema, tableName);
// 导入
return this.createCodegen0(userId, CodegenImportTypeEnum.DB, schemaTable, schemaColumns);
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<Long> createCodegenListFromDB(Long userId, List<String> tableNames) {
List<Long> ids = new ArrayList<>(tableNames.size());
// 遍历添加。虽然效率会低一点,但是没必要做成完全批量,因为不会这么大量
tableNames.forEach(tableName -> ids.add(createCodegen(userId, tableName)));
return ids;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateCodegen(CodegenUpdateReqVO updateReqVO) {
// 校验是否已经存在
if (codegenTableMapper.selectById(updateReqVO.getTable().getId()) == null) {
throw exception(CODEGEN_TABLE_NOT_EXISTS);
}
// 更新 table 表定义
CodegenTableDO updateTableObj = CodegenConvert.INSTANCE.convert(updateReqVO.getTable());
codegenTableMapper.updateById(updateTableObj);
// 更新 column 字段定义
List<CodegenColumnDO> updateColumnObjs = CodegenConvert.INSTANCE.convertList03(updateReqVO.getColumns());
updateColumnObjs.forEach(updateColumnObj -> codegenColumnMapper.updateById(updateColumnObj));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncCodegenFromDB(Long tableId) {
// 校验是否已经存在
CodegenTableDO table = codegenTableMapper.selectById(tableId);
if (table == null) {
throw exception(CODEGEN_TABLE_NOT_EXISTS);
}
String tableSchema = codegenProperties.getDbSchemas().iterator().next();
// 从数据库中,获得数据库表结构
List<SchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableSchema, table.getTableName());
// 执行同步
this.syncCodegen0(tableId, schemaColumns);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncCodegenFromSQL(Long tableId, String sql) {
// 校验是否已经存在
CodegenTableDO table = codegenTableMapper.selectById(tableId);
if (table == null) {
throw exception(CODEGEN_TABLE_NOT_EXISTS);
}
// 从 SQL 中,获得数据库表结构
List<SchemaColumnDO> schemaColumns;
try {
KeyValue<SchemaTableDO, List<SchemaColumnDO>> result = CodegenSQLParser.parse(sql);
schemaColumns = result.getValue();
} catch (Exception ex) {
throw exception(CODEGEN_PARSE_SQL_ERROR);
}
// 执行同步
this.syncCodegen0(tableId, schemaColumns);
}
private void syncCodegen0(Long tableId, List<SchemaColumnDO> schemaColumns) {
// 校验导入的字段不为空
if (CollUtil.isEmpty(schemaColumns)) {
throw exception(CODEGEN_SYNC_COLUMNS_NULL);
}
Set<String> schemaColumnNames = CollectionUtils.convertSet(schemaColumns, SchemaColumnDO::getColumnName);
// 构建 CodegenColumnDO 数组,只同步新增的字段
List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);
// 移除已经存在的字段
schemaColumns.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
// 计算需要删除的字段
Set<Long> deleteColumnIds = codegenColumns.stream().filter(column -> !schemaColumnNames.contains(column.getColumnName()))
.map(CodegenColumnDO::getId).collect(Collectors.toSet());
if (CollUtil.isEmpty(schemaColumns) && CollUtil.isEmpty(deleteColumnIds)) {
throw exception(CODEGEN_SYNC_NONE_CHANGE);
}
// 插入新增的字段
List<CodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
columns.forEach(column -> {
column.setTableId(tableId);
codegenColumnMapper.insert(column); // TODO 批量插入
});
// 删除不存在的字段
if (CollUtil.isNotEmpty(deleteColumnIds)) {
codegenColumnMapper.deleteBatchIds(deleteColumnIds);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteCodegen(Long tableId) {
// 校验是否已经存在
if (codegenTableMapper.selectById(tableId) == null) {
throw exception(CODEGEN_TABLE_NOT_EXISTS);
}
// 删除 table 表定义
codegenTableMapper.deleteById(tableId);
// 删除 column 字段定义
codegenColumnMapper.deleteListByTableId(tableId);
}
@Override
public PageResult<CodegenTableDO> getCodegenTablePage(CodegenTablePageReqVO pageReqVO) {
return codegenTableMapper.selectPage(pageReqVO);
}
@Override
public CodegenTableDO getCodegenTablePage(Long id) {
return codegenTableMapper.selectById(id);
}
@Override
public List<CodegenTableDO> getCodeGenTableList() {
return codegenTableMapper.selectList();
}
@Override
public List<CodegenColumnDO> getCodegenColumnListByTableId(Long tableId) {
return codegenColumnMapper.selectListByTableId(tableId);
}
@Override
public Map<String, String> generationCodes(Long tableId) {
// 校验是否已经存在
CodegenTableDO table = codegenTableMapper.selectById(tableId);
if (codegenTableMapper.selectById(tableId) == null) {
throw exception(CODEGEN_TABLE_NOT_EXISTS);
}
List<CodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(tableId);
if (CollUtil.isEmpty(columns)) {
throw exception(CODEGEN_COLUMN_NOT_EXISTS);
}
// 执行生成
return codegenEngine.execute(table, columns);
}
@Override
public List<SchemaTableDO> getSchemaTableList(String tableName, String tableComment) {
List<SchemaTableDO> tables = schemaTableMapper.selectList(codegenProperties.getDbSchemas(), tableName, tableComment);
// TODO 强制移除 Quartz 的表,未来做成可配置
tables.removeIf(table -> table.getTableName().startsWith("QRTZ_"));
tables.removeIf(table -> table.getTableName().startsWith("ACT_"));
return tables;
}
// /**
// * 修改保存参数校验
// *
// * @param genTable 业务信息
// */
// @Override
// public void validateEdit(GenTable genTable) {
// if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
// String options = JSON.toJSONString(genTable.getParams());
// JSONObject paramsObj = JSONObject.parseObject(options);
// if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) {
// throw new CustomException("树编码字段不能为空");
// } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) {
// throw new CustomException("树父编码字段不能为空");
// } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) {
// throw new CustomException("树名称字段不能为空");
// }
// }
// }
}

View File

@@ -0,0 +1,207 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.infra.convert.codegen.CodegenConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenColumnHtmlTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenColumnListConditionEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.google.common.collect.Sets;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.*;
import static cn.hutool.core.text.CharSequenceUtil.*;
/**
* 代码生成器的 Builder负责
* 1. 将数据库的表 {@link SchemaTableDO} 定义,构建成 {@link CodegenTableDO}
* 2. 将数据库的列 {@link SchemaColumnDO} 构定义,建成 {@link CodegenColumnDO}
*/
@Component
public class CodegenBuilder {
/**
* 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射
* 注意,字段的匹配以后缀的方式
*/
private static final Map<String, CodegenColumnListConditionEnum> columnListOperationConditionMappings =
MapUtil.<String, CodegenColumnListConditionEnum>builder()
.put("name", CodegenColumnListConditionEnum.LIKE)
.put("time", CodegenColumnListConditionEnum.BETWEEN)
.put("date", CodegenColumnListConditionEnum.BETWEEN)
.build();
/**
* 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射
* 注意,字段的匹配以后缀的方式
*/
private static final Map<String, CodegenColumnHtmlTypeEnum> columnHtmlTypeMappings =
MapUtil.<String, CodegenColumnHtmlTypeEnum>builder()
.put("status", CodegenColumnHtmlTypeEnum.RADIO)
.put("sex", CodegenColumnHtmlTypeEnum.RADIO)
.put("type", CodegenColumnHtmlTypeEnum.SELECT)
.put("image", CodegenColumnHtmlTypeEnum.UPLOAD_IMAGE)
.put("file", CodegenColumnHtmlTypeEnum.UPLOAD_FILE)
.put("content", CodegenColumnHtmlTypeEnum.EDITOR)
.put("time", CodegenColumnHtmlTypeEnum.DATETIME)
.put("date", CodegenColumnHtmlTypeEnum.DATETIME)
.build();
/**
* 多租户编号的字段名
*/
public static final String TENANT_ID_FIELD = "tenant_id";
/**
* {@link cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO} 的字段
*/
public static final Set<String> BASE_DO_FIELDS = new HashSet<>();
/**
* 新增操作,不需要传递的字段
*/
private static final Set<String> CREATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id");
/**
* 修改操作,不需要传递的字段
*/
private static final Set<String> UPDATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet();
/**
* 列表操作的条件,不需要传递的字段
*/
private static final Set<String> LIST_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id");
/**
* 列表操作的结果,不需要返回的字段
*/
private static final Set<String> LIST_OPERATION_RESULT_EXCLUDE_COLUMN = Sets.newHashSet();
/**
* Java 类型与 MySQL 类型的映射关系
*/
private static final Map<String, Set<String>> javaTypeMappings = MapUtil.<String, Set<String>>builder()
.put(Boolean.class.getSimpleName(), Sets.newHashSet("bit"))
.put(Integer.class.getSimpleName(), Sets.newHashSet("tinyint", "smallint", "mediumint", "int"))
.put(Long.class.getSimpleName(), Collections.singleton("bigint"))
.put(Double.class.getSimpleName(), Sets.newHashSet("float", "double"))
.put(BigDecimal.class.getSimpleName(), Sets.newHashSet("decimal", "numeric"))
.put(String.class.getSimpleName(), Sets.newHashSet("tinytext", "text", "mediumtext", "longtext", // 长文本
"char", "varchar", "nvarchar", "varchar2")) // 短文本
.put(Date.class.getSimpleName(), Sets.newHashSet("datetime", "time", "date", "timestamp"))
.put("byte[]", Sets.newHashSet("blob"))
.build();
static {
Arrays.stream(ReflectUtil.getFields(BaseDO.class)).forEach(field -> BASE_DO_FIELDS.add(field.getName()));
BASE_DO_FIELDS.add(TENANT_ID_FIELD);
// 处理 OPERATION 相关的字段
CREATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
UPDATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
LIST_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
LIST_OPERATION_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是可能需要传递的
LIST_OPERATION_RESULT_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
LIST_OPERATION_RESULT_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是需要返回的
}
public CodegenTableDO buildTable(SchemaTableDO schemaTable) {
CodegenTableDO table = CodegenConvert.INSTANCE.convert(schemaTable);
initTableDefault(table);
return table;
}
/**
* 初始化 Table 表的默认字段
*
* @param table 表定义
*/
private void initTableDefault(CodegenTableDO table) {
// 以 system_dept 举例子。moduleName 为 system、businessName 为 dept、className 为 SystemDept
// 如果不希望 System 前缀,则可以手动在【代码生成 - 修改生成配置 - 基本信息】,将实体类名称改为 Dept 即可
table.setModuleName(StrUtil.subBefore(table.getTableName(),
'_', false)); // 第一个 _ 前缀的前面,作为 module 名字
table.setBusinessName(toCamelCase(subAfter(table.getTableName(),
'_', false))); // 第一步,第一个 _ 前缀的后面,作为 module 名字; 第二步,可能存在多个 _ 的情况,转换成驼峰
table.setClassName(upperFirst(toCamelCase(table.getTableName()))); // 驼峰 + 首字母大写
table.setClassComment(subBefore(table.getTableComment(), // 去除结尾的表,作为类描述
'表', true));
table.setAuthor("芋艿"); // TODO 稍后改成创建人
table.setTemplateType(CodegenTemplateTypeEnum.CRUD.getType());
}
public List<CodegenColumnDO> buildColumns(List<SchemaColumnDO> schemaColumns) {
List<CodegenColumnDO> columns = CodegenConvert.INSTANCE.convertList(schemaColumns);
columns.forEach(this::initColumnDefault);
return columns;
}
/**
* 初始化 Column 列的默认字段
*
* @param column 列定义
*/
private void initColumnDefault(CodegenColumnDO column) {
// 处理 Java 相关的字段的默认值
processColumnJava(column);
// 处理 CRUD 相关的字段的默认值
processColumnOperation(column);
// 处理 UI 相关的字段的默认值
processColumnUI(column);
}
private void processColumnJava(CodegenColumnDO column) {
// 处理 javaField 字段
column.setJavaField(toCamelCase(column.getColumnName()));
// 处理 dictType 字段,暂无
// 处理 javaType 字段
String dbType = StrUtil.subBefore(column.getColumnType(), '(', false);
javaTypeMappings.entrySet().stream()
.filter(entry -> entry.getValue().contains(dbType))
.findFirst().ifPresent(entry -> column.setJavaType(entry.getKey()));
if (column.getJavaType() == null) {
throw new IllegalStateException(String.format("column(%s) 的数据库类型(%s) 找不到匹配的 Java 类型",
column.getColumnName(), column.getColumnType()));
}
}
private void processColumnOperation(CodegenColumnDO column) {
// 处理 createOperation 字段
column.setCreateOperation(!CREATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
&& !column.getPrimaryKey()); // 对于主键,创建时无需传递
// 处理 updateOperation 字段
column.setUpdateOperation(!UPDATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
|| column.getPrimaryKey()); // 对于主键,更新时需要传递
// 处理 listOperation 字段
column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
&& !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递
// 处理 listOperationCondition 字段
columnListOperationConditionMappings.entrySet().stream()
.filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
.findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition()));
if (column.getListOperationCondition() == null) {
column.setListOperationCondition(CodegenColumnListConditionEnum.EQ.getCondition());
}
// 处理 listOperationResult 字段
column.setListOperationResult(!LIST_OPERATION_RESULT_EXCLUDE_COLUMN.contains(column.getJavaField()));
}
private void processColumnUI(CodegenColumnDO column) {
// 基于后缀进行匹配
columnHtmlTypeMappings.entrySet().stream()
.filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
.findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType()));
// 如果是 Boolean 类型时,设置为 radio 类型.
// 其它类型,因为字段名可以相对保障,所以不进行处理。例如说 date 对应 datetime 类型.
if (Boolean.class.getSimpleName().equals(column.getJavaType())) {
column.setHtmlType(CodegenColumnHtmlTypeEnum.RADIO.getType());
}
// 兜底,设置默认为 input 类型
if (column.getHtmlType() == null) {
column.setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType());
}
}
}

View File

@@ -0,0 +1,225 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.engine.velocity.VelocityEngine;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
import com.google.common.collect.Maps;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static cn.hutool.core.map.MapUtil.getStr;
import static cn.hutool.core.text.CharSequenceUtil.*;
/**
* 代码生成的引擎,用于具体生成代码
* 目前基于 {@link org.apache.velocity.app.Velocity} 模板引擎实现
*
* 考虑到 Java 模板引擎的框架非常多Freemarker、Velocity、Thymeleaf 等等,所以我们采用 hutool 封装的 {@link cn.hutool.extra.template.Template} 抽象
*
* @author 芋道源码
*/
@Component
public class CodegenEngine {
/**
* 模板配置
* key模板在 resources 的地址
* value生成的路径
*/
private static final Map<String, String> TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
// Java module-impl Main
.put(javaTemplatePath("controller/vo/baseVO"), javaModuleImplVOFilePath("BaseVO"))
.put(javaTemplatePath("controller/vo/createReqVO"), javaModuleImplVOFilePath("CreateReqVO"))
.put(javaTemplatePath("controller/vo/pageReqVO"), javaModuleImplVOFilePath("PageReqVO"))
.put(javaTemplatePath("controller/vo/respVO"), javaModuleImplVOFilePath("RespVO"))
.put(javaTemplatePath("controller/vo/updateReqVO"), javaModuleImplVOFilePath("UpdateReqVO"))
.put(javaTemplatePath("controller/vo/exportReqVO"), javaModuleImplVOFilePath("ExportReqVO"))
.put(javaTemplatePath("controller/vo/excelVO"), javaModuleImplVOFilePath("ExcelVO"))
.put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath())
.put(javaTemplatePath("convert/convert"),
javaModuleImplMainFilePath("convert/${table.businessName}/${table.className}Convert"))
.put(javaTemplatePath("dal/do"),
javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${table.className}DO"))
.put(javaTemplatePath("dal/mapper"),
javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${table.className}Mapper"))
.put(javaTemplatePath("service/serviceImpl"),
javaModuleImplMainFilePath("service/${table.businessName}/${table.className}ServiceImpl"))
.put(javaTemplatePath("service/service"),
javaModuleImplMainFilePath("service/${table.businessName}/${table.className}Service"))
// Java module-impl Test
.put(javaTemplatePath("test/serviceTest"),
javaModuleImplTestFilePath("service/${table.businessName}/${table.className}ServiceImplTest"))
// Java module-api Main
.put(javaTemplatePath("enums/errorcode"), javaModuleApiMainFilePath("enums/ErrorCodeConstants_手动操作"))
// Vue
.put(vueTemplatePath("views/index.vue"),
vueFilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
.put(vueTemplatePath("api/api.js"),
vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
// SQL
.put("codegen/sql/sql.vm", "sql/sql.sql")
.put("codegen/sql/h2.vm", "sql/h2.sql")
.build();
@Resource
private CodegenProperties codegenProperties;
/**
* 模板引擎,由 hutool 实现
*/
private final TemplateEngine templateEngine;
/**
* 全局通用变量映射
*/
private final Map<String, Object> globalBindingMap = new HashMap<>();
public CodegenEngine() {
// 初始化 TemplateEngine 属性
TemplateConfig config = new TemplateConfig();
config.setResourceMode(TemplateConfig.ResourceMode.CLASSPATH);
this.templateEngine = new VelocityEngine(config);
}
@PostConstruct
private void initGlobalBindingMap() {
// 全局配置
globalBindingMap.put("basePackage", codegenProperties.getBasePackage());
globalBindingMap.put("baseFrameworkPackage", codegenProperties.getBasePackage()
+ '.' + "framework"); // 用于后续获取测试类的 package 地址
// 全局 Java Bean
globalBindingMap.put("CommonResultClassName", CommonResult.class.getName());
globalBindingMap.put("PageResultClassName", PageResult.class.getName());
// VO 类,独有字段
globalBindingMap.put("PageParamClassName", PageParam.class.getName());
globalBindingMap.put("DictFormatClassName", DictFormat.class.getName());
// DO 类,独有字段
globalBindingMap.put("BaseDOClassName", BaseDO.class.getName());
globalBindingMap.put("baseDOFields", CodegenBuilder.BASE_DO_FIELDS);
globalBindingMap.put("QueryWrapperClassName", LambdaQueryWrapperX.class.getName());
globalBindingMap.put("BaseMapperClassName", BaseMapperX.class.getName());
// Util 工具类
globalBindingMap.put("ServiceExceptionUtilClassName", ServiceExceptionUtil.class.getName());
globalBindingMap.put("DateUtilsClassName", DateUtils.class.getName());
globalBindingMap.put("ExcelUtilsClassName", ExcelUtils.class.getName());
globalBindingMap.put("ObjectUtilsClassName", ObjectUtils.class.getName());
globalBindingMap.put("DictConvertClassName", DictConvert.class.getName());
globalBindingMap.put("OperateLogClassName", OperateLog.class.getName());
globalBindingMap.put("OperateTypeEnumClassName", OperateTypeEnum.class.getName());
}
public Map<String, String> execute(CodegenTableDO table, List<CodegenColumnDO> columns) {
// 创建 bindingMap
Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);
bindingMap.put("table", table);
bindingMap.put("columns", columns);
bindingMap.put("primaryColumn", CollectionUtils.findFirst(columns, CodegenColumnDO::getPrimaryKey)); // 主键字段
bindingMap.put("sceneEnum", CodegenSceneEnum.valueOf(table.getScene()));
// className 相关
// 去掉指定前缀,将 TestDictType 转换成 DictType. 因为在 create 等方法后,不需要带上 Test 前缀
String simpleClassName = removePrefix(table.getClassName(), upperFirst(table.getModuleName()));
bindingMap.put("simpleClassName", simpleClassName);
bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // 将 DictType 转换成 dict_type
bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // 将 DictType 转换成 dictType用于变量
// 将 DictType 转换成 dict-type
String simpleClassNameStrikeCase = toSymbolCase(simpleClassName, '-');
bindingMap.put("simpleClassName_strikeCase", simpleClassNameStrikeCase);
// permission 前缀
bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
// 执行生成
final Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(TEMPLATES.size()); // 有序
TEMPLATES.forEach((vmPath, filePath) -> {
filePath = formatFilePath(filePath, bindingMap);
String content = templateEngine.getTemplate(vmPath).render(bindingMap);
result.put(filePath, content);
});
return result;
}
private String formatFilePath(String filePath, Map<String, Object> bindingMap) {
filePath = StrUtil.replace(filePath, "${basePackage}",
getStr(bindingMap, "basePackage").replaceAll("\\.", "/"));
filePath = StrUtil.replace(filePath, "${classNameVar}",
getStr(bindingMap, "classNameVar"));
// sceneEnum 包含的字段
CodegenSceneEnum sceneEnum = (CodegenSceneEnum) bindingMap.get("sceneEnum");
filePath = StrUtil.replace(filePath, "${sceneEnum.prefixClass}", sceneEnum.getPrefixClass());
filePath = StrUtil.replace(filePath, "${sceneEnum.basePackage}", sceneEnum.getBasePackage());
// table 包含的字段
CodegenTableDO table = (CodegenTableDO) bindingMap.get("table");
filePath = StrUtil.replace(filePath, "${table.moduleName}", table.getModuleName());
filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName());
filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName());
return filePath;
}
private static String javaTemplatePath(String path) {
return "codegen/java/" + path + ".vm";
}
private static String javaModuleImplVOFilePath(String path) {
return javaModuleFilePath("controller/${sceneEnum.basePackage}/${table.businessName}/" +
"vo/${sceneEnum.prefixClass}${table.className}" + path, "impl", "main");
}
private static String javaModuleImplControllerFilePath() {
return javaModuleFilePath("controller/${sceneEnum.basePackage}/${table.businessName}/" +
"${sceneEnum.prefixClass}${table.className}Controller", "impl", "main");
}
private static String javaModuleImplMainFilePath(String path) {
return javaModuleFilePath(path, "impl", "main");
}
private static String javaModuleApiMainFilePath(String path) {
return javaModuleFilePath(path, "api", "main");
}
private static String javaModuleImplTestFilePath(String path) {
return javaModuleFilePath(path, "impl", "test");
}
private static String javaModuleFilePath(String path, String module, String src) {
return "yudao-module-${table.moduleName}/" + // 顶级模块
"yudao-module-${table.moduleName}-" + module + "/" + // 子模块
"src/" + src + "/java/${basePackage}/module/${table.moduleName}/" + path + ".java";
}
private static String vueTemplatePath(String path) {
return "codegen/vue/" + path + ".vm";
}
private static String vueFilePath(String path) {
return "yudao-ui-${sceneEnum.basePackage}/" + // 顶级目录
"src/" + path;
}
}

View File

@@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.SchemaTableDO;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLPrimaryKey;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.alibaba.druid.sql.repository.SchemaRepository;
import org.apache.commons.collections4.KeyValue;
import org.apache.commons.collections4.keyvalue.DefaultKeyValue;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static com.alibaba.druid.sql.SQLUtils.normalize;
/**
* SQL 解析器,将创建表的 SQL解析成 {@link SchemaTableDO} 和 {@link SchemaColumnDO} 对象,
* 后续可以基于它们,生成代码~
*
* @author 芋道源码
*/
public class CodegenSQLParser {
/**
* 解析建表 SQL 语句,返回 {@link SchemaTableDO} 和 {@link SchemaColumnDO} 对象
*
* @param sql 建表 SQL 语句
* @return 解析结果
*/
public static KeyValue<SchemaTableDO, List<SchemaColumnDO>> parse(String sql) {
// 解析 SQL 成 Statement
SQLCreateTableStatement statement = parseCreateSQL(sql);
// 解析 Table 表
SchemaTableDO table = parseTable(statement);
// 解析 Column 字段
List<SchemaColumnDO> columns = parseColumns(statement);
columns.forEach(column -> column.setTableName(table.getTableName()));
// 返回
return new DefaultKeyValue<>(table, columns);
}
/**
* 使用 Druid 工具,建表 SQL 语句
*
* @param sql 建表 SQL 语句
* @return 创建 Statement
*/
private static SQLCreateTableStatement parseCreateSQL(String sql) {
// 解析 SQL
SchemaRepository repository = new SchemaRepository(DbType.mysql);
repository.console(sql);
// 获得该表对应的 MySqlCreateTableStatement 对象
String tableName = CollUtil.getFirst(repository.getDefaultSchema().getObjects()).getName();
return (MySqlCreateTableStatement) repository.findTable(tableName).getStatement();
}
private static SchemaTableDO parseTable(SQLCreateTableStatement statement) {
return SchemaTableDO.builder()
.tableName(statement.getTableSource().getTableName(true))
.tableComment(getCommentText(statement))
.build();
}
private static String getCommentText(SQLCreateTableStatement statement) {
if (statement == null || statement.getComment() == null) {
return "";
}
return ((SQLCharExpr) statement.getComment()).getText();
}
private static List<SchemaColumnDO> parseColumns(SQLCreateTableStatement statement) {
List<SchemaColumnDO> columns = new ArrayList<>();
statement.getTableElementList().forEach(element -> parseColumn(columns, element));
return columns;
}
private static void parseColumn(List<SchemaColumnDO> columns, SQLTableElement element) {
// 处理主键
if (element instanceof SQLPrimaryKey) {
parsePrimaryKey(columns, (SQLPrimaryKey) element);
return;
}
// 处理字段定义
if (element instanceof SQLColumnDefinition) {
parseColumnDefinition(columns, (SQLColumnDefinition) element);
}
}
private static void parsePrimaryKey(List<SchemaColumnDO> columns, SQLPrimaryKey primaryKey) {
String columnName = normalize(primaryKey.getColumns().get(0).toString()); // 暂时不考虑联合主键
// 匹配 columns 主键字段,设置为 primary
columns.stream().filter(column -> column.getColumnName().equals(columnName))
.forEach(column -> column.setPrimaryKey(true));
}
private static void parseColumnDefinition(List<SchemaColumnDO> columns, SQLColumnDefinition definition) {
String text = definition.toString().toUpperCase();
columns.add(SchemaColumnDO.builder()
.columnName(normalize(definition.getColumnName()))
.columnType(definition.getDataType().toString())
.columnComment(Objects.isNull(definition.getComment()) ? ""
: normalize(definition.getComment().toString()))
.nullable(!text.contains(" NOT NULL"))
.primaryKey(false)
.autoIncrement(text.contains("AUTO_INCREMENT"))
.ordinalPosition(columns.size() + 1)
.build());
}
}

View File

@@ -0,0 +1,75 @@
package cn.iocoder.yudao.module.infra.service.test;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExportReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoPageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoUpdateReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* 字典类型 Service 接口
*
* @author 芋道源码
*/
public interface TestDemoService {
/**
* 创建字典类型
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createTestDemo(@Valid TestDemoCreateReqVO createReqVO);
/**
* 更新字典类型
*
* @param updateReqVO 更新信息
*/
void updateTestDemo(@Valid TestDemoUpdateReqVO updateReqVO);
/**
* 删除字典类型
*
* @param id 编号
*/
void deleteTestDemo(Long id);
/**
* 获得字典类型
*
* @param id 编号
* @return 字典类型
*/
TestDemoDO getTestDemo(Long id);
/**
* 获得字典类型列表
*
* @param ids 编号
* @return 字典类型列表
*/
List<TestDemoDO> getTestDemoList(Collection<Long> ids);
/**
* 获得字典类型分页
*
* @param pageReqVO 分页查询
* @return 字典类型分页
*/
PageResult<TestDemoDO> getTestDemoPage(TestDemoPageReqVO pageReqVO);
/**
* 获得字典类型列表, 用于 Excel 导出
*
* @param exportReqVO 查询条件
* @return 字典类型列表
*/
List<TestDemoDO> getTestDemoList(TestDemoExportReqVO exportReqVO);
}

View File

@@ -0,0 +1,85 @@
package cn.iocoder.yudao.module.infra.service.test;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExportReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoPageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoUpdateReqVO;
import cn.iocoder.yudao.module.infra.convert.test.TestDemoConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
import cn.iocoder.yudao.module.infra.dal.mysql.test.TestDemoMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.TEST_DEMO_NOT_EXISTS;
/**
* 字典类型 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class TestDemoServiceImpl implements TestDemoService {
@Resource
private TestDemoMapper testDemoMapper;
@Override
public Long createTestDemo(TestDemoCreateReqVO createReqVO) {
// 插入
TestDemoDO testDemo = TestDemoConvert.INSTANCE.convert(createReqVO);
testDemoMapper.insert(testDemo);
// 返回
return testDemo.getId();
}
@Override
public void updateTestDemo(TestDemoUpdateReqVO updateReqVO) {
// 校验存在
this.validateTestDemoExists(updateReqVO.getId());
// 更新
TestDemoDO updateObj = TestDemoConvert.INSTANCE.convert(updateReqVO);
testDemoMapper.updateById(updateObj);
}
@Override
public void deleteTestDemo(Long id) {
// 校验存在
this.validateTestDemoExists(id);
// 删除
testDemoMapper.deleteById(id);
}
private void validateTestDemoExists(Long id) {
if (testDemoMapper.selectById(id) == null) {
throw exception(TEST_DEMO_NOT_EXISTS);
}
}
@Override
public TestDemoDO getTestDemo(Long id) {
return testDemoMapper.selectById(id);
}
@Override
public List<TestDemoDO> getTestDemoList(Collection<Long> ids) {
return testDemoMapper.selectBatchIds(ids);
}
@Override
public PageResult<TestDemoDO> getTestDemoPage(TestDemoPageReqVO pageReqVO) {
return testDemoMapper.selectPage(pageReqVO);
}
@Override
public List<TestDemoDO> getTestDemoList(TestDemoExportReqVO exportReqVO) {
return testDemoMapper.selectList(exportReqVO);
}
}

View File

@@ -0,0 +1,109 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName};
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
#if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import ${PageResultClassName};
import ${CommonResultClassName};
import static ${CommonResultClassName}.success;
import ${ExcelUtilsClassName};
import ${OperateLogClassName};
import static ${OperateTypeEnumClassName}.*;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
import ${basePackage}.module.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
import ${basePackage}.module.${table.moduleName}.service.${table.businessName}.${table.className}Service;
@Api(tags = "${sceneEnum.name} - ${table.classComment}")
@RestController
##二级的 businessName 暂时不算在 HTTP 路径上,可以根据需要写
@RequestMapping("/${table.moduleName}/${simpleClassName_strikeCase}")
@Validated
public class ${sceneEnum.prefixClass}${table.className}Controller {
@Resource
private ${table.className}Service ${classNameVar}Service;
@PostMapping("/create")
@ApiOperation("创建${table.classComment}")
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")#end
public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO) {
return success(${classNameVar}Service.create${simpleClassName}(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新${table.classComment}")
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")#end
public CommonResult<Boolean> update${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO) {
${classNameVar}Service.update${simpleClassName}(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除${table.classComment}")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = ${primaryColumn.javaType}.class)
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")#end
public CommonResult<Boolean> delete${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
${classNameVar}Service.delete${simpleClassName}(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得${table.classComment}")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = ${primaryColumn.javaType}.class)
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
public CommonResult<${sceneEnum.prefixClass}${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id);
return success(${table.className}Convert.INSTANCE.convert(${classNameVar}));
}
@GetMapping("/list")
@ApiOperation("获得${table.classComment}列表")
@ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
public CommonResult<List<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}List(@RequestParam("ids") Collection<${primaryColumn.javaType}> ids) {
List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(ids);
return success(${table.className}Convert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@ApiOperation("获得${table.classComment}分页")
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
public CommonResult<PageResult<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageVO) {
PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageVO);
return success(${table.className}Convert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@ApiOperation("导出${table.classComment} Excel")
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")#end
@OperateLog(type = EXPORT)
public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(exportReqVO);
// 导出 Excel
List<${sceneEnum.prefixClass}${table.className}ExcelVO> datas = ${table.className}Convert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${sceneEnum.prefixClass}${table.className}ExcelVO.class, datas);
}
}

View File

@@ -0,0 +1,13 @@
## 提供给 baseVO、createVO、updateVO 生成字段
@ApiModelProperty(value = "${column.columnComment}"#if (!${column.nullable}), required = true#end#if ("$!column.example" != ""), example = "${column.example}"#end)
#if (!${column.nullable})## 判断 @NotEmpty 和 @NotNull 注解
#if (${field.fieldType} == 'String')
@NotEmpty(message = "${column.columnComment}不能为空")
#else
@NotNull(message = "${column.columnComment}不能为空")
#end
#end
#if (${column.javaType} == "Date")## 时间类型
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
#end
private ${column.javaType} ${column.javaField};

View File

@@ -0,0 +1,31 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult}
&& ${column.javaType} == "Date")## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#break
#end
#end
/**
* ${table.classComment} Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class ${sceneEnum.prefixClass}${table.className}BaseVO {
#foreach ($column in $columns)
#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult})##通用操作
#parse("codegen/java/controller/vo/_column.vm")
#end
#end
}

View File

@@ -0,0 +1,30 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.createOperation} && (!${column.updateOperation} || !${column.listOperationResult})
&& ${column.javaType} == "Date")## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#break
#end
#end
@ApiModel("${sceneEnum.name} - ${table.classComment}创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ${sceneEnum.prefixClass}${table.className}CreateReqVO extends ${sceneEnum.prefixClass}${table.className}BaseVO {
#foreach ($column in $columns)
#if (${column.createOperation} && (!${column.updateOperation} || !${column.listOperationResult}))##不是通用字段
#parse("codegen/java/controller/vo/_column.vm")
#end
#end
}

View File

@@ -0,0 +1,37 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
#foreach ($column in $columns)
#if ("$!column.dictType" != "")## 有设置数据字典
import ${DictFormatClassName};
import ${DictConvertClassName};
#break
#end
#end
/**
* ${table.classComment} Excel VO
*
* @author ${table.author}
*/
@Data
public class ${sceneEnum.prefixClass}${table.className}ExcelVO {
#foreach ($column in $columns)
#if (${column.listOperationResult})##返回字段
#if ("$!column.dictType" != "")##处理枚举值
@ExcelProperty(value = "${column.columnComment}", converter = DictConvert.class)
@DictFormat("${column.dictType}") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中
#else
@ExcelProperty("${column.columnComment}")
#end
private ${column.javaType} ${column.javaField};
#end
#end
}

View File

@@ -0,0 +1,42 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import ${PageParamClassName};
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.listOperation} && ${column.javaType} == "Date")## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#break
#end
#end
## 字段模板
#macro(columnTpl $prefix $prefixStr)
#if (${column.javaType} == "Date")## 时间类型
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
#end
@ApiModelProperty(value = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
#end
@ApiModel(value = "${sceneEnum.name} - ${table.classComment} Excel 导出 Request VO", description = "参数和 ${table.className}PageReqVO 是一致的")
@Data
public class ${sceneEnum.prefixClass}${table.className}ExportReqVO {
#foreach ($column in $columns)
#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
#if (${column.listOperation})##查询操作
#if (${column.listOperationCondition} == "BETWEEN")## 情况一Between 的时候
#columnTpl('begin', '开始')
#columnTpl('end', '结束')
#else##情况二,非 Between 的时间
#columnTpl('', '')
#end
#end
#end
}

View File

@@ -0,0 +1,44 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import ${PageParamClassName};
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.listOperation} && ${column.javaType} == "Date")## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#break
#end
#end
## 字段模板
#macro(columnTpl $prefix $prefixStr)
#if (${column.javaType} == "Date")## 时间类型
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
#end
@ApiModelProperty(value = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
#end
@ApiModel("${sceneEnum.name} - ${table.classComment}分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ${sceneEnum.prefixClass}${table.className}PageReqVO extends PageParam {
#foreach ($column in $columns)
#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
#if (${column.listOperation})##查询操作
#if (${column.listOperationCondition} == "BETWEEN")## 情况一Between 的时候
#columnTpl('begin', '开始')
#columnTpl('end', '结束')
#else##情况二,非 Between 的时间
#columnTpl('', '')
#end
#end
#end
}

View File

@@ -0,0 +1,20 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
@ApiModel("${sceneEnum.name} - ${table.classComment} Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ${sceneEnum.prefixClass}${table.className}RespVO extends ${sceneEnum.prefixClass}${table.className}BaseVO {
#foreach ($column in $columns)
#if (${column.listOperationResult} && (!${column.createOperation} || !${column.updateOperation}))##不是通用字段
@ApiModelProperty(value = "${column.columnComment}"#if (!${column.nullable}), required = true#end#if ("$!column.example" != ""), example = "${column.example}"#end)
private ${column.javaType} ${column.javaField};
#end
#end
}

View File

@@ -0,0 +1,30 @@
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.updateOperation} && (!${column.createOperation} || !${column.listOperationResult})
&& ${column.javaType} == "Date"))## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#break
#end
#end
@ApiModel("${sceneEnum.name} - ${table.classComment}更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ${sceneEnum.prefixClass}${table.className}UpdateReqVO extends ${sceneEnum.prefixClass}${table.className}BaseVO {
#foreach ($column in $columns)
#if (${column.updateOperation} && (!${column.createOperation} || !${column.listOperationResult}))##不是通用字段
#parse("codegen/java/controller/vo/_column.vm")
#end
#end
}

View File

@@ -0,0 +1,34 @@
package ${basePackage}.module.${table.moduleName}.convert.${table.businessName};
import java.util.*;
import ${PageResultClassName};
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
/**
* ${table.classComment} Convert
*
* @author ${table.author}
*/
@Mapper
public interface ${table.className}Convert {
${table.className}Convert INSTANCE = Mappers.getMapper(${table.className}Convert.class);
${table.className}DO convert(${sceneEnum.prefixClass}${table.className}CreateReqVO bean);
${table.className}DO convert(${sceneEnum.prefixClass}${table.className}UpdateReqVO bean);
${sceneEnum.prefixClass}${table.className}RespVO convert(${table.className}DO bean);
List<${sceneEnum.prefixClass}${table.className}RespVO> convertList(List<${table.className}DO> list);
PageResult<${sceneEnum.prefixClass}${table.className}RespVO> convertPage(PageResult<${table.className}DO> page);
List<${sceneEnum.prefixClass}${table.className}ExcelVO> convertList02(List<${table.className}DO> list);
}

View File

@@ -0,0 +1,38 @@
package ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName};
import lombok.*;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import ${BaseDOClassName};
/**
* ${table.classComment} DO
*
* @author ${table.author}
*/
@TableName("${table.tableName}")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ${table.className}DO extends BaseDO {
#foreach ($column in $columns)
#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段
/**
* ${column.columnComment}
#if ("$!column.dictType" != "")##处理枚举值
*
* 枚举 {@link TODO ${column.dictType} 对应的类}
#end
*/
#if (${column.primaryKey})##处理主键
@TableId#if (${column.javaType} == 'String')type = IdType.INPUT)#end
#end
private ${column.javaType} ${column.javaField};
#end
#end
}

View File

@@ -0,0 +1,66 @@
package ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName};
import java.util.*;
import ${PageResultClassName};
import ${QueryWrapperClassName};
import ${BaseMapperClassName};
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
import org.apache.ibatis.annotations.Mapper;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
## 字段模板
#macro(listCondition)
#foreach ($column in $columns)
#if (${column.listOperation})
#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
#if (${column.listOperationCondition} == "=")##情况一,= 的时候
.eqIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
#end
#if (${column.listOperationCondition} == "!=")##情况二,!= 的时候
.neIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
#end
#if (${column.listOperationCondition} == ">")##情况三,> 的时候
.gtIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
#end
#if (${column.listOperationCondition} == ">=")##情况四,>= 的时候
.geIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
#end
#if (${column.listOperationCondition} == "<")##情况五,< 的时候
.ltIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
#end
#if (${column.listOperationCondition} == "<=")##情况五,<= 的时候
.leIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
#end
#if (${column.listOperationCondition} == "LIKE")##情况七Like 的时候
.likeIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
#end
#if (${column.listOperationCondition} == "BETWEEN")##情况八Between 的时候
.betweenIfPresent(${table.className}DO::get${JavaField}, reqVO.getBegin${JavaField}(), reqVO.getEnd${JavaField}())
#end
#end
#end
#end
/**
* ${table.classComment} Mapper
*
* @author ${table.author}
*/
@Mapper
public interface ${table.className}Mapper extends BaseMapperX<${table.className}DO> {
default PageResult<${table.className}DO> selectPage(${sceneEnum.prefixClass}${table.className}PageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<${table.className}DO>()
#listCondition()
.orderByDesc(${table.className}DO::getId));## 大多数情况下id 倒序
}
default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<${table.className}DO>()
#listCondition()
.orderByDesc(${table.className}DO::getId));## 大多数情况下id 倒序
}
}

View File

@@ -0,0 +1,3 @@
// TODO 待办:请将下面的错误码复制到 yudao-module-${table.moduleName}-api 模块的 ErrorCodeConstants 类中。注意请给“TODO 补充编号”设置一个错误码编号!!!
// ========== ${table.classComment} TODO 补充编号 ==========
ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${table.classComment}不存在");

View File

@@ -0,0 +1,70 @@
package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
import java.util.*;
import javax.validation.*;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
import ${PageResultClassName};
/**
* ${table.classComment} Service 接口
*
* @author ${table.author}
*/
public interface ${table.className}Service {
/**
* 创建${table.classComment}
*
* @param createReqVO 创建信息
* @return 编号
*/
${primaryColumn.javaType} create${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO);
/**
* 更新${table.classComment}
*
* @param updateReqVO 更新信息
*/
void update${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO);
/**
* 删除${table.classComment}
*
* @param id 编号
*/
void delete${simpleClassName}(${primaryColumn.javaType} id);
/**
* 获得${table.classComment}
*
* @param id 编号
* @return ${table.classComment}
*/
${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id);
/**
* 获得${table.classComment}列表
*
* @param ids 编号
* @return ${table.classComment}列表
*/
List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids);
/**
* 获得${table.classComment}分页
*
* @param pageReqVO 分页查询
* @return ${table.classComment}分页
*/
PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO);
/**
* 获得${table.classComment}列表, 用于 Excel 导出
*
* @param exportReqVO 查询条件
* @return ${table.classComment}列表
*/
List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO);
}

View File

@@ -0,0 +1,82 @@
package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
import ${PageResultClassName};
import ${basePackage}.module.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
import static ${ServiceExceptionUtilClassName}.exception;
import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*;
/**
* ${table.classComment} Service 实现类
*
* @author ${table.author}
*/
@Service
@Validated
public class ${table.className}ServiceImpl implements ${table.className}Service {
@Resource
private ${table.className}Mapper ${classNameVar}Mapper;
@Override
public ${primaryColumn.javaType} create${simpleClassName}(${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO) {
// 插入
${table.className}DO ${classNameVar} = ${table.className}Convert.INSTANCE.convert(createReqVO);
${classNameVar}Mapper.insert(${classNameVar});
// 返回
return ${classNameVar}.getId();
}
@Override
public void update${simpleClassName}(${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO) {
// 校验存在
this.validate${simpleClassName}Exists(updateReqVO.getId());
// 更新
${table.className}DO updateObj = ${table.className}Convert.INSTANCE.convert(updateReqVO);
${classNameVar}Mapper.updateById(updateObj);
}
@Override
public void delete${simpleClassName}(${primaryColumn.javaType} id) {
// 校验存在
this.validate${simpleClassName}Exists(id);
// 删除
${classNameVar}Mapper.deleteById(id);
}
private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) {
if (${classNameVar}Mapper.selectById(id) == null) {
throw exception(${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
}
}
@Override
public ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id) {
return ${classNameVar}Mapper.selectById(id);
}
@Override
public List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids) {
return ${classNameVar}Mapper.selectBatchIds(ids);
}
@Override
public PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {
return ${classNameVar}Mapper.selectPage(pageReqVO);
}
@Override
public List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO) {
return ${classNameVar}Mapper.selectList(exportReqVO);
}
}

View File

@@ -0,0 +1,161 @@
package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import ${basePackage}.module.${table.moduleName}.test.BaseDbUnitTest;## 每个项目,默认有一个基础 DB Test 基类
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
import ${PageResultClassName};
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*;
import static cn.hutool.core.util.RandomUtil.*;
import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*;
import static ${baseFrameworkPackage}.test.core.util.AssertUtils.*;
import static ${baseFrameworkPackage}.test.core.util.RandomUtils.*;
import static ${ObjectUtilsClassName}.*;
import static ${DateUtilsClassName}.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
## 字段模板
#macro(getPageCondition $VO)
// mock 数据
${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class, o -> { // 等会查询到
#foreach ($column in $columns)
#if (${column.listOperation})
#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
o.set$JavaField(null);
#end
#end
});
${classNameVar}Mapper.insert(db${simpleClassName});
#foreach ($column in $columns)
#if (${column.listOperation})
#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
// 测试 ${column.javaField} 不匹配
${classNameVar}Mapper.insert(cloneIgnoreId(db${simpleClassName}, o -> o.set$JavaField(null)));
#end
#end
// 准备参数
${sceneEnum.prefixClass}${table.className}${VO} reqVO = new ${sceneEnum.prefixClass}${table.className}${VO}();
#foreach ($column in $columns)
#if (${column.listOperation})
#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
#if (${column.listOperationCondition} == "BETWEEN")## BETWEEN 的情况
reqVO.setBegin${JavaField}(null);
reqVO.setEnd${JavaField}(null);
#else
reqVO.set$JavaField(null);
#end
#end
#end
#end
/**
* {@link ${table.className}ServiceImpl} 的单元测试类
*
* @author ${table.author}
*/
@Import(${table.className}ServiceImpl.class)
public class ${table.className}ServiceImplTest extends BaseDbUnitTest {
@Resource
private ${table.className}ServiceImpl ${classNameVar}Service;
@Resource
private ${table.className}Mapper ${classNameVar}Mapper;
@Test
public void testCreate${simpleClassName}_success() {
// 准备参数
${sceneEnum.prefixClass}${table.className}CreateReqVO reqVO = randomPojo(${sceneEnum.prefixClass}${table.className}CreateReqVO.class);
// 调用
${primaryColumn.javaType} ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(reqVO);
// 断言
assertNotNull(${classNameVar}Id);
// 校验记录的属性是否正确
${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(${classNameVar}Id);
assertPojoEquals(reqVO, ${classNameVar});
}
@Test
public void testUpdate${simpleClassName}_success() {
// mock 数据
${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
// 准备参数
${sceneEnum.prefixClass}${table.className}UpdateReqVO reqVO = randomPojo(${sceneEnum.prefixClass}${table.className}UpdateReqVO.class, o -> {
o.setId(db${simpleClassName}.getId()); // 设置更新的 ID
});
// 调用
${classNameVar}Service.update${simpleClassName}(reqVO);
// 校验是否更新正确
${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, ${classNameVar});
}
@Test
public void testUpdate${simpleClassName}_notExists() {
// 准备参数
${sceneEnum.prefixClass}${table.className}UpdateReqVO reqVO = randomPojo(${sceneEnum.prefixClass}${table.className}UpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> ${classNameVar}Service.update${simpleClassName}(reqVO), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
}
@Test
public void testDelete${simpleClassName}_success() {
// mock 数据
${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
// 准备参数
${primaryColumn.javaType} id = db${simpleClassName}.getId();
// 调用
${classNameVar}Service.delete${simpleClassName}(id);
// 校验数据不存在了
assertNull(${classNameVar}Mapper.selectById(id));
}
@Test
public void testDelete${simpleClassName}_notExists() {
// 准备参数
${primaryColumn.javaType} id = random${primaryColumn.javaType}Id();
// 调用, 并断言异常
assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
}
@Test // TODO 请修改 null 为需要的值
public void testGet${simpleClassName}Page() {
#getPageCondition("PageReqVO")
// 调用
PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0));
}
@Test // TODO 请修改 null 为需要的值
public void testGet${simpleClassName}List() {
#getPageCondition("ExportReqVO")
// 调用
List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO);
// 断言
assertEquals(1, list.size());
assertPojoEquals(db${simpleClassName}, list.get(0));
}
}

View File

@@ -0,0 +1,24 @@
-- 将该建表 SQL 语句,添加到 yudao-module-${table.moduleName}-impl 模块的 test/resources/sql/create_tables.sql 文件里
CREATE TABLE IF NOT EXISTS "${table.tableName}" (
#foreach ($column in $columns)
#if (${column.primaryKey})##处理主键
"${column.javaField}"#if (${column.javaType} == 'String') ${column.columnType} NOT NULL#else ${column.columnType} NOT NULL GENERATED BY DEFAULT AS IDENTITY#end,
#else
#if (${column.columnName} == 'create_time')
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
#elseif (${column.columnName} == 'update_time')
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
#elseif (${column.columnName} == 'creator' || ${column.columnName} == 'updater')
"${column.columnName}" ${column.columnType} DEFAULT '',
#elseif (${column.columnName} == 'deleted')
"deleted" bit NOT NULL DEFAULT FALSE,
#else
"${column.columnName}" ${column.columnType}#if (${column.nullable} == false) NOT NULL#end,
#end
#end
#end
PRIMARY KEY ("${primaryColumn.columnName}")
) COMMENT '${table.tableComment}';
-- 将该删表 SQL 语句,添加到 yudao-module-${table.moduleName}-impl 模块的 test/resources/sql/clean.sql 文件里
DELETE FROM "${table.tableName}";

View File

@@ -0,0 +1,27 @@
-- 菜单 SQL
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${classNameVar}/index', 0
);
-- 按钮父菜单ID
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
#foreach ($functionName in $functionNames)
#set ($index = $foreach.count - 1)
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,
'', '', '', 0
);
#end

View File

@@ -0,0 +1,55 @@
import request from '@/utils/request'
#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
// 创建${table.classComment}
export function create${simpleClassName}(data) {
return request({
url: '${baseURL}/create',
method: 'post',
data: data
})
}
// 更新${table.classComment}
export function update${simpleClassName}(data) {
return request({
url: '${baseURL}/update',
method: 'put',
data: data
})
}
// 删除${table.classComment}
export function delete${simpleClassName}(id) {
return request({
url: '${baseURL}/delete?id=' + id,
method: 'delete'
})
}
// 获得${table.classComment}
export function get${simpleClassName}(id) {
return request({
url: '${baseURL}/get?id=' + id,
method: 'get'
})
}
// 获得${table.classComment}分页
export function get${simpleClassName}Page(query) {
return request({
url: '${baseURL}/page',
method: 'get',
params: query
})
}
// 导出${table.classComment} Excel
export function export${simpleClassName}Excel(query) {
return request({
url: '${baseURL}/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@@ -0,0 +1,399 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
#foreach($column in $columns)
#if ($column.listOperation)
#set ($dictType=$column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment=$column.columnComment)
#if ($column.htmlType == "input")
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="queryParams.${javaField}" placeholder="请输入${comment}" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
#elseif ($column.htmlType == "select" || $column.htmlType == "radio")
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="queryParams.${javaField}" placeholder="请选择${comment}" clearable size="small">
#if ("" != $dictType)## 设置了 dictType 数据字典的情况
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" :label="dict.label" :value="dict.value"/>
#else## 未设置 dictType 数据字典的情况
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "datetime")
#if ($column.listOperationCondition != "BETWEEN")## 非范围
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker clearable size="small" v-model="queryParams.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${comment}" />
</el-form-item>
#else## 范围
<el-form-item label="${comment}">
<el-date-picker v-model="dateRange${AttrName}" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
#end
#end
#end
#end
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
#foreach($column in $columns)
#if ($column.listOperationResult)
#set ($dictType=$column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment=$column.columnComment)
#if ($column.javaType == "Date")## 时间类型
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.${javaField}) }}</span>
</template>
</el-table-column>
#elseif("" != $column.dictType)## 数据字典
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
</template>
</el-table-column>
#else
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['${permissionPrefix}:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['${permissionPrefix}:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
#foreach($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment)
#if ($column.htmlType == "input")
#if (!$column.primaryKey)## 忽略主键,不用在表单里
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="form.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
#end
#elseif($column.htmlType == "imageUpload")## 图片上传
#set ($hasImageUploadColumn = true)
<el-form-item label="${comment}">
<imageUpload v-model="form.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
#set ($hasFileUploadColumn = true)
<el-form-item label="${comment}">
<fileUpload v-model="form.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
#set ($hasEditorColumn = true)
<el-form-item label="${comment}">
<editor v-model="form.${javaField}" :min-height="192"/>
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="form.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="form.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="form.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker clearable size="small" v-model="form.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${comment}" />
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="form.${javaField}" type="textarea" placeholder="请输入内容" />
</el-form-item>
#end
#end
#end
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { create${simpleClassName}, update${simpleClassName}, delete${simpleClassName}, get${simpleClassName}, get${simpleClassName}Page, export${simpleClassName}Excel } from "@/api/${table.moduleName}/${classNameVar}";
#if ($hasImageUploadColumn)
import ImageUpload from '@/components/ImageUpload';
#end
#if ($hasFileUploadColumn)
import FileUpload from '@/components/FileUpload';
#end
#if ($hasEditorColumn)
import Editor from '@/components/Editor';
#end
export default {
name: "${simpleClassName}",
components: {
#if ($hasImageUploadColumn)
ImageUpload,
#end
#if ($hasFileUploadColumn)
FileUpload,
#end
#if ($hasEditorColumn)
Editor,
#end
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// ${table.classComment}列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
#foreach ($column in $columns)## 时间范围
#if ($column.listOperation)
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
dateRange${AttrName}: [],
#end
#end
#end
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
#foreach ($column in $columns)
#if ($column.listOperation && $column.listOperationCondition != 'BETWEEN')
$column.javaField: null,
#end
#end
},
// 表单参数
form: {},
// 表单校验
rules: {
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [{ required: true, message: "${comment}不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }],
#end
#end
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 处理查询参数
let params = {...this.queryParams};
#foreach ($column in $columns)
#if ($column.listOperation)
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
this.addBeginAndEndTime(params, this.dateRange${AttrName}, '${column.javaField}');
#end
#end
#end
// 执行查询
get${simpleClassName}Page(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
#foreach ($column in $columns)
#if ($column.listOperation)
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
this.dateRange${AttrName} = [];
#end
#end
#end
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加${table.classComment}";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
get${simpleClassName}(${primaryColumn.javaField}).then(response => {
this.form = response.data;
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")## checkbox 特殊处理
this.form.$column.javaField = this.form.${column.javaField}.split(",");
#end
#end
this.open = true;
this.title = "修改${table.classComment}";
});
},
/** 提交按钮 */
submitForm() {
this.#[[$]]#refs["form"].validate(valid => {
if (!valid) {
return;
}
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
this.form.$column.javaField = this.form.${column.javaField}.join(",");
#end
#end
// 修改的提交
if (this.form.${primaryColumn.javaField} != null) {
update${simpleClassName}(this.form).then(response => {
this.#[[$modal]]#.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
create${simpleClassName}(this.form).then(response => {
this.#[[$modal]]#.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?').then(function() {
return delete${simpleClassName}(${primaryColumn.javaField});
}).then(() => {
this.getList();
this.#[[$modal]]#.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
#foreach ($column in $columns)
#if ($column.listOperation)
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
this.addBeginAndEndTime(params, this.dateRange${AttrName}, '${column.javaField}');
#end
#end
#end
// 执行导出
this.#[[$modal]]#.confirm('是否确认导出所有${table.classComment}数据项?').then(() => {
this.exportLoading = true;
return export${simpleClassName}Excel(params);
}).then(response => {
this.$download.excel(response, '${table.classComment}.xls');
this.exportLoading = false;
}).catch(() => {});
}
}
};
</script>

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.infra.dal.mysql.codegen;
import cn.iocoder.yudao.module.tool.dal.dataobject.codegen.SchemaColumnDO;
import cn.iocoder.yudao.module.tool.test.BaseDbUnitTest;
import org.junit.jupiter.api.Test;
import javax.annotation.Resource;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SchemaColumnMapperTest extends BaseDbUnitTest {
@Resource
private SchemaColumnMapper schemaColumnMapper;
@Test
public void testSelectListByTableName() {
List<SchemaColumnDO> columns = schemaColumnMapper.selectListByTableName("", "inf_config");
assertTrue(columns.size() > 0);
}
}

View File

@@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.infra.dal.mysql;

View File

@@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.infra.dal;

View File

@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.infra.service.codegen;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest;
import org.junit.jupiter.api.Test;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
public class CodegenEngineTest extends BaseDbUnitTest {
@Resource
private CodegenTableMapper codegenTableMapper;
@Resource
private CodegenColumnMapper codegenColumnMapper;
@Resource
private CodegenEngine codegenEngine;
@Test
public void testExecute() {
CodegenTableDO table = codegenTableMapper.selectById(20);
List<CodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(table.getId());
Map<String, String> result = codegenEngine.execute(table, columns);
result.forEach((s, s2) -> System.out.println(s2));
// System.out.println(result.get("vue/views/system/test/index.vue"));
}
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.infra.service.codegen;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenSQLParser;
import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest;
import org.junit.jupiter.api.Test;
public class CodegenSQLParserTest extends BaseDbUnitTest {
@Test
public void testParse() {
String sql = "CREATE TABLE `infra_test_demo` (\n" +
" `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',\n" +
" `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名字',\n" +
" `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态',\n" +
" `type` tinyint(4) NOT NULL COMMENT '类型',\n" +
" `category` tinyint(4) NOT NULL COMMENT '分类',\n" +
" `remark` varchar(500) DEFAULT NULL COMMENT '备注',\n" +
" `create_by` varchar(64) DEFAULT '' COMMENT '创建者',\n" +
" `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n" +
" `update_by` varchar(64) DEFAULT '' COMMENT '更新者',\n" +
" `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n" +
" `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',\n" +
" PRIMARY KEY (`id`) USING BTREE\n" +
") ENGINE=InnoDB AUTO_INCREMENT=108 DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表';";
CodegenSQLParser.parse(sql);
// TODO 芋艿:后续完善断言
}
}

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.infra.service.codegen;
import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest;
import org.junit.jupiter.api.Test;
import javax.annotation.Resource;
class CodegenServiceImplTest extends BaseDbUnitTest {
@Resource
private CodegenServiceImpl codegenService;
@Test
public void tetCreateCodegenTable() {
codegenService.createCodegen(0L, "infra_test_demo");
// infraCodegenService.createCodegenTable("infra_codegen_table");
// infraCodegenService.createCodegen("infra_codegen_column");
}
}

View File

@@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.infra.service;

View File

@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.infra.test;
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisIntegrationTest.Application.class)
@ActiveProfiles("integration-test") // 设置使用 application-integration-test 配置文件
public class BaseDbAndRedisIntegrationTest {
@Import({
// DB 配置类
DynamicDataSourceAutoConfiguration.class, // Dynamic Datasource 配置类
YudaoDataSourceAutoConfiguration.class, // 自己的 DB 配置类
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
// MyBatis 配置类
YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
// Redis 配置类
RedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动高配置类
})
public static class Application {
}
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.infra.test;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseRedisIntegrationTest.Application.class)
@ActiveProfiles("integration-test") // 设置使用 application-integration-test 配置文件
public class BaseRedisIntegrationTest {
@Import({
// Redis 配置类
RedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动高配置类
})
public static class Application {
}
}

View File

@@ -0,0 +1,187 @@
package cn.iocoder.yudao.module.infra.service.test;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExportReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoPageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoUpdateReqVO;
import cn.iocoder.yudao.module.infra.dal.mysql.test.TestDemoMapper;
import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.TEST_DEMO_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link TestDemoServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(TestDemoServiceImpl.class)
public class TestDemoServiceImplTest extends BaseDbUnitTest {
@Resource
private TestDemoServiceImpl testDemoService;
@Resource
private TestDemoMapper testDemoMapper;
@Test
public void testCreateTestDemo_success() {
// 准备参数
TestDemoCreateReqVO reqVO = randomPojo(TestDemoCreateReqVO.class);
// 调用
Long testDemoId = testDemoService.createTestDemo(reqVO);
// 断言
assertNotNull(testDemoId);
// 校验记录的属性是否正确
TestDemoDO testDemo = testDemoMapper.selectById(testDemoId);
assertPojoEquals(reqVO, testDemo);
}
@Test
public void testUpdateTestDemo_success() {
// mock 数据
TestDemoDO dbTestDemo = randomPojo(TestDemoDO.class);
testDemoMapper.insert(dbTestDemo);// @Sql: 先插入出一条存在的数据
// 准备参数
TestDemoUpdateReqVO reqVO = randomPojo(TestDemoUpdateReqVO.class, o -> {
o.setId(dbTestDemo.getId()); // 设置更新的 ID
});
// 调用
testDemoService.updateTestDemo(reqVO);
// 校验是否更新正确
TestDemoDO testDemo = testDemoMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, testDemo);
}
@Test
public void testUpdateTestDemo_notExists() {
// 准备参数
TestDemoUpdateReqVO reqVO = randomPojo(TestDemoUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> testDemoService.updateTestDemo(reqVO), TEST_DEMO_NOT_EXISTS);
}
@Test
public void testDeleteTestDemo_success() {
// mock 数据
TestDemoDO dbTestDemo = randomPojo(TestDemoDO.class);
testDemoMapper.insert(dbTestDemo);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbTestDemo.getId();
// 调用
testDemoService.deleteTestDemo(id);
// 校验数据不存在了
assertNull(testDemoMapper.selectById(id));
}
@Test
public void testDeleteTestDemo_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> testDemoService.deleteTestDemo(id), TEST_DEMO_NOT_EXISTS);
}
@Test
public void testGetTestDemoPage() {
// mock 数据
TestDemoDO dbTestDemo = randomPojo(TestDemoDO.class, o -> { // 等会查询到
o.setName("芋道源码");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setType(1);
o.setCategory(2);
o.setRemark("哈哈哈");
o.setCreateTime(DateUtils.buildTime(2021, 11, 11));
});
testDemoMapper.insert(dbTestDemo);
// 测试 name 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setName("不匹配")));
// 测试 status 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 type 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setType(2)));
// 测试 category 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setCategory(1)));
// 测试 remark 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setRemark("呵呵呵")));
// 测试 createTime 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setCreateTime(DateUtils.buildTime(2021, 12, 12))));
// 准备参数
TestDemoPageReqVO reqVO = new TestDemoPageReqVO();
reqVO.setName("芋道");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setType(1);
reqVO.setCategory(2);
reqVO.setRemark("哈哈哈");
reqVO.setBeginCreateTime(DateUtils.buildTime(2021, 11, 10));
reqVO.setEndCreateTime(DateUtils.buildTime(2021, 11, 12));
// 调用
PageResult<TestDemoDO> pageResult = testDemoService.getTestDemoPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbTestDemo, pageResult.getList().get(0));
}
@Test
public void testGetTestDemoList() {
// mock 数据
TestDemoDO dbTestDemo = randomPojo(TestDemoDO.class, o -> { // 等会查询到
o.setName("芋道源码");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setType(1);
o.setCategory(2);
o.setRemark("哈哈哈");
o.setCreateTime(DateUtils.buildTime(2021, 11, 11));
});
testDemoMapper.insert(dbTestDemo);
// 测试 name 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setName("不匹配")));
// 测试 status 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 type 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setType(2)));
// 测试 category 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setCategory(1)));
// 测试 remark 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setRemark("呵呵呵")));
// 测试 createTime 不匹配
testDemoMapper.insert(cloneIgnoreId(dbTestDemo, o -> o.setCreateTime(DateUtils.buildTime(2021, 12, 12))));
// 准备参数
TestDemoExportReqVO reqVO = new TestDemoExportReqVO();
reqVO.setName("芋道");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setType(1);
reqVO.setCategory(2);
reqVO.setRemark("哈哈哈");
reqVO.setBeginCreateTime(DateUtils.buildTime(2021, 11, 10));
reqVO.setEndCreateTime(DateUtils.buildTime(2021, 11, 12));
// 调用
List<TestDemoDO> list = testDemoService.getTestDemoList(reqVO);
// 断言
assertEquals(1, list.size());
assertPojoEquals(dbTestDemo, list.get(0));
}
}

View File

@@ -7,3 +7,4 @@ DELETE FROM "infra_api_error_log";
DELETE FROM "infra_api_access_log";
DELETE FROM "infra_file";
DELETE FROM "infra_api_error_log";
DELETE FROM "infra_test_demo";

View File

@@ -135,3 +135,18 @@ CREATE TABLE IF NOT EXISTS "infra_api_error_log" (
"tenant_id" bigint not null default '0',
primary key ("id")
) COMMENT '系统异常日志';
CREATE TABLE IF NOT EXISTS "infra_test_demo" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(100) NOT NULL,
"status" tinyint NOT NULL,
"type" tinyint NOT NULL,
"category" tinyint NOT NULL,
"remark" varchar(500),
"creator" varchar(64) DEFAULT '''',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '''',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '字典类型表';